interpolation_test.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. from __future__ import absolute_import
  2. from __future__ import unicode_literals
  3. import pytest
  4. from compose.config.environment import Environment
  5. from compose.config.interpolation import interpolate_environment_variables
  6. from compose.config.interpolation import Interpolator
  7. from compose.config.interpolation import InvalidInterpolation
  8. from compose.config.interpolation import TemplateWithDefaults
  9. from compose.const import COMPOSEFILE_V2_0 as V2_0
  10. from compose.const import COMPOSEFILE_V2_3 as V2_3
  11. from compose.const import COMPOSEFILE_V3_4 as V3_4
  12. @pytest.fixture
  13. def mock_env():
  14. return Environment({
  15. 'USER': 'jenny',
  16. 'FOO': 'bar',
  17. 'TRUE': 'True',
  18. 'FALSE': 'OFF',
  19. 'POSINT': '50',
  20. 'NEGINT': '-200',
  21. 'FLOAT': '0.145',
  22. 'MODE': '0600',
  23. })
  24. @pytest.fixture
  25. def variable_mapping():
  26. return Environment({'FOO': 'first', 'BAR': ''})
  27. @pytest.fixture
  28. def defaults_interpolator(variable_mapping):
  29. return Interpolator(TemplateWithDefaults, variable_mapping).interpolate
  30. def test_interpolate_environment_variables_in_services(mock_env):
  31. services = {
  32. 'servicea': {
  33. 'image': 'example:${USER}',
  34. 'volumes': ['$FOO:/target'],
  35. 'logging': {
  36. 'driver': '${FOO}',
  37. 'options': {
  38. 'user': '$USER',
  39. }
  40. }
  41. }
  42. }
  43. expected = {
  44. 'servicea': {
  45. 'image': 'example:jenny',
  46. 'volumes': ['bar:/target'],
  47. 'logging': {
  48. 'driver': 'bar',
  49. 'options': {
  50. 'user': 'jenny',
  51. }
  52. }
  53. }
  54. }
  55. value = interpolate_environment_variables(V2_0, services, 'service', mock_env)
  56. assert value == expected
  57. def test_interpolate_environment_variables_in_volumes(mock_env):
  58. volumes = {
  59. 'data': {
  60. 'driver': '$FOO',
  61. 'driver_opts': {
  62. 'max': 2,
  63. 'user': '${USER}'
  64. }
  65. },
  66. 'other': None,
  67. }
  68. expected = {
  69. 'data': {
  70. 'driver': 'bar',
  71. 'driver_opts': {
  72. 'max': 2,
  73. 'user': 'jenny'
  74. }
  75. },
  76. 'other': {},
  77. }
  78. value = interpolate_environment_variables(V2_0, volumes, 'volume', mock_env)
  79. assert value == expected
  80. def test_interpolate_environment_variables_in_secrets(mock_env):
  81. secrets = {
  82. 'secretservice': {
  83. 'file': '$FOO',
  84. 'labels': {
  85. 'max': 2,
  86. 'user': '${USER}'
  87. }
  88. },
  89. 'other': None,
  90. }
  91. expected = {
  92. 'secretservice': {
  93. 'file': 'bar',
  94. 'labels': {
  95. 'max': 2,
  96. 'user': 'jenny'
  97. }
  98. },
  99. 'other': {},
  100. }
  101. value = interpolate_environment_variables(V3_4, secrets, 'secret', mock_env)
  102. assert value == expected
  103. def test_interpolate_environment_services_convert_types_v2(mock_env):
  104. entry = {
  105. 'service1': {
  106. 'blkio_config': {
  107. 'weight': '${POSINT}',
  108. 'weight_device': [{'file': '/dev/sda1', 'weight': '${POSINT}'}]
  109. },
  110. 'cpus': '${FLOAT}',
  111. 'cpu_count': '$POSINT',
  112. 'healthcheck': {
  113. 'retries': '${POSINT:-3}',
  114. 'disable': '${FALSE}',
  115. 'command': 'true'
  116. },
  117. 'mem_swappiness': '${DEFAULT:-127}',
  118. 'oom_score_adj': '${NEGINT}',
  119. 'scale': '${POSINT}',
  120. 'ulimits': {
  121. 'nproc': '${POSINT}',
  122. 'nofile': {
  123. 'soft': '${POSINT}',
  124. 'hard': '${DEFAULT:-40000}'
  125. },
  126. },
  127. 'privileged': '${TRUE}',
  128. 'read_only': '${DEFAULT:-no}',
  129. 'tty': '${DEFAULT:-N}',
  130. 'stdin_open': '${DEFAULT-on}',
  131. }
  132. }
  133. expected = {
  134. 'service1': {
  135. 'blkio_config': {
  136. 'weight': 50,
  137. 'weight_device': [{'file': '/dev/sda1', 'weight': 50}]
  138. },
  139. 'cpus': 0.145,
  140. 'cpu_count': 50,
  141. 'healthcheck': {
  142. 'retries': 50,
  143. 'disable': False,
  144. 'command': 'true'
  145. },
  146. 'mem_swappiness': 127,
  147. 'oom_score_adj': -200,
  148. 'scale': 50,
  149. 'ulimits': {
  150. 'nproc': 50,
  151. 'nofile': {
  152. 'soft': 50,
  153. 'hard': 40000
  154. },
  155. },
  156. 'privileged': True,
  157. 'read_only': False,
  158. 'tty': False,
  159. 'stdin_open': True,
  160. }
  161. }
  162. value = interpolate_environment_variables(V2_3, entry, 'service', mock_env)
  163. assert value == expected
  164. def test_interpolate_environment_services_convert_types_v3(mock_env):
  165. entry = {
  166. 'service1': {
  167. 'healthcheck': {
  168. 'retries': '${POSINT:-3}',
  169. 'disable': '${FALSE}',
  170. 'command': 'true'
  171. },
  172. 'ulimits': {
  173. 'nproc': '${POSINT}',
  174. 'nofile': {
  175. 'soft': '${POSINT}',
  176. 'hard': '${DEFAULT:-40000}'
  177. },
  178. },
  179. 'privileged': '${TRUE}',
  180. 'read_only': '${DEFAULT:-no}',
  181. 'tty': '${DEFAULT:-N}',
  182. 'stdin_open': '${DEFAULT-on}',
  183. 'deploy': {
  184. 'update_config': {
  185. 'parallelism': '${DEFAULT:-2}',
  186. 'max_failure_ratio': '${FLOAT}',
  187. },
  188. 'restart_policy': {
  189. 'max_attempts': '$POSINT',
  190. },
  191. 'replicas': '${DEFAULT-3}'
  192. },
  193. 'ports': [{'target': '${POSINT}', 'published': '${DEFAULT:-5000}'}],
  194. 'configs': [{'mode': '${MODE}', 'source': 'config1'}],
  195. 'secrets': [{'mode': '${MODE}', 'source': 'secret1'}],
  196. }
  197. }
  198. expected = {
  199. 'service1': {
  200. 'healthcheck': {
  201. 'retries': 50,
  202. 'disable': False,
  203. 'command': 'true'
  204. },
  205. 'ulimits': {
  206. 'nproc': 50,
  207. 'nofile': {
  208. 'soft': 50,
  209. 'hard': 40000
  210. },
  211. },
  212. 'privileged': True,
  213. 'read_only': False,
  214. 'tty': False,
  215. 'stdin_open': True,
  216. 'deploy': {
  217. 'update_config': {
  218. 'parallelism': 2,
  219. 'max_failure_ratio': 0.145,
  220. },
  221. 'restart_policy': {
  222. 'max_attempts': 50,
  223. },
  224. 'replicas': 3
  225. },
  226. 'ports': [{'target': 50, 'published': 5000}],
  227. 'configs': [{'mode': 0o600, 'source': 'config1'}],
  228. 'secrets': [{'mode': 0o600, 'source': 'secret1'}],
  229. }
  230. }
  231. value = interpolate_environment_variables(V3_4, entry, 'service', mock_env)
  232. assert value == expected
  233. def test_interpolate_environment_network_convert_types(mock_env):
  234. entry = {
  235. 'network1': {
  236. 'external': '${FALSE}',
  237. 'attachable': '${TRUE}',
  238. 'internal': '${DEFAULT:-false}'
  239. }
  240. }
  241. expected = {
  242. 'network1': {
  243. 'external': False,
  244. 'attachable': True,
  245. 'internal': False,
  246. }
  247. }
  248. value = interpolate_environment_variables(V3_4, entry, 'network', mock_env)
  249. assert value == expected
  250. def test_interpolate_environment_external_resource_convert_types(mock_env):
  251. entry = {
  252. 'resource1': {
  253. 'external': '${TRUE}',
  254. }
  255. }
  256. expected = {
  257. 'resource1': {
  258. 'external': True,
  259. }
  260. }
  261. value = interpolate_environment_variables(V3_4, entry, 'network', mock_env)
  262. assert value == expected
  263. value = interpolate_environment_variables(V3_4, entry, 'volume', mock_env)
  264. assert value == expected
  265. value = interpolate_environment_variables(V3_4, entry, 'secret', mock_env)
  266. assert value == expected
  267. value = interpolate_environment_variables(V3_4, entry, 'config', mock_env)
  268. assert value == expected
  269. def test_escaped_interpolation(defaults_interpolator):
  270. assert defaults_interpolator('$${foo}') == '${foo}'
  271. def test_invalid_interpolation(defaults_interpolator):
  272. with pytest.raises(InvalidInterpolation):
  273. defaults_interpolator('${')
  274. with pytest.raises(InvalidInterpolation):
  275. defaults_interpolator('$}')
  276. with pytest.raises(InvalidInterpolation):
  277. defaults_interpolator('${}')
  278. with pytest.raises(InvalidInterpolation):
  279. defaults_interpolator('${ }')
  280. with pytest.raises(InvalidInterpolation):
  281. defaults_interpolator('${ foo}')
  282. with pytest.raises(InvalidInterpolation):
  283. defaults_interpolator('${foo }')
  284. with pytest.raises(InvalidInterpolation):
  285. defaults_interpolator('${foo!}')
  286. def test_interpolate_missing_no_default(defaults_interpolator):
  287. assert defaults_interpolator("This ${missing} var") == "This var"
  288. assert defaults_interpolator("This ${BAR} var") == "This var"
  289. def test_interpolate_with_value(defaults_interpolator):
  290. assert defaults_interpolator("This $FOO var") == "This first var"
  291. assert defaults_interpolator("This ${FOO} var") == "This first var"
  292. def test_interpolate_missing_with_default(defaults_interpolator):
  293. assert defaults_interpolator("ok ${missing:-def}") == "ok def"
  294. assert defaults_interpolator("ok ${missing-def}") == "ok def"
  295. assert defaults_interpolator("ok ${BAR:-/non:-alphanumeric}") == "ok /non:-alphanumeric"
  296. def test_interpolate_with_empty_and_default_value(defaults_interpolator):
  297. assert defaults_interpolator("ok ${BAR:-def}") == "ok def"
  298. assert defaults_interpolator("ok ${BAR-def}") == "ok "