kamal-insecure 2.7.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 (130) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +13 -0
  4. data/bin/kamal +18 -0
  5. data/lib/kamal/cli/accessory.rb +313 -0
  6. data/lib/kamal/cli/alias/command.rb +10 -0
  7. data/lib/kamal/cli/app/assets.rb +24 -0
  8. data/lib/kamal/cli/app/boot.rb +126 -0
  9. data/lib/kamal/cli/app/error_pages.rb +33 -0
  10. data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
  11. data/lib/kamal/cli/app.rb +400 -0
  12. data/lib/kamal/cli/base.rb +223 -0
  13. data/lib/kamal/cli/build/clone.rb +61 -0
  14. data/lib/kamal/cli/build.rb +204 -0
  15. data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
  16. data/lib/kamal/cli/healthcheck/error.rb +2 -0
  17. data/lib/kamal/cli/healthcheck/poller.rb +42 -0
  18. data/lib/kamal/cli/lock.rb +45 -0
  19. data/lib/kamal/cli/main.rb +277 -0
  20. data/lib/kamal/cli/proxy.rb +290 -0
  21. data/lib/kamal/cli/prune.rb +34 -0
  22. data/lib/kamal/cli/registry.rb +19 -0
  23. data/lib/kamal/cli/secrets.rb +49 -0
  24. data/lib/kamal/cli/server.rb +50 -0
  25. data/lib/kamal/cli/templates/deploy.yml +101 -0
  26. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
  27. data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
  28. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
  29. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  30. data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
  31. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
  32. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
  33. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +122 -0
  34. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
  35. data/lib/kamal/cli/templates/secrets +17 -0
  36. data/lib/kamal/cli.rb +9 -0
  37. data/lib/kamal/commander/specifics.rb +62 -0
  38. data/lib/kamal/commander.rb +167 -0
  39. data/lib/kamal/commands/accessory/proxy.rb +16 -0
  40. data/lib/kamal/commands/accessory.rb +113 -0
  41. data/lib/kamal/commands/app/assets.rb +51 -0
  42. data/lib/kamal/commands/app/containers.rb +31 -0
  43. data/lib/kamal/commands/app/error_pages.rb +9 -0
  44. data/lib/kamal/commands/app/execution.rb +32 -0
  45. data/lib/kamal/commands/app/images.rb +13 -0
  46. data/lib/kamal/commands/app/logging.rb +28 -0
  47. data/lib/kamal/commands/app/proxy.rb +32 -0
  48. data/lib/kamal/commands/app.rb +124 -0
  49. data/lib/kamal/commands/auditor.rb +39 -0
  50. data/lib/kamal/commands/base.rb +134 -0
  51. data/lib/kamal/commands/builder/base.rb +124 -0
  52. data/lib/kamal/commands/builder/clone.rb +31 -0
  53. data/lib/kamal/commands/builder/cloud.rb +22 -0
  54. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  55. data/lib/kamal/commands/builder/local.rb +14 -0
  56. data/lib/kamal/commands/builder/pack.rb +46 -0
  57. data/lib/kamal/commands/builder/remote.rb +63 -0
  58. data/lib/kamal/commands/builder.rb +48 -0
  59. data/lib/kamal/commands/docker.rb +34 -0
  60. data/lib/kamal/commands/hook.rb +20 -0
  61. data/lib/kamal/commands/lock.rb +70 -0
  62. data/lib/kamal/commands/proxy.rb +127 -0
  63. data/lib/kamal/commands/prune.rb +38 -0
  64. data/lib/kamal/commands/registry.rb +16 -0
  65. data/lib/kamal/commands/server.rb +15 -0
  66. data/lib/kamal/commands.rb +2 -0
  67. data/lib/kamal/configuration/accessory.rb +241 -0
  68. data/lib/kamal/configuration/alias.rb +15 -0
  69. data/lib/kamal/configuration/boot.rb +25 -0
  70. data/lib/kamal/configuration/builder.rb +211 -0
  71. data/lib/kamal/configuration/docs/accessory.yml +128 -0
  72. data/lib/kamal/configuration/docs/alias.yml +26 -0
  73. data/lib/kamal/configuration/docs/boot.yml +19 -0
  74. data/lib/kamal/configuration/docs/builder.yml +132 -0
  75. data/lib/kamal/configuration/docs/configuration.yml +184 -0
  76. data/lib/kamal/configuration/docs/env.yml +116 -0
  77. data/lib/kamal/configuration/docs/logging.yml +21 -0
  78. data/lib/kamal/configuration/docs/proxy.yml +164 -0
  79. data/lib/kamal/configuration/docs/registry.yml +56 -0
  80. data/lib/kamal/configuration/docs/role.yml +53 -0
  81. data/lib/kamal/configuration/docs/servers.yml +27 -0
  82. data/lib/kamal/configuration/docs/ssh.yml +70 -0
  83. data/lib/kamal/configuration/docs/sshkit.yml +23 -0
  84. data/lib/kamal/configuration/env/tag.rb +13 -0
  85. data/lib/kamal/configuration/env.rb +38 -0
  86. data/lib/kamal/configuration/logging.rb +33 -0
  87. data/lib/kamal/configuration/proxy/boot.rb +129 -0
  88. data/lib/kamal/configuration/proxy.rb +124 -0
  89. data/lib/kamal/configuration/registry.rb +32 -0
  90. data/lib/kamal/configuration/role.rb +222 -0
  91. data/lib/kamal/configuration/servers.rb +25 -0
  92. data/lib/kamal/configuration/ssh.rb +57 -0
  93. data/lib/kamal/configuration/sshkit.rb +22 -0
  94. data/lib/kamal/configuration/validation.rb +27 -0
  95. data/lib/kamal/configuration/validator/accessory.rb +13 -0
  96. data/lib/kamal/configuration/validator/alias.rb +15 -0
  97. data/lib/kamal/configuration/validator/builder.rb +15 -0
  98. data/lib/kamal/configuration/validator/configuration.rb +6 -0
  99. data/lib/kamal/configuration/validator/env.rb +54 -0
  100. data/lib/kamal/configuration/validator/proxy.rb +25 -0
  101. data/lib/kamal/configuration/validator/registry.rb +25 -0
  102. data/lib/kamal/configuration/validator/role.rb +13 -0
  103. data/lib/kamal/configuration/validator/servers.rb +7 -0
  104. data/lib/kamal/configuration/validator.rb +191 -0
  105. data/lib/kamal/configuration/volume.rb +22 -0
  106. data/lib/kamal/configuration.rb +372 -0
  107. data/lib/kamal/docker.rb +30 -0
  108. data/lib/kamal/env_file.rb +44 -0
  109. data/lib/kamal/git.rb +37 -0
  110. data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +51 -0
  111. data/lib/kamal/secrets/adapters/base.rb +33 -0
  112. data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
  113. data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
  114. data/lib/kamal/secrets/adapters/doppler.rb +57 -0
  115. data/lib/kamal/secrets/adapters/enpass.rb +71 -0
  116. data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
  117. data/lib/kamal/secrets/adapters/last_pass.rb +40 -0
  118. data/lib/kamal/secrets/adapters/one_password.rb +104 -0
  119. data/lib/kamal/secrets/adapters/passbolt.rb +130 -0
  120. data/lib/kamal/secrets/adapters/test.rb +14 -0
  121. data/lib/kamal/secrets/adapters.rb +16 -0
  122. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +33 -0
  123. data/lib/kamal/secrets.rb +42 -0
  124. data/lib/kamal/sshkit_with_ext.rb +142 -0
  125. data/lib/kamal/tags.rb +40 -0
  126. data/lib/kamal/utils/sensitive.rb +20 -0
  127. data/lib/kamal/utils.rb +110 -0
  128. data/lib/kamal/version.rb +3 -0
  129. data/lib/kamal.rb +14 -0
  130. metadata +365 -0
@@ -0,0 +1,34 @@
1
+ class Kamal::Commands::Docker < Kamal::Commands::Base
2
+ # Install Docker using the https://github.com/docker/docker-install convenience script.
3
+ def install
4
+ pipe get_docker, :sh
5
+ end
6
+
7
+ # Checks the Docker client version. Fails if Docker is not installed.
8
+ def installed?
9
+ docker "-v"
10
+ end
11
+
12
+ # Checks the Docker server version. Fails if Docker is not running.
13
+ def running?
14
+ docker :version
15
+ end
16
+
17
+ # Do we have superuser access to install Docker and start system services?
18
+ def superuser?
19
+ [ '[ "${EUID:-$(id -u)}" -eq 0 ] || command -v sudo >/dev/null || command -v su >/dev/null' ]
20
+ end
21
+
22
+ def create_network
23
+ docker :network, :create, :kamal
24
+ end
25
+
26
+ private
27
+ def get_docker
28
+ shell \
29
+ any \
30
+ [ :curl, "-fsSL", "https://get.docker.com" ],
31
+ [ :wget, "-O -", "https://get.docker.com" ],
32
+ [ :echo, "\"exit 1\"" ]
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ class Kamal::Commands::Hook < Kamal::Commands::Base
2
+ def run(hook)
3
+ [ hook_file(hook) ]
4
+ end
5
+
6
+ def env(secrets: false, **details)
7
+ tags(**details).env.tap do |env|
8
+ env.merge!(config.secrets.to_h) if secrets
9
+ end
10
+ end
11
+
12
+ def hook_exists?(hook)
13
+ Pathname.new(hook_file(hook)).exist?
14
+ end
15
+
16
+ private
17
+ def hook_file(hook)
18
+ File.join(config.hooks_path, hook)
19
+ end
20
+ end
@@ -0,0 +1,70 @@
1
+ require "active_support/duration"
2
+ require "time"
3
+ require "base64"
4
+
5
+ class Kamal::Commands::Lock < Kamal::Commands::Base
6
+ def acquire(message, version)
7
+ combine \
8
+ [ :mkdir, lock_dir ],
9
+ write_lock_details(message, version)
10
+ end
11
+
12
+ def release
13
+ combine \
14
+ [ :rm, lock_details_file ],
15
+ [ :rm, "-r", lock_dir ]
16
+ end
17
+
18
+ def status
19
+ combine \
20
+ stat_lock_dir,
21
+ read_lock_details
22
+ end
23
+
24
+ def ensure_locks_directory
25
+ [ :mkdir, "-p", locks_dir ]
26
+ end
27
+
28
+ private
29
+ def write_lock_details(message, version)
30
+ write \
31
+ [ :echo, "\"#{Base64.encode64(lock_details(message, version))}\"" ],
32
+ lock_details_file
33
+ end
34
+
35
+ def read_lock_details
36
+ pipe \
37
+ [ :cat, lock_details_file ],
38
+ [ :base64, "-d" ]
39
+ end
40
+
41
+ def stat_lock_dir
42
+ write \
43
+ [ :stat, lock_dir ],
44
+ "/dev/null"
45
+ end
46
+
47
+ def lock_dir
48
+ dir_name = [ "lock", config.service, config.destination ].compact.join("-")
49
+
50
+ File.join(config.run_directory, dir_name)
51
+ end
52
+
53
+ def lock_details_file
54
+ File.join(lock_dir, "details")
55
+ end
56
+
57
+ def lock_details(message, version)
58
+ <<~DETAILS.strip
59
+ Locked by: #{locked_by} at #{Time.now.utc.iso8601}
60
+ Version: #{version}
61
+ Message: #{message}
62
+ DETAILS
63
+ end
64
+
65
+ def locked_by
66
+ Kamal::Git.user_name
67
+ rescue Errno::ENOENT
68
+ "Unknown"
69
+ end
70
+ end
@@ -0,0 +1,127 @@
1
+ class Kamal::Commands::Proxy < Kamal::Commands::Base
2
+ delegate :argumentize, :optionize, to: Kamal::Utils
3
+
4
+ def run
5
+ pipe boot_config, xargs(docker_run)
6
+ end
7
+
8
+ def start
9
+ docker :container, :start, container_name
10
+ end
11
+
12
+ def stop(name: container_name)
13
+ docker :container, :stop, name
14
+ end
15
+
16
+ def start_or_run
17
+ combine start, run, by: "||"
18
+ end
19
+
20
+ def info
21
+ docker :ps, "--filter", "name=^#{container_name}$"
22
+ end
23
+
24
+ def version
25
+ pipe \
26
+ docker(:inspect, container_name, "--format '{{.Config.Image}}'"),
27
+ [ :awk, "-F:", "'{print \$NF}'" ]
28
+ end
29
+
30
+ def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
31
+ pipe \
32
+ docker(:logs, container_name, ("--since #{since}" if since), ("--tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
33
+ ("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
34
+ end
35
+
36
+ def follow_logs(host:, timestamps: true, grep: nil, grep_options: nil)
37
+ run_over_ssh pipe(
38
+ docker(:logs, container_name, ("--timestamps" if timestamps), "--tail", "10", "--follow", "2>&1"),
39
+ (%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
40
+ ).join(" "), host: host
41
+ end
42
+
43
+ def remove_container
44
+ docker :container, :prune, "--force", "--filter", "label=org.opencontainers.image.title=kamal-proxy"
45
+ end
46
+
47
+ def remove_image
48
+ docker :image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=kamal-proxy"
49
+ end
50
+
51
+ def cleanup_traefik
52
+ chain \
53
+ docker(:container, :stop, "traefik"),
54
+ combine(
55
+ docker(:container, :prune, "--force", "--filter", "label=org.opencontainers.image.title=Traefik"),
56
+ docker(:image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=Traefik")
57
+ )
58
+ end
59
+
60
+ def ensure_proxy_directory
61
+ make_directory config.proxy_boot.host_directory
62
+ end
63
+
64
+ def remove_proxy_directory
65
+ remove_directory config.proxy_boot.host_directory
66
+ end
67
+
68
+ def ensure_apps_config_directory
69
+ make_directory config.proxy_boot.apps_directory
70
+ end
71
+
72
+ def boot_config
73
+ [ :echo, "#{substitute(read_boot_options)} #{substitute(read_image)}:#{substitute(read_image_version)} #{substitute(read_run_command)}" ]
74
+ end
75
+
76
+ def read_boot_options
77
+ read_file(config.proxy_boot.options_file, default: config.proxy_boot.default_boot_options.join(" "))
78
+ end
79
+
80
+ def read_image
81
+ read_file(config.proxy_boot.image_file, default: config.proxy_boot.image_default)
82
+ end
83
+
84
+ def read_image_version
85
+ read_file(config.proxy_boot.image_version_file, default: Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION)
86
+ end
87
+
88
+ def read_run_command
89
+ read_file(config.proxy_boot.run_command_file)
90
+ end
91
+
92
+ def reset_boot_options
93
+ remove_file config.proxy_boot.options_file
94
+ end
95
+
96
+ def reset_image
97
+ remove_file config.proxy_boot.image_file
98
+ end
99
+
100
+ def reset_image_version
101
+ remove_file config.proxy_boot.image_version_file
102
+ end
103
+
104
+ def reset_run_command
105
+ remove_file config.proxy_boot.run_command_file
106
+ end
107
+
108
+ private
109
+ def container_name
110
+ config.proxy_boot.container_name
111
+ end
112
+
113
+ def read_file(file, default: nil)
114
+ combine [ :cat, file, "2>", "/dev/null" ], [ :echo, "\"#{default}\"" ], by: "||"
115
+ end
116
+
117
+ def docker_run
118
+ docker \
119
+ :run,
120
+ "--name", container_name,
121
+ "--network", "kamal",
122
+ "--detach",
123
+ "--restart", "unless-stopped",
124
+ "--volume", "kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy",
125
+ *config.proxy_boot.apps_volume.docker_args
126
+ end
127
+ end
@@ -0,0 +1,38 @@
1
+ require "active_support/duration"
2
+ require "active_support/core_ext/numeric/time"
3
+
4
+ class Kamal::Commands::Prune < Kamal::Commands::Base
5
+ def dangling_images
6
+ docker :image, :prune, "--force", "--filter", "label=service=#{config.service}"
7
+ end
8
+
9
+ def tagged_images
10
+ pipe \
11
+ docker(:image, :ls, *service_filter, "--format", "'{{.ID}} {{.Repository}}:{{.Tag}}'"),
12
+ grep("-v -w \"#{active_image_list}\""),
13
+ "while read image tag; do docker rmi $tag; done"
14
+ end
15
+
16
+ def app_containers(retain:)
17
+ pipe \
18
+ docker(:ps, "-q", "-a", *service_filter, *stopped_containers_filters),
19
+ "tail -n +#{retain + 1}",
20
+ "while read container_id; do docker rm $container_id; done"
21
+ end
22
+
23
+ private
24
+ def stopped_containers_filters
25
+ [ "created", "exited", "dead" ].flat_map { |status| [ "--filter", "status=#{status}" ] }
26
+ end
27
+
28
+ def active_image_list
29
+ # Pull the images that are used by any containers
30
+ # Append repo:latest - to avoid deleting the latest tag
31
+ # Append repo:<none> - to avoid deleting dangling images that are in use. Unused dangling images are deleted separately
32
+ "$(docker container ls -a --format '{{.Image}}\\|' --filter label=service=#{config.service} | tr -d '\\n')#{config.latest_image}\\|#{config.repository}:<none>"
33
+ end
34
+
35
+ def service_filter
36
+ [ "--filter", "label=service=#{config.service}" ]
37
+ end
38
+ end
@@ -0,0 +1,16 @@
1
+ class Kamal::Commands::Registry < Kamal::Commands::Base
2
+ def login(registry_config: nil)
3
+ registry_config ||= config.registry
4
+
5
+ docker :login,
6
+ registry_config.server,
7
+ "-u", sensitive(Kamal::Utils.escape_shell_value(registry_config.username)),
8
+ "-p", sensitive(Kamal::Utils.escape_shell_value(registry_config.password))
9
+ end
10
+
11
+ def logout(registry_config: nil)
12
+ registry_config ||= config.registry
13
+
14
+ docker :logout, registry_config.server
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ class Kamal::Commands::Server < Kamal::Commands::Base
2
+ def ensure_run_directory
3
+ make_directory config.run_directory
4
+ end
5
+
6
+ def remove_app_directory
7
+ remove_directory config.app_directory
8
+ end
9
+
10
+ def app_directory_count
11
+ pipe \
12
+ [ :ls, config.apps_directory ],
13
+ [ :wc, "-l" ]
14
+ end
15
+ end
@@ -0,0 +1,2 @@
1
+ module Kamal::Commands
2
+ end
@@ -0,0 +1,241 @@
1
+ class Kamal::Configuration::Accessory
2
+ include Kamal::Configuration::Validation
3
+
4
+ DEFAULT_NETWORK = "kamal"
5
+
6
+ delegate :argumentize, :optionize, to: Kamal::Utils
7
+
8
+ attr_reader :name, :env, :proxy, :registry
9
+
10
+ def initialize(name, config:)
11
+ @name, @config, @accessory_config = name.inquiry, config, config.raw_config["accessories"][name]
12
+
13
+ validate! \
14
+ accessory_config,
15
+ example: validation_yml["accessories"]["mysql"],
16
+ context: "accessories/#{name}",
17
+ with: Kamal::Configuration::Validator::Accessory
18
+
19
+ ensure_valid_roles
20
+
21
+ @env = initialize_env
22
+ @proxy = initialize_proxy if running_proxy?
23
+ @registry = initialize_registry if accessory_config["registry"].present?
24
+ end
25
+
26
+ def service_name
27
+ accessory_config["service"] || "#{config.service}-#{name}"
28
+ end
29
+
30
+ def image
31
+ [ registry&.server, accessory_config["image"] ].compact.join("/")
32
+ end
33
+
34
+ def hosts
35
+ hosts_from_host || hosts_from_hosts || hosts_from_roles || hosts_from_tags
36
+ end
37
+
38
+ def port
39
+ if port = accessory_config["port"]&.to_s
40
+ port.include?(":") ? port : "#{port}:#{port}"
41
+ end
42
+ end
43
+
44
+ def network_args
45
+ argumentize "--network", network
46
+ end
47
+
48
+ def publish_args
49
+ argumentize "--publish", port if port
50
+ end
51
+
52
+ def labels
53
+ default_labels.merge(accessory_config["labels"] || {})
54
+ end
55
+
56
+ def label_args
57
+ argumentize "--label", labels
58
+ end
59
+
60
+ def env_args
61
+ [ *env.clear_args, *argumentize("--env-file", secrets_path) ]
62
+ end
63
+
64
+ def env_directory
65
+ File.join(config.env_directory, "accessories")
66
+ end
67
+
68
+ def secrets_io
69
+ env.secrets_io
70
+ end
71
+
72
+ def secrets_path
73
+ File.join(config.env_directory, "accessories", "#{name}.env")
74
+ end
75
+
76
+ def files
77
+ accessory_config["files"]&.to_h do |local_to_remote_mapping|
78
+ local_file, remote_file = local_to_remote_mapping.split(":")
79
+ [ expand_local_file(local_file), expand_remote_file(remote_file) ]
80
+ end || {}
81
+ end
82
+
83
+ def directories
84
+ accessory_config["directories"]&.to_h do |host_to_container_mapping|
85
+ host_path, container_path = host_to_container_mapping.split(":")
86
+ [ expand_host_path(host_path), container_path ]
87
+ end || {}
88
+ end
89
+
90
+ def volumes
91
+ specific_volumes + remote_files_as_volumes + remote_directories_as_volumes
92
+ end
93
+
94
+ def volume_args
95
+ argumentize "--volume", volumes
96
+ end
97
+
98
+ def option_args
99
+ if args = accessory_config["options"]
100
+ optionize args
101
+ else
102
+ []
103
+ end
104
+ end
105
+
106
+ def cmd
107
+ accessory_config["cmd"]
108
+ end
109
+
110
+ def running_proxy?
111
+ accessory_config["proxy"].present?
112
+ end
113
+
114
+ private
115
+ attr_reader :config, :accessory_config
116
+
117
+ def initialize_env
118
+ Kamal::Configuration::Env.new \
119
+ config: accessory_config.fetch("env", {}),
120
+ secrets: config.secrets,
121
+ context: "accessories/#{name}/env"
122
+ end
123
+
124
+ def initialize_proxy
125
+ Kamal::Configuration::Proxy.new \
126
+ config: config,
127
+ proxy_config: accessory_config["proxy"],
128
+ context: "accessories/#{name}/proxy",
129
+ secrets: config.secrets
130
+ end
131
+
132
+ def initialize_registry
133
+ Kamal::Configuration::Registry.new \
134
+ config: accessory_config,
135
+ secrets: config.secrets,
136
+ context: "accessories/#{name}/registry"
137
+ end
138
+
139
+ def default_labels
140
+ { "service" => service_name }
141
+ end
142
+
143
+ def expand_local_file(local_file)
144
+ if local_file.end_with?("erb")
145
+ with_clear_env_loaded { read_dynamic_file(local_file) }
146
+ else
147
+ Pathname.new(File.expand_path(local_file)).to_s
148
+ end
149
+ end
150
+
151
+ def with_clear_env_loaded
152
+ env.clear.each { |k, v| ENV[k] = v }
153
+ yield
154
+ ensure
155
+ env.clear.each { |k, v| ENV.delete(k) }
156
+ end
157
+
158
+ def read_dynamic_file(local_file)
159
+ StringIO.new(ERB.new(File.read(local_file)).result)
160
+ end
161
+
162
+ def expand_remote_file(remote_file)
163
+ service_name + remote_file
164
+ end
165
+
166
+ def specific_volumes
167
+ accessory_config["volumes"] || []
168
+ end
169
+
170
+ def remote_files_as_volumes
171
+ accessory_config["files"]&.collect do |local_to_remote_mapping|
172
+ _, remote_file = local_to_remote_mapping.split(":")
173
+ "#{service_data_directory + remote_file}:#{remote_file}"
174
+ end || []
175
+ end
176
+
177
+ def remote_directories_as_volumes
178
+ accessory_config["directories"]&.collect do |host_to_container_mapping|
179
+ host_path, container_path = host_to_container_mapping.split(":")
180
+ [ expand_host_path(host_path), container_path ].join(":")
181
+ end || []
182
+ end
183
+
184
+ def expand_host_path(host_path)
185
+ absolute_path?(host_path) ? host_path : File.join(service_data_directory, host_path)
186
+ end
187
+
188
+ def absolute_path?(path)
189
+ Pathname.new(path).absolute?
190
+ end
191
+
192
+ def service_data_directory
193
+ "$PWD/#{service_name}"
194
+ end
195
+
196
+ def hosts_from_host
197
+ [ accessory_config["host"] ] if accessory_config.key?("host")
198
+ end
199
+
200
+ def hosts_from_hosts
201
+ accessory_config["hosts"] if accessory_config.key?("hosts")
202
+ end
203
+
204
+ def hosts_from_roles
205
+ if accessory_config.key?("role")
206
+ config.role(accessory_config["role"])&.hosts
207
+ elsif accessory_config.key?("roles")
208
+ accessory_config["roles"].flat_map { |role| config.role(role)&.hosts }
209
+ end
210
+ end
211
+
212
+ def hosts_from_tags
213
+ if accessory_config.key?("tag")
214
+ extract_hosts_from_config_with_tag(accessory_config["tag"])
215
+ elsif accessory_config.key?("tags")
216
+ accessory_config["tags"].flat_map { |tag| extract_hosts_from_config_with_tag(tag) }
217
+ end
218
+ end
219
+
220
+ def extract_hosts_from_config_with_tag(tag)
221
+ if (servers_with_roles = config.raw_config.servers).is_a?(Hash)
222
+ servers_with_roles.flat_map do |role, servers_in_role|
223
+ servers_in_role.filter_map do |host|
224
+ host.keys.first if host.is_a?(Hash) && host.values.first.include?(tag)
225
+ end
226
+ end
227
+ end
228
+ end
229
+
230
+ def network
231
+ accessory_config["network"] || DEFAULT_NETWORK
232
+ end
233
+
234
+ def ensure_valid_roles
235
+ if accessory_config["roles"] && (missing_roles = accessory_config["roles"] - config.roles.map(&:name)).any?
236
+ raise Kamal::ConfigurationError, "accessories/#{name}: unknown roles #{missing_roles.join(", ")}"
237
+ elsif accessory_config["role"] && !config.role(accessory_config["role"])
238
+ raise Kamal::ConfigurationError, "accessories/#{name}: unknown role #{accessory_config["role"]}"
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,15 @@
1
+ class Kamal::Configuration::Alias
2
+ include Kamal::Configuration::Validation
3
+
4
+ attr_reader :name, :command
5
+
6
+ def initialize(name, config:)
7
+ @name, @command = name.inquiry, config.raw_config["aliases"][name]
8
+
9
+ validate! \
10
+ command,
11
+ example: validation_yml["aliases"]["uname"],
12
+ context: "aliases/#{name}",
13
+ with: Kamal::Configuration::Validator::Alias
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ class Kamal::Configuration::Boot
2
+ include Kamal::Configuration::Validation
3
+
4
+ attr_reader :boot_config, :host_count
5
+
6
+ def initialize(config:)
7
+ @boot_config = config.raw_config.boot || {}
8
+ @host_count = config.all_hosts.count
9
+ validate! boot_config
10
+ end
11
+
12
+ def limit
13
+ limit = boot_config["limit"]
14
+
15
+ if limit.to_s.end_with?("%")
16
+ [ host_count * limit.to_i / 100, 1 ].max
17
+ else
18
+ limit
19
+ end
20
+ end
21
+
22
+ def wait
23
+ boot_config["wait"]
24
+ end
25
+ end