nocoffee-kamal 2.3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|