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,32 @@
1
+ module Kamal::Commands::App::Proxy
2
+ delegate :container_name, to: :"config.proxy_boot", prefix: :proxy
3
+
4
+ def deploy(target:)
5
+ proxy_exec :deploy, role.container_prefix, *role.proxy.deploy_command_args(target: target)
6
+ end
7
+
8
+ def remove
9
+ proxy_exec :remove, role.container_prefix
10
+ end
11
+
12
+ def live
13
+ proxy_exec :resume, role.container_prefix
14
+ end
15
+
16
+ def maintenance(**options)
17
+ proxy_exec :stop, role.container_prefix, *role.proxy.stop_command_args(**options)
18
+ end
19
+
20
+ def remove_proxy_app_directory
21
+ remove_directory config.proxy_boot.app_directory
22
+ end
23
+
24
+ def create_ssl_directory
25
+ make_directory(File.join(config.proxy_boot.tls_directory, role.name))
26
+ end
27
+
28
+ private
29
+ def proxy_exec(*command)
30
+ docker :exec, proxy_container_name, "kamal-proxy", *command
31
+ end
32
+ end
@@ -0,0 +1,124 @@
1
+ class Kamal::Commands::App < Kamal::Commands::Base
2
+ include Assets, Containers, ErrorPages, Execution, Images, Logging, Proxy
3
+
4
+ ACTIVE_DOCKER_STATUSES = [ :running, :restarting ]
5
+
6
+ attr_reader :role, :host
7
+
8
+ delegate :container_name, to: :role
9
+
10
+ def initialize(config, role: nil, host: nil)
11
+ super(config)
12
+ @role = role
13
+ @host = host
14
+ end
15
+
16
+ def run(hostname: nil)
17
+ docker :run,
18
+ "--detach",
19
+ "--restart unless-stopped",
20
+ "--name", container_name,
21
+ "--network", "kamal",
22
+ *([ "--hostname", hostname ] if hostname),
23
+ "--env", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
24
+ "--env", "KAMAL_VERSION=\"#{config.version}\"",
25
+ "--env", "KAMAL_HOST=\"#{host}\"",
26
+ *role.env_args(host),
27
+ *role.logging_args,
28
+ *config.volume_args,
29
+ *role.asset_volume_args,
30
+ *role.label_args,
31
+ *role.option_args,
32
+ config.absolute_image,
33
+ role.cmd
34
+ end
35
+
36
+ def start
37
+ docker :start, container_name
38
+ end
39
+
40
+ def status(version:)
41
+ pipe container_id_for_version(version), xargs(docker(:inspect, "--format", DOCKER_HEALTH_STATUS_FORMAT))
42
+ end
43
+
44
+ def stop(version: nil)
45
+ pipe \
46
+ version ? container_id_for_version(version) : current_running_container_id,
47
+ xargs(docker(:stop, *role.stop_args))
48
+ end
49
+
50
+ def info
51
+ docker :ps, *container_filter_args
52
+ end
53
+
54
+
55
+ def current_running_container_id
56
+ current_running_container(format: "--quiet")
57
+ end
58
+
59
+ def container_id_for_version(version, only_running: false)
60
+ container_id_for(container_name: container_name(version), only_running: only_running)
61
+ end
62
+
63
+ def current_running_version
64
+ pipe \
65
+ current_running_container(format: "--format '{{.Names}}'"),
66
+ extract_version_from_name
67
+ end
68
+
69
+ def list_versions(*docker_args, statuses: nil)
70
+ pipe \
71
+ docker(:ps, *container_filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
72
+ extract_version_from_name
73
+ end
74
+
75
+ def ensure_env_directory
76
+ make_directory role.env_directory
77
+ end
78
+
79
+ private
80
+ def latest_image_id
81
+ docker :image, :ls, *argumentize("--filter", "reference=#{config.latest_image}"), "--format", "'{{.ID}}'"
82
+ end
83
+
84
+ def current_running_container(format:)
85
+ pipe \
86
+ shell(chain(latest_image_container(format: format), latest_container(format: format))),
87
+ [ :head, "-1" ]
88
+ end
89
+
90
+ def latest_image_container(format:)
91
+ latest_container format: format, filters: [ "ancestor=$(#{latest_image_id.join(" ")})" ]
92
+ end
93
+
94
+ def latest_container(format:, filters: nil)
95
+ docker :ps, "--latest", *format, *container_filter_args(statuses: ACTIVE_DOCKER_STATUSES), argumentize("--filter", filters)
96
+ end
97
+
98
+ def container_filter_args(statuses: nil)
99
+ argumentize "--filter", container_filters(statuses: statuses)
100
+ end
101
+
102
+ def image_filter_args
103
+ argumentize "--filter", image_filters
104
+ end
105
+
106
+ def extract_version_from_name
107
+ # Extract SHA from "service-role-dest-SHA"
108
+ %(while read line; do echo ${line##{role.container_prefix}-}; done)
109
+ end
110
+
111
+ def container_filters(statuses: nil)
112
+ [ "label=service=#{config.service}" ].tap do |filters|
113
+ filters << "label=destination=#{config.destination}"
114
+ filters << "label=role=#{role}" if role
115
+ statuses&.each do |status|
116
+ filters << "status=#{status}"
117
+ end
118
+ end
119
+ end
120
+
121
+ def image_filters
122
+ [ "label=service=#{config.service}" ]
123
+ end
124
+ end
@@ -0,0 +1,39 @@
1
+ class Kamal::Commands::Auditor < Kamal::Commands::Base
2
+ attr_reader :details
3
+ delegate :escape_shell_value, to: Kamal::Utils
4
+
5
+ def initialize(config, **details)
6
+ super(config)
7
+ @details = details
8
+ end
9
+
10
+ # Runs remotely
11
+ def record(line, **details)
12
+ combine \
13
+ make_run_directory,
14
+ append([ :echo, escape_shell_value(audit_line(line, **details)) ], audit_log_file)
15
+ end
16
+
17
+ def reveal
18
+ [ :tail, "-n", 50, audit_log_file ]
19
+ end
20
+
21
+ private
22
+ def audit_log_file
23
+ file = [ config.service, config.destination, "audit.log" ].compact.join("-")
24
+
25
+ File.join(config.run_directory, file)
26
+ end
27
+
28
+ def audit_tags(**details)
29
+ tags(**self.details, **details)
30
+ end
31
+
32
+ def make_run_directory
33
+ [ :mkdir, "-p", config.run_directory ]
34
+ end
35
+
36
+ def audit_line(line, **details)
37
+ "#{audit_tags(**details).except(:version, :service_version, :service)} #{line}"
38
+ end
39
+ end
@@ -0,0 +1,134 @@
1
+ module Kamal::Commands
2
+ class Base
3
+ delegate :sensitive, :argumentize, to: Kamal::Utils
4
+
5
+ DOCKER_HEALTH_STATUS_FORMAT = "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'"
6
+
7
+ attr_accessor :config
8
+
9
+ def initialize(config)
10
+ @config = config
11
+ end
12
+
13
+ def run_over_ssh(*command, host:)
14
+ "ssh#{ssh_proxy_args}#{ssh_keys_args} -t #{config.ssh.user}@#{host} -p #{config.ssh.port} '#{command.join(" ").gsub("'", "'\\\\''")}'"
15
+ end
16
+
17
+ def container_id_for(container_name:, only_running: false)
18
+ docker :container, :ls, *("--all" unless only_running), "--filter", "name=^#{container_name}$", "--quiet"
19
+ end
20
+
21
+ def make_directory_for(remote_file)
22
+ make_directory Pathname.new(remote_file).dirname.to_s
23
+ end
24
+
25
+ def make_directory(path)
26
+ [ :mkdir, "-p", path ]
27
+ end
28
+
29
+ def remove_directory(path)
30
+ [ :rm, "-r", path ]
31
+ end
32
+
33
+ def remove_file(path)
34
+ [ :rm, path ]
35
+ end
36
+
37
+ def ensure_docker_installed
38
+ combine \
39
+ ensure_local_docker_installed,
40
+ ensure_local_buildx_installed
41
+ end
42
+
43
+ private
44
+ def combine(*commands, by: "&&")
45
+ commands
46
+ .compact
47
+ .collect { |command| Array(command) + [ by ] }.flatten # Join commands
48
+ .tap { |commands| commands.pop } # Remove trailing combiner
49
+ end
50
+
51
+ def chain(*commands)
52
+ combine *commands, by: ";"
53
+ end
54
+
55
+ def pipe(*commands)
56
+ combine *commands, by: "|"
57
+ end
58
+
59
+ def append(*commands)
60
+ combine *commands, by: ">>"
61
+ end
62
+
63
+ def write(*commands)
64
+ combine *commands, by: ">"
65
+ end
66
+
67
+ def any(*commands)
68
+ combine *commands, by: "||"
69
+ end
70
+
71
+ def substitute(*commands)
72
+ "\$\(#{commands.join(" ")}\)"
73
+ end
74
+
75
+ def xargs(command)
76
+ [ :xargs, command ].flatten
77
+ end
78
+
79
+ def shell(command)
80
+ [ :sh, "-c", "'#{command.flatten.join(" ").gsub("'", "'\\\\''")}'" ]
81
+ end
82
+
83
+ def docker(*args)
84
+ args.compact.unshift :docker
85
+ end
86
+
87
+ def pack(*args)
88
+ args.compact.unshift :pack
89
+ end
90
+
91
+ def git(*args, path: nil)
92
+ [ :git, *([ "-C", path ] if path), *args.compact ]
93
+ end
94
+
95
+ def grep(*args)
96
+ args.compact.unshift :grep
97
+ end
98
+
99
+ def tags(**details)
100
+ Kamal::Tags.from_config(config, **details)
101
+ end
102
+
103
+ def ssh_proxy_args
104
+ case config.ssh.proxy
105
+ when Net::SSH::Proxy::Jump
106
+ " -J #{config.ssh.proxy.jump_proxies}"
107
+ when Net::SSH::Proxy::Command
108
+ " -o ProxyCommand='#{config.ssh.proxy.command_line_template}'"
109
+ end
110
+ end
111
+
112
+ def ssh_keys_args
113
+ "#{ ssh_keys.join("") if ssh_keys}" + "#{" -o IdentitiesOnly=yes" if config.ssh&.keys_only}"
114
+ end
115
+
116
+ def ssh_keys
117
+ config.ssh.keys&.map do |key|
118
+ " -i #{key}"
119
+ end
120
+ end
121
+
122
+ def ensure_local_docker_installed
123
+ docker "--version"
124
+ end
125
+
126
+ def ensure_local_buildx_installed
127
+ docker :buildx, "version"
128
+ end
129
+
130
+ def docker_interactive_args
131
+ STDIN.isatty ? "-it" : "-i"
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,124 @@
1
+ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
2
+ class BuilderError < StandardError; end
3
+
4
+ ENDPOINT_DOCKER_HOST_INSPECT = "'{{.Endpoints.docker.Host}}'"
5
+
6
+ delegate :argumentize, to: Kamal::Utils
7
+ delegate \
8
+ :args, :secrets, :dockerfile, :target, :arches, :local_arches, :remote_arches, :remote,
9
+ :pack?, :pack_builder, :pack_buildpacks,
10
+ :cache_from, :cache_to, :ssh, :provenance, :sbom, :driver, :docker_driver?,
11
+ to: :builder_config
12
+
13
+ def clean
14
+ docker :image, :rm, "--force", config.absolute_image
15
+ end
16
+
17
+ def push(export_action = "registry", tag_as_dirty: false)
18
+ docker :buildx, :build,
19
+ "--output=type=#{export_action},registry.insecure=true",
20
+ *platform_options(arches),
21
+ *([ "--builder", builder_name ] unless docker_driver?),
22
+ *build_tag_options(tag_as_dirty: tag_as_dirty),
23
+ *build_options,
24
+ build_context,
25
+ "2>&1"
26
+ end
27
+
28
+ def pull
29
+ docker :pull, config.absolute_image
30
+ end
31
+
32
+ def info
33
+ combine \
34
+ docker(:context, :ls),
35
+ docker(:buildx, :ls)
36
+ end
37
+
38
+ def inspect_builder
39
+ docker :buildx, :inspect, builder_name unless docker_driver?
40
+ end
41
+
42
+ def build_options
43
+ [ *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance, *builder_sbom ]
44
+ end
45
+
46
+ def build_context
47
+ config.builder.context
48
+ end
49
+
50
+ def validate_image
51
+ pipe \
52
+ docker(:inspect, "-f", "'{{ .Config.Labels.service }}'", config.absolute_image),
53
+ any(
54
+ [ :grep, "-x", config.service ],
55
+ "(echo \"Image #{config.absolute_image} is missing the 'service' label\" && exit 1)"
56
+ )
57
+ end
58
+
59
+ def first_mirror
60
+ docker(:info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
61
+ end
62
+
63
+ private
64
+ def build_tag_names(tag_as_dirty: false)
65
+ tag_names = [ config.absolute_image, config.latest_image ]
66
+ tag_names.map! { |t| "#{t}-dirty" } if tag_as_dirty
67
+ tag_names
68
+ end
69
+
70
+ def build_tag_options(tag_as_dirty: false)
71
+ build_tag_names(tag_as_dirty: tag_as_dirty).flat_map { |name| [ "-t", name ] }
72
+ end
73
+
74
+ def build_cache
75
+ if cache_to && cache_from
76
+ [ "--cache-to", cache_to,
77
+ "--cache-from", cache_from ]
78
+ end
79
+ end
80
+
81
+ def build_labels
82
+ argumentize "--label", { service: config.service }
83
+ end
84
+
85
+ def build_args
86
+ argumentize "--build-arg", args, sensitive: true
87
+ end
88
+
89
+ def build_secrets
90
+ argumentize "--secret", secrets.keys.collect { |secret| [ "id", secret ] }
91
+ end
92
+
93
+ def build_dockerfile
94
+ if Pathname.new(File.expand_path(dockerfile)).exist?
95
+ argumentize "--file", dockerfile
96
+ else
97
+ raise BuilderError, "Missing #{dockerfile}"
98
+ end
99
+ end
100
+
101
+ def build_target
102
+ argumentize "--target", target if target.present?
103
+ end
104
+
105
+ def build_ssh
106
+ argumentize "--ssh", ssh if ssh.present?
107
+ end
108
+
109
+ def builder_provenance
110
+ argumentize "--provenance", provenance unless provenance.nil?
111
+ end
112
+
113
+ def builder_sbom
114
+ argumentize "--sbom", sbom unless sbom.nil?
115
+ end
116
+
117
+ def builder_config
118
+ config.builder
119
+ end
120
+
121
+ def platform_options(arches)
122
+ argumentize "--platform", arches.map { |arch| "linux/#{arch}" }.join(",") if arches.any?
123
+ end
124
+ end
@@ -0,0 +1,31 @@
1
+ module Kamal::Commands::Builder::Clone
2
+ def clone
3
+ git :clone, escaped_root, "--recurse-submodules", path: config.builder.clone_directory.shellescape
4
+ end
5
+
6
+ def clone_reset_steps
7
+ [
8
+ git(:remote, "set-url", :origin, escaped_root, path: escaped_build_directory),
9
+ git(:fetch, :origin, path: escaped_build_directory),
10
+ git(:reset, "--hard", Kamal::Git.revision, path: escaped_build_directory),
11
+ git(:clean, "-fdx", path: escaped_build_directory),
12
+ git(:submodule, :update, "--init", path: escaped_build_directory)
13
+ ]
14
+ end
15
+
16
+ def clone_status
17
+ git :status, "--porcelain", path: escaped_build_directory
18
+ end
19
+
20
+ def clone_revision
21
+ git :"rev-parse", :HEAD, path: escaped_build_directory
22
+ end
23
+
24
+ def escaped_root
25
+ Kamal::Git.root.shellescape
26
+ end
27
+
28
+ def escaped_build_directory
29
+ config.builder.build_directory.shellescape
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ class Kamal::Commands::Builder::Cloud < Kamal::Commands::Builder::Base
2
+ # Expects `driver` to be of format "cloud docker-org-name/builder-name"
3
+
4
+ def create
5
+ docker :buildx, :create, "--driver", driver
6
+ end
7
+
8
+ def remove
9
+ docker :buildx, :rm, builder_name
10
+ end
11
+
12
+ private
13
+ def builder_name
14
+ driver.gsub(/[ \/]/, "-")
15
+ end
16
+
17
+ def inspect_buildx
18
+ pipe \
19
+ docker(:buildx, :inspect, builder_name),
20
+ grep("-q", "Endpoint:.*cloud://.*")
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ class Kamal::Commands::Builder::Hybrid < Kamal::Commands::Builder::Remote
2
+ def create
3
+ combine \
4
+ create_local_buildx,
5
+ create_remote_context,
6
+ append_remote_buildx
7
+ end
8
+
9
+ private
10
+ def builder_name
11
+ "kamal-hybrid-#{driver}-#{remote.gsub(/[^a-z0-9_-]/, "-")}"
12
+ end
13
+
14
+ def create_local_buildx
15
+ docker :buildx, :create, *platform_options(local_arches), "--name", builder_name, "--driver=#{driver}"
16
+ end
17
+
18
+ def append_remote_buildx
19
+ docker :buildx, :create, *platform_options(remote_arches), "--append", "--name", builder_name, remote_context_name
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ class Kamal::Commands::Builder::Local < Kamal::Commands::Builder::Base
2
+ def create
3
+ docker :buildx, :create, "--name", builder_name, "--driver=#{driver}" unless docker_driver?
4
+ end
5
+
6
+ def remove
7
+ docker :buildx, :rm, builder_name unless docker_driver?
8
+ end
9
+
10
+ private
11
+ def builder_name
12
+ "kamal-local-#{driver}"
13
+ end
14
+ end
@@ -0,0 +1,46 @@
1
+ class Kamal::Commands::Builder::Pack < Kamal::Commands::Builder::Base
2
+ def push(export_action = "registry")
3
+ combine \
4
+ build,
5
+ export(export_action)
6
+ end
7
+
8
+ def remove;end
9
+
10
+ def info
11
+ pack :builder, :inspect, pack_builder
12
+ end
13
+ alias_method :inspect_builder, :info
14
+
15
+ private
16
+ def build
17
+ pack(:build,
18
+ config.repository,
19
+ "--platform", platform,
20
+ "--creation-time", "now",
21
+ "--builder", pack_builder,
22
+ buildpacks,
23
+ "-t", config.absolute_image,
24
+ "-t", config.latest_image,
25
+ "--env", "BP_IMAGE_LABELS=service=#{config.service}",
26
+ *argumentize("--env", args),
27
+ *argumentize("--env", secrets, sensitive: true),
28
+ "--path", build_context)
29
+ end
30
+
31
+ def export(export_action)
32
+ return unless export_action == "registry"
33
+
34
+ combine \
35
+ docker(:push, config.absolute_image),
36
+ docker(:push, config.latest_image)
37
+ end
38
+
39
+ def platform
40
+ "linux/#{local_arches.first}"
41
+ end
42
+
43
+ def buildpacks
44
+ (pack_buildpacks << "paketo-buildpacks/image-labels").map { |buildpack| [ "--buildpack", buildpack ] }
45
+ end
46
+ end
@@ -0,0 +1,63 @@
1
+ class Kamal::Commands::Builder::Remote < Kamal::Commands::Builder::Base
2
+ def create
3
+ chain \
4
+ create_remote_context,
5
+ create_buildx
6
+ end
7
+
8
+ def remove
9
+ chain \
10
+ remove_remote_context,
11
+ remove_buildx
12
+ end
13
+
14
+ def info
15
+ chain \
16
+ docker(:context, :ls),
17
+ docker(:buildx, :ls)
18
+ end
19
+
20
+ def inspect_builder
21
+ combine \
22
+ combine inspect_buildx, inspect_remote_context,
23
+ [ "(echo no compatible builder && exit 1)" ],
24
+ by: "||"
25
+ end
26
+
27
+ private
28
+ def builder_name
29
+ "kamal-remote-#{remote.gsub(/[^a-z0-9_-]/, "-")}"
30
+ end
31
+
32
+ def remote_context_name
33
+ "#{builder_name}-context"
34
+ end
35
+
36
+ def inspect_buildx
37
+ pipe \
38
+ docker(:buildx, :inspect, builder_name),
39
+ grep("-q", "Endpoint:.*#{remote_context_name}")
40
+ end
41
+
42
+ def inspect_remote_context
43
+ pipe \
44
+ docker(:context, :inspect, remote_context_name, "--format", ENDPOINT_DOCKER_HOST_INSPECT),
45
+ grep("-xq", remote)
46
+ end
47
+
48
+ def create_remote_context
49
+ docker :context, :create, remote_context_name, "--description", "'#{builder_name} host'", "--docker", "'host=#{remote}'"
50
+ end
51
+
52
+ def remove_remote_context
53
+ docker :context, :rm, remote_context_name
54
+ end
55
+
56
+ def create_buildx
57
+ docker :buildx, :create, "--name", builder_name, remote_context_name
58
+ end
59
+
60
+ def remove_buildx
61
+ docker :buildx, :rm, builder_name
62
+ end
63
+ end
@@ -0,0 +1,48 @@
1
+ require "active_support/core_ext/string/filters"
2
+
3
+ class Kamal::Commands::Builder < Kamal::Commands::Base
4
+ delegate :create, :remove, :dev, :push, :clean, :pull, :info, :inspect_builder, :validate_image, :first_mirror, to: :target
5
+ delegate :local?, :remote?, :pack?, :cloud?, to: "config.builder"
6
+
7
+ include Clone
8
+
9
+ def name
10
+ target.class.to_s.remove("Kamal::Commands::Builder::").underscore.inquiry
11
+ end
12
+
13
+ def target
14
+ if remote?
15
+ if local?
16
+ hybrid
17
+ else
18
+ remote
19
+ end
20
+ elsif pack?
21
+ pack
22
+ elsif cloud?
23
+ cloud
24
+ else
25
+ local
26
+ end
27
+ end
28
+
29
+ def remote
30
+ @remote ||= Kamal::Commands::Builder::Remote.new(config)
31
+ end
32
+
33
+ def local
34
+ @local ||= Kamal::Commands::Builder::Local.new(config)
35
+ end
36
+
37
+ def hybrid
38
+ @hybrid ||= Kamal::Commands::Builder::Hybrid.new(config)
39
+ end
40
+
41
+ def pack
42
+ @pack ||= Kamal::Commands::Builder::Pack.new(config)
43
+ end
44
+
45
+ def cloud
46
+ @cloud ||= Kamal::Commands::Builder::Cloud.new(config)
47
+ end
48
+ end