nocoffee-kamal 2.3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) 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 +287 -0
  6. data/lib/kamal/cli/alias/command.rb +9 -0
  7. data/lib/kamal/cli/app/boot.rb +125 -0
  8. data/lib/kamal/cli/app/prepare_assets.rb +24 -0
  9. data/lib/kamal/cli/app.rb +335 -0
  10. data/lib/kamal/cli/base.rb +198 -0
  11. data/lib/kamal/cli/build/clone.rb +61 -0
  12. data/lib/kamal/cli/build.rb +162 -0
  13. data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
  14. data/lib/kamal/cli/healthcheck/error.rb +2 -0
  15. data/lib/kamal/cli/healthcheck/poller.rb +42 -0
  16. data/lib/kamal/cli/lock.rb +45 -0
  17. data/lib/kamal/cli/main.rb +279 -0
  18. data/lib/kamal/cli/proxy.rb +257 -0
  19. data/lib/kamal/cli/prune.rb +34 -0
  20. data/lib/kamal/cli/registry.rb +17 -0
  21. data/lib/kamal/cli/secrets.rb +43 -0
  22. data/lib/kamal/cli/server.rb +48 -0
  23. data/lib/kamal/cli/templates/deploy.yml +98 -0
  24. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
  25. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
  26. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  27. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
  28. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
  29. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +109 -0
  30. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
  31. data/lib/kamal/cli/templates/secrets +17 -0
  32. data/lib/kamal/cli.rb +8 -0
  33. data/lib/kamal/commander/specifics.rb +54 -0
  34. data/lib/kamal/commander.rb +176 -0
  35. data/lib/kamal/commands/accessory.rb +113 -0
  36. data/lib/kamal/commands/app/assets.rb +51 -0
  37. data/lib/kamal/commands/app/containers.rb +31 -0
  38. data/lib/kamal/commands/app/execution.rb +30 -0
  39. data/lib/kamal/commands/app/images.rb +13 -0
  40. data/lib/kamal/commands/app/logging.rb +18 -0
  41. data/lib/kamal/commands/app/proxy.rb +16 -0
  42. data/lib/kamal/commands/app.rb +115 -0
  43. data/lib/kamal/commands/auditor.rb +33 -0
  44. data/lib/kamal/commands/base.rb +98 -0
  45. data/lib/kamal/commands/builder/base.rb +111 -0
  46. data/lib/kamal/commands/builder/clone.rb +31 -0
  47. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  48. data/lib/kamal/commands/builder/local.rb +14 -0
  49. data/lib/kamal/commands/builder/remote.rb +63 -0
  50. data/lib/kamal/commands/builder.rb +56 -0
  51. data/lib/kamal/commands/docker.rb +34 -0
  52. data/lib/kamal/commands/hook.rb +20 -0
  53. data/lib/kamal/commands/lock.rb +70 -0
  54. data/lib/kamal/commands/proxy.rb +87 -0
  55. data/lib/kamal/commands/prune.rb +38 -0
  56. data/lib/kamal/commands/registry.rb +14 -0
  57. data/lib/kamal/commands/server.rb +15 -0
  58. data/lib/kamal/commands.rb +2 -0
  59. data/lib/kamal/configuration/accessory.rb +186 -0
  60. data/lib/kamal/configuration/alias.rb +15 -0
  61. data/lib/kamal/configuration/boot.rb +25 -0
  62. data/lib/kamal/configuration/builder.rb +191 -0
  63. data/lib/kamal/configuration/docs/accessory.yml +100 -0
  64. data/lib/kamal/configuration/docs/alias.yml +26 -0
  65. data/lib/kamal/configuration/docs/boot.yml +19 -0
  66. data/lib/kamal/configuration/docs/builder.yml +110 -0
  67. data/lib/kamal/configuration/docs/configuration.yml +178 -0
  68. data/lib/kamal/configuration/docs/env.yml +85 -0
  69. data/lib/kamal/configuration/docs/logging.yml +21 -0
  70. data/lib/kamal/configuration/docs/proxy.yml +110 -0
  71. data/lib/kamal/configuration/docs/registry.yml +52 -0
  72. data/lib/kamal/configuration/docs/role.yml +53 -0
  73. data/lib/kamal/configuration/docs/servers.yml +27 -0
  74. data/lib/kamal/configuration/docs/ssh.yml +70 -0
  75. data/lib/kamal/configuration/docs/sshkit.yml +23 -0
  76. data/lib/kamal/configuration/env/tag.rb +13 -0
  77. data/lib/kamal/configuration/env.rb +29 -0
  78. data/lib/kamal/configuration/logging.rb +33 -0
  79. data/lib/kamal/configuration/proxy.rb +63 -0
  80. data/lib/kamal/configuration/registry.rb +32 -0
  81. data/lib/kamal/configuration/role.rb +220 -0
  82. data/lib/kamal/configuration/servers.rb +18 -0
  83. data/lib/kamal/configuration/ssh.rb +57 -0
  84. data/lib/kamal/configuration/sshkit.rb +22 -0
  85. data/lib/kamal/configuration/validation.rb +27 -0
  86. data/lib/kamal/configuration/validator/accessory.rb +9 -0
  87. data/lib/kamal/configuration/validator/alias.rb +15 -0
  88. data/lib/kamal/configuration/validator/builder.rb +13 -0
  89. data/lib/kamal/configuration/validator/configuration.rb +6 -0
  90. data/lib/kamal/configuration/validator/env.rb +54 -0
  91. data/lib/kamal/configuration/validator/proxy.rb +15 -0
  92. data/lib/kamal/configuration/validator/registry.rb +25 -0
  93. data/lib/kamal/configuration/validator/role.rb +11 -0
  94. data/lib/kamal/configuration/validator/servers.rb +7 -0
  95. data/lib/kamal/configuration/validator.rb +171 -0
  96. data/lib/kamal/configuration/volume.rb +22 -0
  97. data/lib/kamal/configuration.rb +393 -0
  98. data/lib/kamal/env_file.rb +44 -0
  99. data/lib/kamal/git.rb +27 -0
  100. data/lib/kamal/secrets/adapters/base.rb +23 -0
  101. data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
  102. data/lib/kamal/secrets/adapters/last_pass.rb +39 -0
  103. data/lib/kamal/secrets/adapters/one_password.rb +70 -0
  104. data/lib/kamal/secrets/adapters/test.rb +14 -0
  105. data/lib/kamal/secrets/adapters.rb +14 -0
  106. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +32 -0
  107. data/lib/kamal/secrets.rb +42 -0
  108. data/lib/kamal/sshkit_with_ext.rb +142 -0
  109. data/lib/kamal/tags.rb +40 -0
  110. data/lib/kamal/utils/sensitive.rb +20 -0
  111. data/lib/kamal/utils.rb +110 -0
  112. data/lib/kamal/version.rb +3 -0
  113. data/lib/kamal.rb +14 -0
  114. metadata +349 -0
@@ -0,0 +1,115 @@
1
+ class Kamal::Commands::App < Kamal::Commands::Base
2
+ include Assets, Containers, 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
+ "-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
24
+ "-e", "KAMAL_VERSION=\"#{config.version}\"",
25
+ *role.env_args(host),
26
+ *role.logging_args,
27
+ *config.volume_args,
28
+ *role.asset_volume_args,
29
+ *role.label_args,
30
+ *role.option_args,
31
+ config.absolute_image,
32
+ role.cmd
33
+ end
34
+
35
+ def start
36
+ docker :start, container_name
37
+ end
38
+
39
+ def status(version:)
40
+ pipe container_id_for_version(version), xargs(docker(:inspect, "--format", DOCKER_HEALTH_STATUS_FORMAT))
41
+ end
42
+
43
+ def stop(version: nil)
44
+ pipe \
45
+ version ? container_id_for_version(version) : current_running_container_id,
46
+ xargs(docker(:stop, *role.stop_args))
47
+ end
48
+
49
+ def info
50
+ docker :ps, *filter_args
51
+ end
52
+
53
+
54
+ def current_running_container_id
55
+ current_running_container(format: "--quiet")
56
+ end
57
+
58
+ def container_id_for_version(version, only_running: false)
59
+ container_id_for(container_name: container_name(version), only_running: only_running)
60
+ end
61
+
62
+ def current_running_version
63
+ pipe \
64
+ current_running_container(format: "--format '{{.Names}}'"),
65
+ extract_version_from_name
66
+ end
67
+
68
+ def list_versions(*docker_args, statuses: nil)
69
+ pipe \
70
+ docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
71
+ extract_version_from_name
72
+ end
73
+
74
+ def ensure_env_directory
75
+ make_directory role.env_directory
76
+ end
77
+
78
+ private
79
+ def latest_image_id
80
+ docker :image, :ls, *argumentize("--filter", "reference=#{config.latest_image}"), "--format", "'{{.ID}}'"
81
+ end
82
+
83
+ def current_running_container(format:)
84
+ pipe \
85
+ shell(chain(latest_image_container(format: format), latest_container(format: format))),
86
+ [ :head, "-1" ]
87
+ end
88
+
89
+ def latest_image_container(format:)
90
+ latest_container format: format, filters: [ "ancestor=$(#{latest_image_id.join(" ")})" ]
91
+ end
92
+
93
+ def latest_container(format:, filters: nil)
94
+ docker :ps, "--latest", *format, *filter_args(statuses: ACTIVE_DOCKER_STATUSES), argumentize("--filter", filters)
95
+ end
96
+
97
+ def filter_args(statuses: nil)
98
+ argumentize "--filter", filters(statuses: statuses)
99
+ end
100
+
101
+ def extract_version_from_name
102
+ # Extract SHA from "service-role-dest-SHA"
103
+ %(while read line; do echo ${line##{role.container_prefix}-}; done)
104
+ end
105
+
106
+ def filters(statuses: nil)
107
+ [ "label=service=#{config.service}" ].tap do |filters|
108
+ filters << "label=destination=#{config.destination}" if config.destination
109
+ filters << "label=role=#{role}" if role
110
+ statuses&.each do |status|
111
+ filters << "status=#{status}"
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,33 @@
1
+ class Kamal::Commands::Auditor < Kamal::Commands::Base
2
+ attr_reader :details
3
+
4
+ def initialize(config, **details)
5
+ super(config)
6
+ @details = details
7
+ end
8
+
9
+ # Runs remotely
10
+ def record(line, **details)
11
+ combine \
12
+ [ :mkdir, "-p", config.run_directory ],
13
+ append(
14
+ [ :echo, audit_tags(**details).except(:version, :service_version, :service).to_s, line ],
15
+ audit_log_file
16
+ )
17
+ end
18
+
19
+ def reveal
20
+ [ :tail, "-n", 50, audit_log_file ]
21
+ end
22
+
23
+ private
24
+ def audit_log_file
25
+ file = [ config.service, config.destination, "audit.log" ].compact.join("-")
26
+
27
+ File.join(config.run_directory, file)
28
+ end
29
+
30
+ def audit_tags(**details)
31
+ tags(**self.details, **details)
32
+ end
33
+ end
@@ -0,0 +1,98 @@
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} -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
+ private
38
+ def combine(*commands, by: "&&")
39
+ commands
40
+ .compact
41
+ .collect { |command| Array(command) + [ by ] }.flatten # Join commands
42
+ .tap { |commands| commands.pop } # Remove trailing combiner
43
+ end
44
+
45
+ def chain(*commands)
46
+ combine *commands, by: ";"
47
+ end
48
+
49
+ def pipe(*commands)
50
+ combine *commands, by: "|"
51
+ end
52
+
53
+ def append(*commands)
54
+ combine *commands, by: ">>"
55
+ end
56
+
57
+ def write(*commands)
58
+ combine *commands, by: ">"
59
+ end
60
+
61
+ def any(*commands)
62
+ combine *commands, by: "||"
63
+ end
64
+
65
+ def xargs(command)
66
+ [ :xargs, command ].flatten
67
+ end
68
+
69
+ def shell(command)
70
+ [ :sh, "-c", "'#{command.flatten.join(" ").gsub("'", "'\\\\''")}'" ]
71
+ end
72
+
73
+ def docker(*args)
74
+ args.compact.unshift :docker
75
+ end
76
+
77
+ def git(*args, path: nil)
78
+ [ :git, *([ "-C", path ] if path), *args.compact ]
79
+ end
80
+
81
+ def grep(*args)
82
+ args.compact.unshift :grep
83
+ end
84
+
85
+ def tags(**details)
86
+ Kamal::Tags.from_config(config, **details)
87
+ end
88
+
89
+ def ssh_proxy_args
90
+ case config.ssh.proxy
91
+ when Net::SSH::Proxy::Jump
92
+ " -J #{config.ssh.proxy.jump_proxies}"
93
+ when Net::SSH::Proxy::Command
94
+ " -o ProxyCommand='#{config.ssh.proxy.command_line_template}'"
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,111 @@
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
+ :cache_from, :cache_to, :ssh, :provenance, :driver, :docker_driver?,
10
+ to: :builder_config
11
+
12
+ def clean
13
+ docker :image, :rm, "--force", config.absolute_image
14
+ end
15
+
16
+ def push
17
+ docker :buildx, :build,
18
+ "--push",
19
+ *platform_options(arches),
20
+ *([ "--builder", builder_name ] unless docker_driver?),
21
+ *build_options,
22
+ build_context
23
+ end
24
+
25
+ def pull
26
+ docker :pull, config.absolute_image
27
+ end
28
+
29
+ def info
30
+ combine \
31
+ docker(:context, :ls),
32
+ docker(:buildx, :ls)
33
+ end
34
+
35
+ def inspect_builder
36
+ docker :buildx, :inspect, builder_name unless docker_driver?
37
+ end
38
+
39
+ def build_options
40
+ [ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance ]
41
+ end
42
+
43
+ def build_context
44
+ config.builder.context
45
+ end
46
+
47
+ def validate_image
48
+ pipe \
49
+ docker(:inspect, "-f", "'{{ .Config.Labels.service }}'", config.absolute_image),
50
+ any(
51
+ [ :grep, "-x", config.service ],
52
+ "(echo \"Image #{config.absolute_image} is missing the 'service' label\" && exit 1)"
53
+ )
54
+ end
55
+
56
+ def first_mirror
57
+ docker(:info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
58
+ end
59
+
60
+ private
61
+ def build_tags
62
+ [ "-t", config.absolute_image, "-t", config.latest_image ]
63
+ end
64
+
65
+ def build_cache
66
+ if cache_to && cache_from
67
+ [ "--cache-to", cache_to,
68
+ "--cache-from", cache_from ]
69
+ end
70
+ end
71
+
72
+ def build_labels
73
+ argumentize "--label", { service: config.service }
74
+ end
75
+
76
+ def build_args
77
+ argumentize "--build-arg", args, sensitive: true
78
+ end
79
+
80
+ def build_secrets
81
+ argumentize "--secret", secrets.keys.collect { |secret| [ "id", secret ] }
82
+ end
83
+
84
+ def build_dockerfile
85
+ if Pathname.new(File.expand_path(dockerfile)).exist?
86
+ argumentize "--file", dockerfile
87
+ else
88
+ raise BuilderError, "Missing #{dockerfile}"
89
+ end
90
+ end
91
+
92
+ def build_target
93
+ argumentize "--target", target if target.present?
94
+ end
95
+
96
+ def build_ssh
97
+ argumentize "--ssh", ssh if ssh.present?
98
+ end
99
+
100
+ def builder_provenance
101
+ argumentize "--provenance", provenance unless provenance.nil?
102
+ end
103
+
104
+ def builder_config
105
+ config.builder
106
+ end
107
+
108
+ def platform_options(arches)
109
+ argumentize "--platform", arches.map { |arch| "linux/#{arch}" }.join(",") if arches.any?
110
+ end
111
+ 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,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,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,56 @@
1
+ require "active_support/core_ext/string/filters"
2
+
3
+ class Kamal::Commands::Builder < Kamal::Commands::Base
4
+ delegate :create, :remove, :push, :clean, :pull, :info, :inspect_builder, :validate_image, :first_mirror, to: :target
5
+ delegate :local?, :remote?, 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
+ else
21
+ local
22
+ end
23
+ end
24
+
25
+ def remote
26
+ @remote ||= Kamal::Commands::Builder::Remote.new(config)
27
+ end
28
+
29
+ def local
30
+ @local ||= Kamal::Commands::Builder::Local.new(config)
31
+ end
32
+
33
+ def hybrid
34
+ @hybrid ||= Kamal::Commands::Builder::Hybrid.new(config)
35
+ end
36
+
37
+
38
+ def ensure_local_dependencies_installed
39
+ if name.native?
40
+ ensure_local_docker_installed
41
+ else
42
+ combine \
43
+ ensure_local_docker_installed,
44
+ ensure_local_buildx_installed
45
+ end
46
+ end
47
+
48
+ private
49
+ def ensure_local_docker_installed
50
+ docker "--version"
51
+ end
52
+
53
+ def ensure_local_buildx_installed
54
+ docker :buildx, "version"
55
+ end
56
+ end
@@ -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