krates 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +212 -0
  3. data/LOGO +10 -0
  4. data/VERSION +1 -0
  5. data/bin/krates +23 -0
  6. data/lib/kontena/autoload_core.rb +19 -0
  7. data/lib/kontena/callback.rb +60 -0
  8. data/lib/kontena/callbacks/.gitkeep +0 -0
  9. data/lib/kontena/callbacks/auth/01_list_and_select_grid_after_master_auth.rb +26 -0
  10. data/lib/kontena/callbacks/master/01_clear_current_master_after_terminate.rb +19 -0
  11. data/lib/kontena/callbacks/master/deploy/01_show_logo_before_deploy.rb +14 -0
  12. data/lib/kontena/callbacks/master/deploy/04_default_master_version.rb +18 -0
  13. data/lib/kontena/callbacks/master/deploy/05_before_deploy_configuration_wizard.rb +105 -0
  14. data/lib/kontena/callbacks/master/deploy/40_install_ssl_certificate_after_deploy.rb +32 -0
  15. data/lib/kontena/callbacks/master/deploy/50_authenticate_after_deploy.rb +66 -0
  16. data/lib/kontena/callbacks/master/deploy/55_create_initial_grid_after_deploy.rb +21 -0
  17. data/lib/kontena/callbacks/master/deploy/56_set_server_provider_after_deploy.rb +24 -0
  18. data/lib/kontena/callbacks/master/deploy/60_configure_auth_provider_after_deploy.rb +31 -0
  19. data/lib/kontena/callbacks/master/deploy/70_invite_self_after_deploy.rb +95 -0
  20. data/lib/kontena/callbacks/master/deploy/90_proptip_after_deploy.rb +33 -0
  21. data/lib/kontena/cli/browser_launcher.rb +61 -0
  22. data/lib/kontena/cli/bytes_helper.rb +40 -0
  23. data/lib/kontena/cli/certificate/authorize_command.rb +107 -0
  24. data/lib/kontena/cli/certificate/common.rb +16 -0
  25. data/lib/kontena/cli/certificate/domain_authorization/list_command.rb +24 -0
  26. data/lib/kontena/cli/certificate/domain_authorization/remove_authorization_command.rb +25 -0
  27. data/lib/kontena/cli/certificate/domain_authorize_command.rb +7 -0
  28. data/lib/kontena/cli/certificate/export_command.rb +28 -0
  29. data/lib/kontena/cli/certificate/get_command.rb +33 -0
  30. data/lib/kontena/cli/certificate/import_command.rb +61 -0
  31. data/lib/kontena/cli/certificate/list_command.rb +75 -0
  32. data/lib/kontena/cli/certificate/register_command.rb +30 -0
  33. data/lib/kontena/cli/certificate/remove_command.rb +23 -0
  34. data/lib/kontena/cli/certificate/request_command.rb +20 -0
  35. data/lib/kontena/cli/certificate/show_command.rb +22 -0
  36. data/lib/kontena/cli/certificate_command.rb +18 -0
  37. data/lib/kontena/cli/cloud/login_command.rb +186 -0
  38. data/lib/kontena/cli/cloud/logout_command.rb +14 -0
  39. data/lib/kontena/cli/cloud/master/add_command.rb +156 -0
  40. data/lib/kontena/cli/cloud/master/list_command.rb +35 -0
  41. data/lib/kontena/cli/cloud/master/remove_command.rb +68 -0
  42. data/lib/kontena/cli/cloud/master/show_command.rb +21 -0
  43. data/lib/kontena/cli/cloud/master/update_command.rb +52 -0
  44. data/lib/kontena/cli/cloud/master_command.rb +14 -0
  45. data/lib/kontena/cli/cloud_command.rb +13 -0
  46. data/lib/kontena/cli/common.rb +360 -0
  47. data/lib/kontena/cli/config.rb +662 -0
  48. data/lib/kontena/cli/container_command.rb +10 -0
  49. data/lib/kontena/cli/containers/exec_command.rb +31 -0
  50. data/lib/kontena/cli/containers/inspect_command.rb +16 -0
  51. data/lib/kontena/cli/containers/list_command.rb +51 -0
  52. data/lib/kontena/cli/containers/logs_command.rb +19 -0
  53. data/lib/kontena/cli/etcd/common.rb +8 -0
  54. data/lib/kontena/cli/etcd/get_command.rb +26 -0
  55. data/lib/kontena/cli/etcd/health_command.rb +53 -0
  56. data/lib/kontena/cli/etcd/list_command.rb +36 -0
  57. data/lib/kontena/cli/etcd/mkdir_command.rb +23 -0
  58. data/lib/kontena/cli/etcd/remove_command.rb +29 -0
  59. data/lib/kontena/cli/etcd/set_command.rb +24 -0
  60. data/lib/kontena/cli/etcd_command.rb +12 -0
  61. data/lib/kontena/cli/external_registries/add_command.rb +25 -0
  62. data/lib/kontena/cli/external_registries/list_command.rb +23 -0
  63. data/lib/kontena/cli/external_registries/remove_command.rb +17 -0
  64. data/lib/kontena/cli/external_registry_command.rb +9 -0
  65. data/lib/kontena/cli/grid_command.rb +21 -0
  66. data/lib/kontena/cli/grid_options.rb +12 -0
  67. data/lib/kontena/cli/grids/audit_log_command.rb +22 -0
  68. data/lib/kontena/cli/grids/cloud_config_command.rb +53 -0
  69. data/lib/kontena/cli/grids/common.rb +182 -0
  70. data/lib/kontena/cli/grids/create_command.rb +48 -0
  71. data/lib/kontena/cli/grids/current_command.rb +25 -0
  72. data/lib/kontena/cli/grids/env_command.rb +32 -0
  73. data/lib/kontena/cli/grids/events_command.rb +50 -0
  74. data/lib/kontena/cli/grids/health_command.rb +69 -0
  75. data/lib/kontena/cli/grids/list_command.rb +59 -0
  76. data/lib/kontena/cli/grids/logs_command.rb +35 -0
  77. data/lib/kontena/cli/grids/remove_command.rb +31 -0
  78. data/lib/kontena/cli/grids/show_command.rb +25 -0
  79. data/lib/kontena/cli/grids/trusted_subnet_command.rb +10 -0
  80. data/lib/kontena/cli/grids/trusted_subnets/add_command.rb +18 -0
  81. data/lib/kontena/cli/grids/trusted_subnets/list_command.rb +18 -0
  82. data/lib/kontena/cli/grids/trusted_subnets/remove_command.rb +26 -0
  83. data/lib/kontena/cli/grids/update_command.rb +35 -0
  84. data/lib/kontena/cli/grids/use_command.rb +26 -0
  85. data/lib/kontena/cli/grids/user_command.rb +9 -0
  86. data/lib/kontena/cli/grids/users/add_command.rb +18 -0
  87. data/lib/kontena/cli/grids/users/list_command.rb +20 -0
  88. data/lib/kontena/cli/grids/users/remove_command.rb +20 -0
  89. data/lib/kontena/cli/helpers/exec_helper.rb +209 -0
  90. data/lib/kontena/cli/helpers/health_helper.rb +65 -0
  91. data/lib/kontena/cli/helpers/log_helper.rb +113 -0
  92. data/lib/kontena/cli/helpers/time_helper.rb +29 -0
  93. data/lib/kontena/cli/localhost_web_server.rb +113 -0
  94. data/lib/kontena/cli/log_formatters/compact.rb +65 -0
  95. data/lib/kontena/cli/log_formatters/strip_color.rb +13 -0
  96. data/lib/kontena/cli/logout_command.rb +10 -0
  97. data/lib/kontena/cli/master/audit_log_command.rb +19 -0
  98. data/lib/kontena/cli/master/config/export_command.rb +46 -0
  99. data/lib/kontena/cli/master/config/get_command.rb +26 -0
  100. data/lib/kontena/cli/master/config/import_command.rb +67 -0
  101. data/lib/kontena/cli/master/config/set_command.rb +19 -0
  102. data/lib/kontena/cli/master/config/unset_command.rb +20 -0
  103. data/lib/kontena/cli/master/config_command.rb +17 -0
  104. data/lib/kontena/cli/master/create_command.rb +74 -0
  105. data/lib/kontena/cli/master/current_command.rb +25 -0
  106. data/lib/kontena/cli/master/init_cloud_command.rb +45 -0
  107. data/lib/kontena/cli/master/join_command.rb +22 -0
  108. data/lib/kontena/cli/master/list_command.rb +24 -0
  109. data/lib/kontena/cli/master/login_command.rb +331 -0
  110. data/lib/kontena/cli/master/logout_command.rb +25 -0
  111. data/lib/kontena/cli/master/remove_command.rb +55 -0
  112. data/lib/kontena/cli/master/ssh_command.rb +72 -0
  113. data/lib/kontena/cli/master/token/common.rb +29 -0
  114. data/lib/kontena/cli/master/token/create_command.rb +50 -0
  115. data/lib/kontena/cli/master/token/current_command.rb +45 -0
  116. data/lib/kontena/cli/master/token/list_command.rb +39 -0
  117. data/lib/kontena/cli/master/token/remove_command.rb +19 -0
  118. data/lib/kontena/cli/master/token/show_command.rb +34 -0
  119. data/lib/kontena/cli/master/token_command.rb +13 -0
  120. data/lib/kontena/cli/master/use_command.rb +31 -0
  121. data/lib/kontena/cli/master/user/invite_command.rb +51 -0
  122. data/lib/kontena/cli/master/user/list_command.rb +29 -0
  123. data/lib/kontena/cli/master/user/remove_command.rb +24 -0
  124. data/lib/kontena/cli/master/user/role/add_command.rb +29 -0
  125. data/lib/kontena/cli/master/user/role/remove_command.rb +27 -0
  126. data/lib/kontena/cli/master/user/role_command.rb +6 -0
  127. data/lib/kontena/cli/master/user_command.rb +9 -0
  128. data/lib/kontena/cli/master_command.rb +21 -0
  129. data/lib/kontena/cli/node_command.rb +17 -0
  130. data/lib/kontena/cli/nodes/create_command.rb +25 -0
  131. data/lib/kontena/cli/nodes/env_command.rb +37 -0
  132. data/lib/kontena/cli/nodes/health_command.rb +47 -0
  133. data/lib/kontena/cli/nodes/label_command.rb +10 -0
  134. data/lib/kontena/cli/nodes/labels/add_command.rb +18 -0
  135. data/lib/kontena/cli/nodes/labels/list_command.rb +19 -0
  136. data/lib/kontena/cli/nodes/labels/remove_command.rb +32 -0
  137. data/lib/kontena/cli/nodes/list_command.rb +97 -0
  138. data/lib/kontena/cli/nodes/remove_command.rb +34 -0
  139. data/lib/kontena/cli/nodes/reset_token_command.rb +34 -0
  140. data/lib/kontena/cli/nodes/show_command.rb +56 -0
  141. data/lib/kontena/cli/nodes/ssh_command.rb +63 -0
  142. data/lib/kontena/cli/nodes/update_command.rb +31 -0
  143. data/lib/kontena/cli/plugin_command.rb +12 -0
  144. data/lib/kontena/cli/plugins/common.rb +8 -0
  145. data/lib/kontena/cli/plugins/install_command.rb +42 -0
  146. data/lib/kontena/cli/plugins/list_command.rb +31 -0
  147. data/lib/kontena/cli/plugins/search_command.rb +25 -0
  148. data/lib/kontena/cli/plugins/show_command.rb +17 -0
  149. data/lib/kontena/cli/plugins/uninstall_command.rb +31 -0
  150. data/lib/kontena/cli/plugins/upgrade_command.rb +60 -0
  151. data/lib/kontena/cli/registry/create_command.rb +151 -0
  152. data/lib/kontena/cli/registry/remove_command.rb +21 -0
  153. data/lib/kontena/cli/registry_command.rb +8 -0
  154. data/lib/kontena/cli/service_command.rb +28 -0
  155. data/lib/kontena/cli/services/container_command.rb +8 -0
  156. data/lib/kontena/cli/services/containers_command.rb +39 -0
  157. data/lib/kontena/cli/services/create_command.rb +105 -0
  158. data/lib/kontena/cli/services/deploy_command.rb +25 -0
  159. data/lib/kontena/cli/services/env_command.rb +9 -0
  160. data/lib/kontena/cli/services/envs/add_command.rb +21 -0
  161. data/lib/kontena/cli/services/envs/list_command.rb +22 -0
  162. data/lib/kontena/cli/services/envs/remove_command.rb +22 -0
  163. data/lib/kontena/cli/services/events_command.rb +36 -0
  164. data/lib/kontena/cli/services/exec_command.rb +107 -0
  165. data/lib/kontena/cli/services/link_command.rb +35 -0
  166. data/lib/kontena/cli/services/list_command.rb +66 -0
  167. data/lib/kontena/cli/services/logs_command.rb +33 -0
  168. data/lib/kontena/cli/services/monitor_command.rb +58 -0
  169. data/lib/kontena/cli/services/remove_command.rb +60 -0
  170. data/lib/kontena/cli/services/restart_command.rb +19 -0
  171. data/lib/kontena/cli/services/scale_command.rb +21 -0
  172. data/lib/kontena/cli/services/secret_command.rb +8 -0
  173. data/lib/kontena/cli/services/secrets/link_command.rb +26 -0
  174. data/lib/kontena/cli/services/secrets/unlink_command.rb +28 -0
  175. data/lib/kontena/cli/services/services_helper.rb +579 -0
  176. data/lib/kontena/cli/services/show_command.rb +26 -0
  177. data/lib/kontena/cli/services/start_command.rb +21 -0
  178. data/lib/kontena/cli/services/stats_command.rb +87 -0
  179. data/lib/kontena/cli/services/stop_command.rb +21 -0
  180. data/lib/kontena/cli/services/unlink_command.rb +30 -0
  181. data/lib/kontena/cli/services/update_command.rb +94 -0
  182. data/lib/kontena/cli/spinner.rb +205 -0
  183. data/lib/kontena/cli/stack_command.rb +21 -0
  184. data/lib/kontena/cli/stacks/build_command.rb +125 -0
  185. data/lib/kontena/cli/stacks/common.rb +209 -0
  186. data/lib/kontena/cli/stacks/deploy_command.rb +37 -0
  187. data/lib/kontena/cli/stacks/events_command.rb +33 -0
  188. data/lib/kontena/cli/stacks/inspect_command.rb +17 -0
  189. data/lib/kontena/cli/stacks/install_command.rb +95 -0
  190. data/lib/kontena/cli/stacks/label_command.rb +10 -0
  191. data/lib/kontena/cli/stacks/labels/add_command.rb +21 -0
  192. data/lib/kontena/cli/stacks/labels/common.rb +19 -0
  193. data/lib/kontena/cli/stacks/labels/list_command.rb +21 -0
  194. data/lib/kontena/cli/stacks/labels/remove_command.rb +21 -0
  195. data/lib/kontena/cli/stacks/list_command.rb +154 -0
  196. data/lib/kontena/cli/stacks/logs_command.rb +35 -0
  197. data/lib/kontena/cli/stacks/monitor_command.rb +93 -0
  198. data/lib/kontena/cli/stacks/registry/create_command.rb +24 -0
  199. data/lib/kontena/cli/stacks/registry/make_private_command.rb +24 -0
  200. data/lib/kontena/cli/stacks/registry/make_public_command.rb +24 -0
  201. data/lib/kontena/cli/stacks/registry/pull_command.rb +28 -0
  202. data/lib/kontena/cli/stacks/registry/push_command.rb +40 -0
  203. data/lib/kontena/cli/stacks/registry/remove_command.rb +30 -0
  204. data/lib/kontena/cli/stacks/registry/search_command.rb +42 -0
  205. data/lib/kontena/cli/stacks/registry/show_command.rb +65 -0
  206. data/lib/kontena/cli/stacks/registry_command.rb +12 -0
  207. data/lib/kontena/cli/stacks/remove_command.rb +80 -0
  208. data/lib/kontena/cli/stacks/restart_command.rb +24 -0
  209. data/lib/kontena/cli/stacks/service_generator.rb +131 -0
  210. data/lib/kontena/cli/stacks/service_generator_v2.rb +27 -0
  211. data/lib/kontena/cli/stacks/show_command.rb +168 -0
  212. data/lib/kontena/cli/stacks/stack_name.rb +71 -0
  213. data/lib/kontena/cli/stacks/stacks_helper.rb +83 -0
  214. data/lib/kontena/cli/stacks/stop_command.rb +24 -0
  215. data/lib/kontena/cli/stacks/upgrade_command.rb +264 -0
  216. data/lib/kontena/cli/stacks/validate_command.rb +75 -0
  217. data/lib/kontena/cli/stacks/yaml/custom_validators/affinities_validator.rb +19 -0
  218. data/lib/kontena/cli/stacks/yaml/custom_validators/build_validator.rb +22 -0
  219. data/lib/kontena/cli/stacks/yaml/custom_validators/certificates_validator.rb +22 -0
  220. data/lib/kontena/cli/stacks/yaml/custom_validators/extends_validator.rb +22 -0
  221. data/lib/kontena/cli/stacks/yaml/custom_validators/hooks_validator.rb +102 -0
  222. data/lib/kontena/cli/stacks/yaml/custom_validators/secrets_validator.rb +22 -0
  223. data/lib/kontena/cli/stacks/yaml/opto/certificates_resolver.rb +37 -0
  224. data/lib/kontena/cli/stacks/yaml/opto/prompt_resolver.rb +78 -0
  225. data/lib/kontena/cli/stacks/yaml/opto/service_instances_resolver.rb +25 -0
  226. data/lib/kontena/cli/stacks/yaml/opto/service_link_resolver.rb +80 -0
  227. data/lib/kontena/cli/stacks/yaml/opto/vault_cert_prompt_resolver.rb +39 -0
  228. data/lib/kontena/cli/stacks/yaml/opto/vault_resolver.rb +13 -0
  229. data/lib/kontena/cli/stacks/yaml/opto/vault_setter.rb +12 -0
  230. data/lib/kontena/cli/stacks/yaml/opto.rb +16 -0
  231. data/lib/kontena/cli/stacks/yaml/reader.rb +525 -0
  232. data/lib/kontena/cli/stacks/yaml/service_extender.rb +65 -0
  233. data/lib/kontena/cli/stacks/yaml/stack_file_loader/file_loader.rb +41 -0
  234. data/lib/kontena/cli/stacks/yaml/stack_file_loader/registry_loader.rb +24 -0
  235. data/lib/kontena/cli/stacks/yaml/stack_file_loader/uri_loader.rb +23 -0
  236. data/lib/kontena/cli/stacks/yaml/stack_file_loader.rb +152 -0
  237. data/lib/kontena/cli/stacks/yaml/validations.rb +119 -0
  238. data/lib/kontena/cli/stacks/yaml/validator_v3.rb +164 -0
  239. data/lib/kontena/cli/subcommand_loader.rb +83 -0
  240. data/lib/kontena/cli/table_generator.rb +128 -0
  241. data/lib/kontena/cli/vault/export_command.rb +24 -0
  242. data/lib/kontena/cli/vault/import_command.rb +75 -0
  243. data/lib/kontena/cli/vault/list_command.rb +37 -0
  244. data/lib/kontena/cli/vault/read_command.rb +27 -0
  245. data/lib/kontena/cli/vault/remove_command.rb +23 -0
  246. data/lib/kontena/cli/vault/update_command.rb +24 -0
  247. data/lib/kontena/cli/vault/write_command.rb +23 -0
  248. data/lib/kontena/cli/vault_command.rb +13 -0
  249. data/lib/kontena/cli/version.rb +10 -0
  250. data/lib/kontena/cli/version_command.rb +20 -0
  251. data/lib/kontena/cli/volume_command.rb +9 -0
  252. data/lib/kontena/cli/volumes/create_command.rb +42 -0
  253. data/lib/kontena/cli/volumes/list_command.rb +29 -0
  254. data/lib/kontena/cli/volumes/remove_command.rb +29 -0
  255. data/lib/kontena/cli/volumes/show_command.rb +38 -0
  256. data/lib/kontena/cli/vpn/config_command.rb +27 -0
  257. data/lib/kontena/cli/vpn/create_command.rb +99 -0
  258. data/lib/kontena/cli/vpn/remove_command.rb +22 -0
  259. data/lib/kontena/cli/vpn_command.rb +9 -0
  260. data/lib/kontena/cli/whoami_command.rb +38 -0
  261. data/lib/kontena/client.rb +574 -0
  262. data/lib/kontena/command.rb +251 -0
  263. data/lib/kontena/debug_instrumentor.rb +80 -0
  264. data/lib/kontena/errors.rb +50 -0
  265. data/lib/kontena/light_prompt.rb +103 -0
  266. data/lib/kontena/machine/cert_helper.rb +43 -0
  267. data/lib/kontena/machine/cloud_config/cloudinit.yml +82 -0
  268. data/lib/kontena/machine/cloud_config/node_generator.rb +28 -0
  269. data/lib/kontena/machine/common.rb +17 -0
  270. data/lib/kontena/machine/random_name.rb +42 -0
  271. data/lib/kontena/main_command.rb +66 -0
  272. data/lib/kontena/plugin_manager/cleaner.rb +33 -0
  273. data/lib/kontena/plugin_manager/common.rb +89 -0
  274. data/lib/kontena/plugin_manager/installer.rb +78 -0
  275. data/lib/kontena/plugin_manager/loader.rb +93 -0
  276. data/lib/kontena/plugin_manager/rubygems_client.rb +59 -0
  277. data/lib/kontena/plugin_manager/uninstaller.rb +34 -0
  278. data/lib/kontena/plugin_manager.rb +26 -0
  279. data/lib/kontena/presets/github_auth_provider.yml +11 -0
  280. data/lib/kontena/presets/kontena_auth_provider.yml +11 -0
  281. data/lib/kontena/scripts/completer +9 -0
  282. data/lib/kontena/scripts/completer.rb +334 -0
  283. data/lib/kontena/scripts/init +18 -0
  284. data/lib/kontena/scripts/kontena.zsh +11 -0
  285. data/lib/kontena/scripts/krates.bash +8 -0
  286. data/lib/kontena/stacks/change_resolver.rb +118 -0
  287. data/lib/kontena/stacks/stack_data.rb +58 -0
  288. data/lib/kontena/stacks/stack_data_set.rb +51 -0
  289. data/lib/kontena/stacks_cache.rb +110 -0
  290. data/lib/kontena/stacks_client.rb +177 -0
  291. data/lib/kontena/util.rb +116 -0
  292. data/lib/kontena_cli.rb +190 -0
  293. metadata +518 -0
@@ -0,0 +1,525 @@
1
+ require_relative 'stack_file_loader'
2
+ require_relative 'service_extender'
3
+ require_relative '../service_generator_v2'
4
+ require_relative 'validator_v3'
5
+ require 'opto'
6
+ require 'liquid'
7
+ require_relative 'opto'
8
+
9
+ module Kontena::Cli::Stacks
10
+ module YAML
11
+ module Opto
12
+ module Resolvers; end
13
+ module Setters; end
14
+ end
15
+
16
+ class LiquidNull
17
+ # Workaround for nil-valued variables in Liquid templates:
18
+ # https://github.com/Shopify/liquid/issues/749
19
+ # This is something that we can pass in to `Liquid::Template.render` that gets evaluated as nil.
20
+ # If we pass in a nil value directly, then Liquid ignores it and considers the variable to be undefined.
21
+ def to_liquid
22
+ nil
23
+ end
24
+ end
25
+
26
+ class Reader
27
+ # The kontena Stack YAML reader
28
+
29
+ include Kontena::Util
30
+ include Kontena::Cli::Common
31
+
32
+ attr_reader :file, :loader, :errors, :notifications
33
+
34
+ # @param stack_origin [String] a filename, pointer to registry or an URL
35
+ # @return [Reader]
36
+ def initialize(file)
37
+ if file.kind_of?(StackFileLoader)
38
+ @file = file.source
39
+ @loader = file
40
+ else
41
+ @file = file
42
+ @loader = StackFileLoader.for(file)
43
+ end
44
+
45
+ @errors = []
46
+ @notifications = []
47
+ end
48
+
49
+ # @param without_defaults [TrueClass,FalseClass] strip the GRID, STACK, etc from response
50
+ # @param without_vault [TrueClass,FalseClass] strip out any values that are going to or coming from VAULT
51
+ # @return [Hash] a hash of key value pairs representing the values of stack variables
52
+ def variable_values(without_defaults: false, without_vault: false, with_errors: false)
53
+ result = variables.to_h(values_only: true, with_errors: with_errors)
54
+ if without_defaults
55
+ result.delete_if { |k, _| default_envs.key?(k.to_s) || k.to_s == 'PARENT_STACK' }
56
+ end
57
+ if without_vault
58
+ result.delete_if { |k, _| variables.option(k).from.include?('vault') || variables.option(k).to.include?('vault') }
59
+ end
60
+ result
61
+ end
62
+
63
+ # Values that are set always when parsing stacks
64
+ # @return [Hash] a hash of key value pairs
65
+ def default_envs
66
+ {
67
+ 'GRID' => env['GRID'],
68
+ 'STACK' => env['STACK'],
69
+ 'PLATFORM' => env['PLATFORM'] || env['GRID']
70
+ }
71
+ end
72
+
73
+ # Only uses the values from #default_envs to provide a hash from minimally interpolated
74
+ # YAML file. Useful for accessing some parts of the YAML without asking any questions.
75
+ #
76
+ # @return [Hash] minimally interpolated YAMl from the stack file.
77
+ def internals_interpolated_yaml
78
+ @internals_interpolated_yaml ||= ::YAML.safe_load(
79
+ replace_dollar_dollars(
80
+ interpolate(
81
+ raw_content,
82
+ use_opto: false,
83
+ substitutions: default_envs,
84
+ warnings: false
85
+ )
86
+ ), [], [], true, file
87
+ )
88
+ rescue Psych::SyntaxError => ex
89
+ raise ex, "Error while parsing #{file} : #{ex.message}"
90
+ end
91
+
92
+ # Uses variable interpolation, prompts as needed, liquid interpolation
93
+ #
94
+ # @return [Hash] the most commplete stack parsing outcome
95
+ def fully_interpolated_yaml
96
+ return @fully_interpolated_yaml if @fully_interpolated_yaml
97
+ @fully_interpolated_yaml = ::YAML.safe_load(
98
+ replace_dollar_dollars(
99
+ interpolate(
100
+ interpolate_liquid(
101
+ raw_content,
102
+ variable_values
103
+ ),
104
+ use_opto: true,
105
+ raise_on_unknown: true
106
+ )
107
+ ), [], [], true, file
108
+ )
109
+ rescue Psych::SyntaxError => ex
110
+ raise ex, "Error while parsing #{file} : #{ex.message}"
111
+ end
112
+
113
+ # The YAML file raw content
114
+ def raw_content
115
+ loader.content
116
+ end
117
+
118
+ # @return [Hash] with zero interpolation/processing. Will mostly fail
119
+ def raw_yaml
120
+ loader.yaml
121
+ end
122
+
123
+ # Creates an opto option definition compatible hash from the #default_envs hash
124
+ # @return [Hash]
125
+ def default_envs_to_options
126
+ default_envs.each_with_object({}) { |env, obj| obj[env[0]] = { type: :string, value: env[1] } }
127
+ end
128
+
129
+ # Accessor to the Opto variable handler
130
+ # @return [Opto::Group]
131
+ def variables
132
+ @variables ||= ::Opto::Group.new(
133
+ internals_interpolated_yaml.fetch('variables', {}).merge(default_envs_to_options),
134
+ defaults: { from: :env }
135
+ )
136
+ end
137
+
138
+ # Accepts a hash of variable_name => variable_value pairs and sets the values as variable default values
139
+ # Used when previous answers are read from master and passed as default values for upgrade.
140
+ # @param defaults [Hash] { 'variable_name' => 'variable_value' }
141
+ def set_variable_defaults(defaults)
142
+ defaults.each do |key, val|
143
+ var = variables.option(key.to_s)
144
+ var.default = val if var
145
+ end
146
+ end
147
+
148
+ # Set values from a hash to values of the variables.
149
+ # Used when variable values are read from a file or command line parameters or dependency variable injection
150
+ # @param [Hash] a hash of variable_name => variable_value pairs
151
+ def set_variable_values(values)
152
+ values.each do |key, val|
153
+ var = variables.option(key.to_s)
154
+ var.set(val) if var
155
+ end
156
+ end
157
+
158
+ # Creates a set of variables using the 'depends' section. The variable name is the name of the dependency
159
+ # and the variable value is the generated child stack name. For example,.have something like:
160
+ # depends:
161
+ # redis:
162
+ # stack: foo/redis
163
+ # you will get a new variable called "redis" and its value will be "this-stack-name-redis".
164
+ # This variable can be used to interpolate for example a hostname to some environment variable:
165
+ # environment:
166
+ # - "REDIS_HOST=redis.${REDIS}"
167
+ def create_dependency_variables(dependencies, name)
168
+ return if dependencies.nil?
169
+ dependencies.each do |options|
170
+ variables.build_option(name: options['name'].to_s, type: :string, value: "#{name}-#{options['name']}")
171
+ create_dependency_variables(options['depends'], "#{name}.#{options['name']}")
172
+ end
173
+ end
174
+
175
+ # If this stack is a part of a dependency chain and has a parent, the variable $PARENT_STACK will
176
+ # interpolate to the name of the parent stack.
177
+ def create_parent_variable(parent_name)
178
+ variables.build_option(name: 'PARENT_STACK', type: :string, value: parent_name)
179
+ end
180
+
181
+ # @return [Boolean] did this stack come from a local file?
182
+ def from_file?
183
+ loader.origin == 'file'
184
+ end
185
+
186
+ # @param [String] service_name (set when using extends)
187
+ # @param name [String] override stackname (default is to parse it from the YAML, but if you set it through -n it needs to be overriden)
188
+ # @param parent_name [String] parent stack name
189
+ # @param skip_validation [Boolean] skip running validations
190
+ # @param values [Hash] force-set variable values using variable_name => variable_value key pairs
191
+ # @param defaults [Hash] set variable defaults from variable_name => variable_value key pairs
192
+ # @return [Hash]
193
+ def execute(service_name = nil, name: loader.stack_name.stack, parent_name: nil, skip_validation: false, values: nil, defaults: nil)
194
+ set_variable_defaults(defaults) if defaults
195
+ set_variable_values(values) if values
196
+ create_dependency_variables(dependencies, name)
197
+ create_parent_variable(parent_name) if parent_name
198
+
199
+ variables.run
200
+
201
+ if !skip_validation && !variables.valid?
202
+ stringify_keys(variables.errors).each do |k,v|
203
+ errors << { 'variables' => { k => v } }
204
+ end
205
+ end
206
+
207
+ validate unless skip_validation
208
+
209
+ result = {}
210
+ Dir.chdir(from_file? ? File.dirname(File.expand_path(file)) : Dir.pwd) do
211
+ result['stack'] = raw_yaml['stack']
212
+ result['version'] = loader.stack_name.version || '0.0.1'
213
+ result['name'] = name
214
+ result['labels'] = fully_interpolated_yaml['labels'] || []
215
+ result['registry'] = loader.registry
216
+ result['expose'] = fully_interpolated_yaml['expose']
217
+ result['services'] = errors.empty? ? parse_services(service_name) : {}
218
+ result['volumes'] = errors.empty? ? parse_volumes : {}
219
+ result['dependencies'] = dependencies
220
+ result['source'] = raw_content
221
+ result['variables'] = variable_values(without_defaults: true, without_vault: true)
222
+ result['metadata'] = raw_yaml['meta'] || {}
223
+ end
224
+
225
+ if parent_name
226
+ result['parent'] = { 'name' => parent_name }
227
+ else
228
+ result['parent'] = nil
229
+ end
230
+ if service_name.nil?
231
+ result['services'].each do |service|
232
+ errors << { 'services' => { service['name'] => { 'image' => "image is missing" } } } if service['image'].to_s.empty?
233
+ end
234
+ errors << { file => { 'stack' => 'Required field missing' } } if result['stack'].nil?
235
+ end
236
+ result
237
+ end
238
+
239
+ # Returns an array of hashes containing the dependency tree starting from this file
240
+ # @return [Array<Hash>]]
241
+ def dependencies
242
+ @dependencies ||= loader.dependencies
243
+ end
244
+
245
+ # Interpolate any Liquid templating in the YAML content
246
+ # @param content [String] file content
247
+ # @param vars [Hash] key-value pairs
248
+ # @return [String]
249
+ # @raise [Liquid::Error]
250
+ def interpolate_liquid(content, vars)
251
+ Liquid::Template.error_mode = :strict
252
+ template = Liquid::Template.parse(content)
253
+
254
+ # Wrap nil values in LiquidNull to not have Liquid consider them as undefined
255
+ vars = vars.map {|key, value| [key, value.nil? ? LiquidNull.new : value]}.to_h
256
+
257
+ template.render!(vars, strict_variables: true, strict_filters: true)
258
+ end
259
+
260
+ # @return [Array<Hash>] array of validation errors
261
+ def validate
262
+ result = validator.validate(fully_interpolated_yaml)
263
+ store_failures(result)
264
+ result
265
+ end
266
+
267
+ # @return [Kontena::Cli::Stacks::YAML::ValidatorV3]
268
+ def validator
269
+ @validator ||= YAML::ValidatorV3.new
270
+ end
271
+
272
+ def parse_volumes
273
+ volumes.each do |name, config|
274
+ if process_hash?(config)
275
+ volumes[name].delete('only_if')
276
+ volumes[name].delete('skip_if')
277
+ volumes[name] = process_volume(name, config)
278
+ else
279
+ volumes.delete(name)
280
+ end
281
+ end
282
+ volumes.map { |name, vol| vol.merge('name' => name) }
283
+ end
284
+
285
+ ##
286
+ # @param [String] service_name - optional service to parse
287
+ # @return [Hash]
288
+ def parse_services(service_name = nil)
289
+ services = self.services.dup # do not modify the fully_interpolated_yaml['services'] hash in-place
290
+ if service_name.nil?
291
+ services.each do |name, config|
292
+ services[name] = process_config(config, name)
293
+ if process_hash?(config)
294
+ services[name].delete('only_if')
295
+ services[name].delete('skip_if')
296
+ else
297
+ services.delete(name)
298
+ end
299
+ end
300
+ services.map { |name, svc| svc.merge('name' => name) }
301
+ else
302
+ raise ("Service '#{service_name}' not found in #{file}") unless services.key?(service_name)
303
+ process_config(services[service_name], service_name)
304
+ end
305
+ end
306
+
307
+ # If the supplied hash contains skip_if/only_if conditionals, process that conditional and return true/false
308
+ #
309
+ # @param [Hash]
310
+ # @return [Boolean]
311
+ def process_hash?(hash)
312
+ return true unless hash['skip_if'] || hash['only_if']
313
+
314
+ skip_lambdas = normalize_ifs(hash['skip_if'])
315
+ only_lambdas = normalize_ifs(hash['only_if'])
316
+
317
+ if skip_lambdas
318
+ return false if skip_lambdas.any? { |s| s.call }
319
+ end
320
+
321
+ if only_lambdas
322
+ return false unless only_lambdas.all? { |s| s.call }
323
+ end
324
+
325
+ true
326
+ end
327
+
328
+ # @param [Hash] service_config
329
+ def process_config(service_config, name=nil)
330
+ normalize_env_vars(service_config)
331
+ merge_env_vars(service_config)
332
+ expand_build_context(service_config)
333
+ normalize_build_args(service_config)
334
+ if service_config.key?('extends')
335
+ service_config = extend_config(service_config)
336
+ service_config.delete('extends')
337
+ end
338
+ if name
339
+ ServiceGeneratorV2.new(service_config).generate.merge('name' => name)
340
+ else
341
+ ServiceGeneratorV2.new(service_config).generate
342
+ end
343
+ end
344
+
345
+ def process_volume(name, volume_config)
346
+ return [] if volume_config.nil? || volume_config.empty?
347
+ if volume_config['external'].is_a?(TrueClass)
348
+ volume_config['external'] = name
349
+ elsif volume_config['external']['name']
350
+ volume_config['external'] = volume_config['external']['name']
351
+ end
352
+ volume_config['name'] = name
353
+ volume_config
354
+ end
355
+
356
+ def volumes
357
+ @volumes ||= fully_interpolated_yaml.fetch('volumes', {})
358
+ end
359
+
360
+ # @return [Hash] - services from YAML file
361
+ def services
362
+ @services ||= fully_interpolated_yaml.fetch('services', {})
363
+ end
364
+
365
+ def from_external_stack(name, service_name)
366
+ external_reader = StackFileLoader.for(name, loader).reader
367
+ variables.to_a(with_value: true).each do |var|
368
+ external_reader.variables.build_option(var)
369
+ end
370
+ outcome = external_reader.execute(service_name)
371
+ errors.concat external_reader.errors unless external_reader.errors.empty? || errors.include?(external_reader.errors)
372
+ notifications.concat external_reader.notifications unless external_reader.notifications.empty? || notifications.include?(external_reader.notifications)
373
+ outcome['services']
374
+ end
375
+
376
+ private
377
+
378
+ ##
379
+ # @param [String] content - content of YAML file
380
+ def interpolate(content, use_opto: true, substitutions: {}, raise_on_unknown: false, warnings: true)
381
+ content.split(/[\r\n]/).map.with_index do |row, line_num|
382
+ # skip lines that opto may be interpolating
383
+ if row.strip.start_with?('interpolate:') || row.strip.start_with?('evaluate:')
384
+ row
385
+ else
386
+ row.gsub(/(?<!\$)\$(?!\$)\{?\w+\}?/) do |v| # searches $VAR and ${VAR} and not $$VAR
387
+ var = v.tr('${}', '')
388
+
389
+ if use_opto
390
+ opt = variables.option(var)
391
+ if opt.nil?
392
+ to_env = variables.find { |opt| Array(opt.to[:env]).include?(var) }
393
+ if to_env
394
+ val = to_env.value
395
+ else
396
+ raise RuntimeError, "Undeclared variable '#{var}' in #{file}:#{line_num} -- #{row}" if raise_on_unknown
397
+ end
398
+ else
399
+ val = opt.value
400
+ end
401
+ else
402
+ val = substitutions[var]
403
+ end
404
+
405
+ if val && !val.to_s.empty?
406
+ val.to_s =~ /[\r\n\"\'\|]/ ? val.inspect : val.to_s
407
+ else
408
+ puts "Value for #{var} is not set. Substituting with an empty string." if warnings
409
+ ''
410
+ end
411
+ end
412
+ end
413
+ end.join("\n")
414
+ end
415
+
416
+ ##
417
+ # @param [String] text - content of yaml file
418
+ def replace_dollar_dollars(text)
419
+ text.gsub('$$', '$')
420
+ end
421
+
422
+ # Generates an array of lambdas that return true if a condition is true
423
+ # Possible syntaxes:
424
+ # @example
425
+ # normalize_ifs( 'wp' ) # lambdas return true if variable wp is not null or false or 'false'
426
+ # normalize_ifs( wp: 1 ) # lambdas return true if value of wp is 1
427
+ # normalize_ifs( ['wp, :ws'] ) # lambdas return true if wp and ws are not not null or false or 'false'
428
+ # normalize_ifs( wp: 1, ws: 1) # lambdas return true if wp and ws are 1
429
+ # normalize_ifs(nil) # returns nil
430
+ def normalize_ifs(ifs)
431
+ case ifs
432
+ when NilClass
433
+ nil
434
+ when Array
435
+ ifs.map do |iff|
436
+ lambda { val = variables.value_of(iff.to_s); !val.nil? && !val.kind_of?(FalseClass) && val != 'false' }
437
+ end
438
+ when Hash
439
+ ifs.each_with_object([]) do |(k, v), arr|
440
+ arr << lambda { variables.value_of(k.to_s) == v }
441
+ end
442
+ when String, Symbol
443
+ [lambda { val = variables.value_of(ifs.to_s); !val.nil? && !val.kind_of?(FalseClass) && val != 'false' }]
444
+ else
445
+ raise TypeError, "Invalid syntax for if: #{ifs.inspect}"
446
+ end
447
+ end
448
+
449
+ # @param [Hash] service_config
450
+ # @return [Hash] updated service config
451
+ def extend_config(service_config)
452
+ extends = service_config['extends']
453
+ case extends
454
+ when NilClass
455
+ return
456
+ when String
457
+ raise ("Service '#{extends}' not found in #{file}") unless services.key?(extends)
458
+ parent_config = process_config(services[extends])
459
+ when Hash
460
+ target = extends['file'] || extends['stack']
461
+ raise ("Service '#{extends}' does not define file: or stack: source") if target.nil?
462
+ parent_config = from_external_stack(target, extends['service'])
463
+ else
464
+ raise TypeError, "Extends must be a hash or string"
465
+ end
466
+ ServiceExtender.new(service_config).extend_from(parent_config)
467
+ end
468
+
469
+ def store_failures(data)
470
+ data['errors'] ||= data[:errors] || []
471
+ data['notifications'] ||= data[:notifications] || []
472
+ errors << { File.basename(file) => data['errors'] } unless data['errors'].empty?
473
+ notifications << { File.basename(file) => data['notifications'] } unless data['notifications'].empty?
474
+ end
475
+
476
+ # @param [Hash] options - service config
477
+ def normalize_env_vars(options)
478
+ if options['environment'].kind_of?(Hash)
479
+ options['environment'] = options['environment'].map { |k, v| "#{k}=#{v}" }
480
+ end
481
+ end
482
+
483
+ # @param [Hash] options
484
+ def merge_env_vars(options)
485
+ return options['environment'] unless options['env_file']
486
+
487
+ options['env_file'] = [options['env_file']] if options['env_file'].kind_of?(String)
488
+ options['environment'] = [] unless options['environment']
489
+ options['env_file'].each do |env_file|
490
+ options['environment'].concat(read_env_file(env_file))
491
+ end
492
+ options.delete('env_file')
493
+ options['environment'].uniq! { |s| s.split('=').first }
494
+ end
495
+
496
+ # @param [String] path
497
+ def read_env_file(path)
498
+ File.readlines(path).map { |line| line.strip }.reject { |line| line.start_with?('#') || line.empty? }
499
+ end
500
+
501
+ def expand_build_context(options)
502
+ if options['build'].kind_of?(String)
503
+ options['build'] = File.expand_path(options['build'])
504
+ elsif context = safe_dig(options, 'build', 'context')
505
+ options['build']['context'] = File.expand_path(context)
506
+ end
507
+ end
508
+
509
+ # @param [Hash] options - service config
510
+ def normalize_build_args(options)
511
+ build = options['build']
512
+ return unless build.kind_of?(Hash)
513
+ args = build['args']
514
+ return unless args
515
+ return unless args.kind_of?(Array)
516
+ build.delete('args')
517
+ build['args'] = args.map { |arg| arg.split('=', 2) }.to_h
518
+ end
519
+
520
+ def env
521
+ ENV
522
+ end
523
+ end
524
+ end
525
+ end
@@ -0,0 +1,65 @@
1
+ module Kontena::Cli::Stacks
2
+ module YAML
3
+ class ServiceExtender
4
+ include Kontena::Util
5
+ attr_reader :service_config
6
+
7
+ # @param [Hash] service_config
8
+ def initialize(service_config)
9
+ @service_config = service_config
10
+ end
11
+
12
+ # @param [Hash] from
13
+ # @return [Hash]
14
+ def extend_from(from)
15
+ service_config['environment'] = extend_env_vars(from['env'], service_config['environment'])
16
+ service_config['secrets'] = extend_secrets( from['secrets'], service_config['secrets'])
17
+ build_args = extend_build_args(safe_dig(from, 'build', 'args'), safe_dig(service_config, 'build', 'args'))
18
+ unless build_args.empty?
19
+ service_config['build'] ||= {}
20
+ service_config['build']['args'] = build_args
21
+ end
22
+ from.merge(service_config)
23
+ end
24
+
25
+ private
26
+
27
+ def env_to_hash(env_array)
28
+ env_array.map { |env| env.split('=', 2) }.to_h
29
+ end
30
+
31
+ # Takes two arrays of "key=value" pairs and merges them. Keys in "from"-array
32
+ # will not overwrite keys that already exist in "to"-array.
33
+ #
34
+ # @param [Array] from
35
+ # @param [Array] to
36
+ # @return [Array]
37
+ def extend_env_vars(from, to)
38
+ env_to_hash(from || []).merge(env_to_hash(to || [])).map { |k,v| [k.to_s, v.to_s].join('=') }
39
+ end
40
+
41
+ # Takes two arrays of hashes containing { 'secret' => 'str', 'type' => 'str', 'name' => 'str' }
42
+ # and merges them. 'secret' is the primary key, secrets found in "to" are not overwritten.
43
+ #
44
+ # @param [Array] from
45
+ # @param [Array] to
46
+ # @return [Array]
47
+ def extend_secrets(from, to)
48
+ from ||= []
49
+ to ||= []
50
+ uniq_from = []
51
+ from.each do |from_hash|
52
+ uniq_from << from_hash unless to.find {|to_hash| from_hash['secret'] == to_hash['secret'] }
53
+ end
54
+ to + uniq_from
55
+ end
56
+
57
+ # Basic merge of two hashes, "to" is dominant.
58
+ def extend_build_args(from, to)
59
+ from ||= {}
60
+ to ||= {}
61
+ from.merge(to)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,41 @@
1
+ require 'pathname'
2
+
3
+ module Kontena::Cli::Stacks
4
+ module YAML
5
+ class FileLoader < StackFileLoader
6
+ def self.match?(source, parent = nil)
7
+ ::File.exist?(with_context(source, parent))
8
+ end
9
+
10
+ def self.is_file?(parent)
11
+ parent.is_a?(FileLoader)
12
+ end
13
+
14
+ def self.with_context(source, parent = nil)
15
+ if is_file?(parent)
16
+ File.join(File.dirname(parent.source), source)
17
+ else
18
+ File.absolute_path(source)
19
+ end
20
+ end
21
+
22
+ def initialize(*args)
23
+ super
24
+ @source = self.class.with_context(@source, @parent)
25
+ end
26
+
27
+ def read_content
28
+ ::File.read(source)
29
+ end
30
+
31
+ def origin
32
+ "file"
33
+ end
34
+
35
+ def registry
36
+ "file://"
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,24 @@
1
+ module Kontena::Cli::Stacks
2
+ module YAML
3
+ class RegistryLoader < StackFileLoader
4
+ def self.match?(source, parent = nil)
5
+ source =~ /\A[a-zA-Z0-9\_\.\-]+\/[a-zA-Z0-9\_\.\-]+(?::.*)?\z/ && !FileLoader.match?(source, parent)
6
+ end
7
+
8
+ def read_content
9
+ Kontena::StacksCache.pull(Kontena::Cli::Stacks::StackName.new(source))
10
+ end
11
+
12
+ def origin
13
+ "registry"
14
+ end
15
+
16
+ def registry
17
+ account = Kontena::Cli::Config.current_account
18
+ raise "Current account not set" if account.nil?
19
+ account.stacks_url
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,23 @@
1
+ module Kontena::Cli::Stacks
2
+ module YAML
3
+ class UriLoader < StackFileLoader
4
+ def self.match?(source, parent = nil)
5
+ source.include?('://') && !FileLoader.match?(source, parent)
6
+ end
7
+
8
+ def read_content
9
+ require 'open-uri'
10
+ stream = open(source)
11
+ stream.read
12
+ end
13
+
14
+ def origin
15
+ "uri"
16
+ end
17
+
18
+ def registry
19
+ "file://"
20
+ end
21
+ end
22
+ end
23
+ end