nocoffee-kamal 2.3.0.1
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +13 -0
- data/bin/kamal +18 -0
- data/lib/kamal/cli/accessory.rb +287 -0
- data/lib/kamal/cli/alias/command.rb +9 -0
- data/lib/kamal/cli/app/boot.rb +125 -0
- data/lib/kamal/cli/app/prepare_assets.rb +24 -0
- data/lib/kamal/cli/app.rb +335 -0
- data/lib/kamal/cli/base.rb +198 -0
- data/lib/kamal/cli/build/clone.rb +61 -0
- data/lib/kamal/cli/build.rb +162 -0
- data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
- data/lib/kamal/cli/healthcheck/error.rb +2 -0
- data/lib/kamal/cli/healthcheck/poller.rb +42 -0
- data/lib/kamal/cli/lock.rb +45 -0
- data/lib/kamal/cli/main.rb +279 -0
- data/lib/kamal/cli/proxy.rb +257 -0
- data/lib/kamal/cli/prune.rb +34 -0
- data/lib/kamal/cli/registry.rb +17 -0
- data/lib/kamal/cli/secrets.rb +43 -0
- data/lib/kamal/cli/server.rb +48 -0
- data/lib/kamal/cli/templates/deploy.yml +98 -0
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
- data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +109 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/secrets +17 -0
- data/lib/kamal/cli.rb +8 -0
- data/lib/kamal/commander/specifics.rb +54 -0
- data/lib/kamal/commander.rb +176 -0
- data/lib/kamal/commands/accessory.rb +113 -0
- data/lib/kamal/commands/app/assets.rb +51 -0
- data/lib/kamal/commands/app/containers.rb +31 -0
- data/lib/kamal/commands/app/execution.rb +30 -0
- data/lib/kamal/commands/app/images.rb +13 -0
- data/lib/kamal/commands/app/logging.rb +18 -0
- data/lib/kamal/commands/app/proxy.rb +16 -0
- data/lib/kamal/commands/app.rb +115 -0
- data/lib/kamal/commands/auditor.rb +33 -0
- data/lib/kamal/commands/base.rb +98 -0
- data/lib/kamal/commands/builder/base.rb +111 -0
- data/lib/kamal/commands/builder/clone.rb +31 -0
- data/lib/kamal/commands/builder/hybrid.rb +21 -0
- data/lib/kamal/commands/builder/local.rb +14 -0
- data/lib/kamal/commands/builder/remote.rb +63 -0
- data/lib/kamal/commands/builder.rb +56 -0
- data/lib/kamal/commands/docker.rb +34 -0
- data/lib/kamal/commands/hook.rb +20 -0
- data/lib/kamal/commands/lock.rb +70 -0
- data/lib/kamal/commands/proxy.rb +87 -0
- data/lib/kamal/commands/prune.rb +38 -0
- data/lib/kamal/commands/registry.rb +14 -0
- data/lib/kamal/commands/server.rb +15 -0
- data/lib/kamal/commands.rb +2 -0
- data/lib/kamal/configuration/accessory.rb +186 -0
- data/lib/kamal/configuration/alias.rb +15 -0
- data/lib/kamal/configuration/boot.rb +25 -0
- data/lib/kamal/configuration/builder.rb +191 -0
- data/lib/kamal/configuration/docs/accessory.yml +100 -0
- data/lib/kamal/configuration/docs/alias.yml +26 -0
- data/lib/kamal/configuration/docs/boot.yml +19 -0
- data/lib/kamal/configuration/docs/builder.yml +110 -0
- data/lib/kamal/configuration/docs/configuration.yml +178 -0
- data/lib/kamal/configuration/docs/env.yml +85 -0
- data/lib/kamal/configuration/docs/logging.yml +21 -0
- data/lib/kamal/configuration/docs/proxy.yml +110 -0
- data/lib/kamal/configuration/docs/registry.yml +52 -0
- data/lib/kamal/configuration/docs/role.yml +53 -0
- data/lib/kamal/configuration/docs/servers.yml +27 -0
- data/lib/kamal/configuration/docs/ssh.yml +70 -0
- data/lib/kamal/configuration/docs/sshkit.yml +23 -0
- data/lib/kamal/configuration/env/tag.rb +13 -0
- data/lib/kamal/configuration/env.rb +29 -0
- data/lib/kamal/configuration/logging.rb +33 -0
- data/lib/kamal/configuration/proxy.rb +63 -0
- data/lib/kamal/configuration/registry.rb +32 -0
- data/lib/kamal/configuration/role.rb +220 -0
- data/lib/kamal/configuration/servers.rb +18 -0
- data/lib/kamal/configuration/ssh.rb +57 -0
- data/lib/kamal/configuration/sshkit.rb +22 -0
- data/lib/kamal/configuration/validation.rb +27 -0
- data/lib/kamal/configuration/validator/accessory.rb +9 -0
- data/lib/kamal/configuration/validator/alias.rb +15 -0
- data/lib/kamal/configuration/validator/builder.rb +13 -0
- data/lib/kamal/configuration/validator/configuration.rb +6 -0
- data/lib/kamal/configuration/validator/env.rb +54 -0
- data/lib/kamal/configuration/validator/proxy.rb +15 -0
- data/lib/kamal/configuration/validator/registry.rb +25 -0
- data/lib/kamal/configuration/validator/role.rb +11 -0
- data/lib/kamal/configuration/validator/servers.rb +7 -0
- data/lib/kamal/configuration/validator.rb +171 -0
- data/lib/kamal/configuration/volume.rb +22 -0
- data/lib/kamal/configuration.rb +393 -0
- data/lib/kamal/env_file.rb +44 -0
- data/lib/kamal/git.rb +27 -0
- data/lib/kamal/secrets/adapters/base.rb +23 -0
- data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
- data/lib/kamal/secrets/adapters/last_pass.rb +39 -0
- data/lib/kamal/secrets/adapters/one_password.rb +70 -0
- data/lib/kamal/secrets/adapters/test.rb +14 -0
- data/lib/kamal/secrets/adapters.rb +14 -0
- data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +32 -0
- data/lib/kamal/secrets.rb +42 -0
- data/lib/kamal/sshkit_with_ext.rb +142 -0
- data/lib/kamal/tags.rb +40 -0
- data/lib/kamal/utils/sensitive.rb +20 -0
- data/lib/kamal/utils.rb +110 -0
- data/lib/kamal/version.rb +3 -0
- data/lib/kamal.rb +14 -0
- 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
|