dash 2.12.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +13 -0
- data/bin/dash +18 -0
- data/bin/kamal +18 -0
- data/lib/kamal/cli/accessory.rb +342 -0
- data/lib/kamal/cli/alias/command.rb +10 -0
- data/lib/kamal/cli/app/assets.rb +24 -0
- data/lib/kamal/cli/app/boot.rb +126 -0
- data/lib/kamal/cli/app/error_pages.rb +33 -0
- data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
- data/lib/kamal/cli/app.rb +368 -0
- data/lib/kamal/cli/base.rb +324 -0
- data/lib/kamal/cli/build/clone.rb +59 -0
- data/lib/kamal/cli/build/port_forwarding.rb +66 -0
- data/lib/kamal/cli/build.rb +242 -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 +34 -0
- data/lib/kamal/cli/main.rb +299 -0
- data/lib/kamal/cli/proxy.rb +419 -0
- data/lib/kamal/cli/prune.rb +34 -0
- data/lib/kamal/cli/registry.rb +49 -0
- data/lib/kamal/cli/secrets.rb +50 -0
- data/lib/kamal/cli/server.rb +70 -0
- data/lib/kamal/cli/templates/deploy.yml +102 -0
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/post-app-boot.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-app-boot.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 +122 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/secrets +22 -0
- data/lib/kamal/cli.rb +9 -0
- data/lib/kamal/commander/specifics.rb +62 -0
- data/lib/kamal/commander.rb +230 -0
- data/lib/kamal/commands/accessory/proxy.rb +16 -0
- data/lib/kamal/commands/accessory.rb +118 -0
- data/lib/kamal/commands/app/assets.rb +51 -0
- data/lib/kamal/commands/app/containers.rb +31 -0
- data/lib/kamal/commands/app/error_pages.rb +9 -0
- data/lib/kamal/commands/app/execution.rb +38 -0
- data/lib/kamal/commands/app/images.rb +13 -0
- data/lib/kamal/commands/app/logging.rb +28 -0
- data/lib/kamal/commands/app/proxy.rb +32 -0
- data/lib/kamal/commands/app.rb +125 -0
- data/lib/kamal/commands/auditor.rb +39 -0
- data/lib/kamal/commands/base.rb +147 -0
- data/lib/kamal/commands/builder/base.rb +143 -0
- data/lib/kamal/commands/builder/clone.rb +32 -0
- data/lib/kamal/commands/builder/cloud.rb +22 -0
- data/lib/kamal/commands/builder/hybrid.rb +21 -0
- data/lib/kamal/commands/builder/local.rb +20 -0
- data/lib/kamal/commands/builder/pack.rb +46 -0
- data/lib/kamal/commands/builder/remote.rb +75 -0
- data/lib/kamal/commands/builder.rb +54 -0
- data/lib/kamal/commands/docker.rb +50 -0
- data/lib/kamal/commands/hook.rb +20 -0
- data/lib/kamal/commands/loadbalancer.rb +130 -0
- data/lib/kamal/commands/lock.rb +70 -0
- data/lib/kamal/commands/proxy.rb +150 -0
- data/lib/kamal/commands/prune.rb +38 -0
- data/lib/kamal/commands/registry.rb +38 -0
- data/lib/kamal/commands/server.rb +15 -0
- data/lib/kamal/commands.rb +2 -0
- data/lib/kamal/configuration/accessory.rb +280 -0
- data/lib/kamal/configuration/alias.rb +15 -0
- data/lib/kamal/configuration/boot.rb +29 -0
- data/lib/kamal/configuration/builder.rb +218 -0
- data/lib/kamal/configuration/docs/accessory.yml +160 -0
- data/lib/kamal/configuration/docs/alias.yml +29 -0
- data/lib/kamal/configuration/docs/boot.yml +21 -0
- data/lib/kamal/configuration/docs/builder.yml +132 -0
- data/lib/kamal/configuration/docs/configuration.yml +228 -0
- data/lib/kamal/configuration/docs/env.yml +118 -0
- data/lib/kamal/configuration/docs/logging.yml +21 -0
- data/lib/kamal/configuration/docs/output.yml +25 -0
- data/lib/kamal/configuration/docs/proxy.yml +207 -0
- data/lib/kamal/configuration/docs/registry.yml +64 -0
- data/lib/kamal/configuration/docs/role.yml +54 -0
- data/lib/kamal/configuration/docs/servers.yml +27 -0
- data/lib/kamal/configuration/docs/ssh.yml +81 -0
- data/lib/kamal/configuration/docs/sshkit.yml +31 -0
- data/lib/kamal/configuration/env/tag.rb +13 -0
- data/lib/kamal/configuration/env.rb +42 -0
- data/lib/kamal/configuration/loadbalancer.rb +34 -0
- data/lib/kamal/configuration/logging.rb +33 -0
- data/lib/kamal/configuration/output.rb +34 -0
- data/lib/kamal/configuration/proxy/boot.rb +124 -0
- data/lib/kamal/configuration/proxy/run.rb +152 -0
- data/lib/kamal/configuration/proxy.rb +156 -0
- data/lib/kamal/configuration/registry.rb +40 -0
- data/lib/kamal/configuration/role.rb +247 -0
- data/lib/kamal/configuration/servers.rb +25 -0
- data/lib/kamal/configuration/ssh.rb +76 -0
- data/lib/kamal/configuration/sshkit.rb +26 -0
- data/lib/kamal/configuration/validation.rb +27 -0
- data/lib/kamal/configuration/validator/accessory.rb +13 -0
- data/lib/kamal/configuration/validator/alias.rb +15 -0
- data/lib/kamal/configuration/validator/builder.rb +15 -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 +47 -0
- data/lib/kamal/configuration/validator/registry.rb +27 -0
- data/lib/kamal/configuration/validator/role.rb +13 -0
- data/lib/kamal/configuration/validator/servers.rb +7 -0
- data/lib/kamal/configuration/validator.rb +251 -0
- data/lib/kamal/configuration/volume.rb +29 -0
- data/lib/kamal/configuration.rb +465 -0
- data/lib/kamal/docker.rb +30 -0
- data/lib/kamal/env_file.rb +44 -0
- data/lib/kamal/git.rb +37 -0
- data/lib/kamal/otel_shipper.rb +176 -0
- data/lib/kamal/output/base_logger.rb +29 -0
- data/lib/kamal/output/file_logger.rb +51 -0
- data/lib/kamal/output/formatter.rb +36 -0
- data/lib/kamal/output/otel_logger.rb +70 -0
- data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +59 -0
- data/lib/kamal/secrets/adapters/base.rb +33 -0
- data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
- data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
- data/lib/kamal/secrets/adapters/doppler.rb +57 -0
- data/lib/kamal/secrets/adapters/enpass.rb +71 -0
- data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
- data/lib/kamal/secrets/adapters/last_pass.rb +40 -0
- data/lib/kamal/secrets/adapters/one_password.rb +104 -0
- data/lib/kamal/secrets/adapters/passbolt.rb +129 -0
- data/lib/kamal/secrets/adapters/test.rb +16 -0
- data/lib/kamal/secrets/adapters.rb +16 -0
- data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +47 -0
- data/lib/kamal/secrets.rb +53 -0
- data/lib/kamal/sshkit_with_ext.rb +273 -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 +15 -0
- metadata +388 -0
|
@@ -0,0 +1,143 @@
|
|
|
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, no_cache: false)
|
|
18
|
+
docker :buildx, :build,
|
|
19
|
+
"--output=type=#{export_action}",
|
|
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
|
+
*([ "--no-cache" ] if no_cache),
|
|
25
|
+
build_context,
|
|
26
|
+
"2>&1"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def pull
|
|
30
|
+
docker :pull, config.absolute_image
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def info
|
|
34
|
+
combine \
|
|
35
|
+
docker(:context, :ls),
|
|
36
|
+
docker(:buildx, :ls)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def inspect_builder
|
|
40
|
+
docker :buildx, :inspect, builder_name unless docker_driver?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def build_options
|
|
44
|
+
[ *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance, *builder_sbom ]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def build_context
|
|
48
|
+
config.builder.context
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def validate_image
|
|
52
|
+
pipe \
|
|
53
|
+
docker(:inspect, "-f", "'{{ .Config.Labels.service }}'", config.absolute_image),
|
|
54
|
+
any(
|
|
55
|
+
[ :grep, "-x", config.service ],
|
|
56
|
+
"(echo \"Image #{config.absolute_image} is missing the 'service' label\" && exit 1)"
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def first_mirror
|
|
61
|
+
docker(:info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def login_to_registry_locally?
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def push_env
|
|
69
|
+
{}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
def build_tag_names(tag_as_dirty: false)
|
|
74
|
+
tag_names = [ config.absolute_image, config.latest_image ]
|
|
75
|
+
tag_names.map! { |t| "#{t}-dirty" } if tag_as_dirty
|
|
76
|
+
tag_names
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def build_tag_options(tag_as_dirty: false)
|
|
80
|
+
build_tag_names(tag_as_dirty: tag_as_dirty).flat_map { |name| [ "-t", name ] }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def build_cache
|
|
84
|
+
if cache_to && cache_from
|
|
85
|
+
[ "--cache-to", cache_to,
|
|
86
|
+
"--cache-from", cache_from ]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def build_labels
|
|
91
|
+
argumentize "--label", { service: config.service }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def build_args
|
|
95
|
+
argumentize "--build-arg", args, sensitive: true
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def build_secrets
|
|
99
|
+
argumentize "--secret", secrets.keys.collect { |secret| [ "id", secret ] }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def build_dockerfile
|
|
103
|
+
if Pathname.new(File.expand_path(dockerfile)).exist?
|
|
104
|
+
argumentize "--file", dockerfile
|
|
105
|
+
else
|
|
106
|
+
raise BuilderError, "Missing #{dockerfile}"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def build_target
|
|
111
|
+
argumentize "--target", target if target.present?
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def build_ssh
|
|
115
|
+
argumentize "--ssh", ssh if ssh.present?
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def builder_provenance
|
|
119
|
+
argumentize "--provenance", provenance unless provenance.nil?
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def builder_sbom
|
|
123
|
+
argumentize "--sbom", sbom unless sbom.nil?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def builder_config
|
|
127
|
+
config.builder
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def registry_config
|
|
131
|
+
config.registry
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def driver_options
|
|
135
|
+
if registry_config.local?
|
|
136
|
+
[ "--driver-opt", "network=host" ]
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def platform_options(arches)
|
|
141
|
+
argumentize "--platform", arches.map { |arch| "linux/#{arch}" }.join(",") if arches.any?
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
git(:gc, "--auto", "--quiet", path: escaped_build_directory)
|
|
14
|
+
]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def clone_status
|
|
18
|
+
git :status, "--porcelain", path: escaped_build_directory
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def clone_revision
|
|
22
|
+
git :"rev-parse", :HEAD, path: escaped_build_directory
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def escaped_root
|
|
26
|
+
Kamal::Git.root.shellescape
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def escaped_build_directory
|
|
30
|
+
config.builder.build_directory.shellescape
|
|
31
|
+
end
|
|
32
|
+
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_builder_name_suffix}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_local_buildx
|
|
15
|
+
docker :buildx, :create, *platform_options(local_arches), "--name", builder_name, "--driver=#{driver}", *driver_options
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def append_remote_buildx
|
|
19
|
+
docker :buildx, :create, *platform_options(remote_arches), "--append", "--name", builder_name, *driver_options, remote_context_name
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class Kamal::Commands::Builder::Local < Kamal::Commands::Builder::Base
|
|
2
|
+
def create
|
|
3
|
+
return if docker_driver?
|
|
4
|
+
|
|
5
|
+
docker :buildx, :create, "--name", builder_name, "--driver=#{driver}", *driver_options
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def remove
|
|
9
|
+
docker :buildx, :rm, builder_name unless docker_driver?
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
def builder_name
|
|
14
|
+
if registry_config.local?
|
|
15
|
+
"kamal-local-registry-#{driver}"
|
|
16
|
+
else
|
|
17
|
+
"kamal-local-#{driver}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
class Kamal::Commands::Builder::Pack < Kamal::Commands::Builder::Base
|
|
2
|
+
def push(export_action = "registry", tag_as_dirty: false, no_cache: false)
|
|
3
|
+
combine \
|
|
4
|
+
build(tag_as_dirty: tag_as_dirty, no_cache: no_cache),
|
|
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(tag_as_dirty: false, no_cache: false)
|
|
17
|
+
pack(:build,
|
|
18
|
+
config.repository,
|
|
19
|
+
"--platform", platform,
|
|
20
|
+
"--creation-time", "now",
|
|
21
|
+
"--builder", pack_builder,
|
|
22
|
+
buildpacks,
|
|
23
|
+
*build_tag_options(tag_as_dirty: tag_as_dirty),
|
|
24
|
+
*([ "--clear-cache" ] if no_cache),
|
|
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,75 @@
|
|
|
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
|
+
def login_to_registry_locally?
|
|
28
|
+
false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def push_env
|
|
32
|
+
{ "BUILDKIT_NO_CLIENT_TOKEN" => "1" }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
def builder_name
|
|
37
|
+
"kamal-remote-#{remote_builder_name_suffix}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def remote_context_name
|
|
41
|
+
"#{builder_name}-context"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def remote_builder_name_suffix
|
|
45
|
+
"#{remote.gsub(/[^a-z0-9_-]/, "-")}#{registry_config.local? ? "-local-registry" : "" }"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def inspect_buildx
|
|
49
|
+
pipe \
|
|
50
|
+
docker(:buildx, :inspect, builder_name),
|
|
51
|
+
grep("-q", "Endpoint:.*#{remote_context_name}")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def inspect_remote_context
|
|
55
|
+
pipe \
|
|
56
|
+
docker(:context, :inspect, remote_context_name, "--format", ENDPOINT_DOCKER_HOST_INSPECT),
|
|
57
|
+
grep("-xq", remote)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def create_remote_context
|
|
61
|
+
docker :context, :create, remote_context_name, "--description", "'#{builder_name} host'", "--docker", "'host=#{remote}'"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def remove_remote_context
|
|
65
|
+
docker :context, :rm, remote_context_name
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def create_buildx
|
|
69
|
+
docker :buildx, :create, "--name", builder_name, *driver_options, remote_context_name
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def remove_buildx
|
|
73
|
+
docker :buildx, :rm, builder_name
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require "active_support/core_ext/string/filters"
|
|
2
|
+
|
|
3
|
+
class Kamal::Commands::Builder < Kamal::Commands::Base
|
|
4
|
+
delegate \
|
|
5
|
+
:create, :remove, :dev, :push, :clean, :pull, :info, :inspect_builder,
|
|
6
|
+
:validate_image, :first_mirror, :login_to_registry_locally?, :push_env,
|
|
7
|
+
to: :target
|
|
8
|
+
|
|
9
|
+
delegate \
|
|
10
|
+
:local?, :remote?, :pack?, :cloud?,
|
|
11
|
+
to: "config.builder"
|
|
12
|
+
|
|
13
|
+
include Clone
|
|
14
|
+
|
|
15
|
+
def name
|
|
16
|
+
target.class.to_s.remove("Kamal::Commands::Builder::").underscore.inquiry
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def target
|
|
20
|
+
if remote?
|
|
21
|
+
if local?
|
|
22
|
+
hybrid
|
|
23
|
+
else
|
|
24
|
+
remote
|
|
25
|
+
end
|
|
26
|
+
elsif pack?
|
|
27
|
+
pack
|
|
28
|
+
elsif cloud?
|
|
29
|
+
cloud
|
|
30
|
+
else
|
|
31
|
+
local
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def remote
|
|
36
|
+
@remote ||= Kamal::Commands::Builder::Remote.new(config)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def local
|
|
40
|
+
@local ||= Kamal::Commands::Builder::Local.new(config)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def hybrid
|
|
44
|
+
@hybrid ||= Kamal::Commands::Builder::Hybrid.new(config)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def pack
|
|
48
|
+
@pack ||= Kamal::Commands::Builder::Pack.new(config)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def cloud
|
|
52
|
+
@cloud ||= Kamal::Commands::Builder::Cloud.new(config)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
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 ] || sudo -nl usermod >/dev/null' ]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def root?
|
|
23
|
+
[ '[ "${EUID:-$(id -u)}" -eq 0 ]' ]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def in_docker_group?
|
|
27
|
+
[ 'id -nG "${USER:-$(id -un)}" | grep -qw docker' ]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def add_to_docker_group
|
|
31
|
+
[ 'sudo -n usermod -aG docker "${USER:-$(id -un)}"' ]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def refresh_session
|
|
35
|
+
[ "kill -HUP $PPID" ]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def create_network
|
|
39
|
+
docker :network, :create, :kamal
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
def get_docker
|
|
44
|
+
shell \
|
|
45
|
+
any \
|
|
46
|
+
[ :curl, "-fsSL", "https://get.docker.com" ],
|
|
47
|
+
[ :wget, "-O -", "https://get.docker.com" ],
|
|
48
|
+
[ :echo, "\"exit 1\"" ]
|
|
49
|
+
end
|
|
50
|
+
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,130 @@
|
|
|
1
|
+
class Kamal::Commands::Loadbalancer < Kamal::Commands::Base
|
|
2
|
+
delegate :argumentize, :optionize, to: Kamal::Utils
|
|
3
|
+
|
|
4
|
+
attr_reader :loadbalancer_config
|
|
5
|
+
|
|
6
|
+
def initialize(config, loadbalancer_config: nil)
|
|
7
|
+
super(config)
|
|
8
|
+
@loadbalancer_config = loadbalancer_config
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def run
|
|
12
|
+
pipe \
|
|
13
|
+
[ :echo, proxy_image ],
|
|
14
|
+
xargs(docker(:run,
|
|
15
|
+
"--name", container_name,
|
|
16
|
+
"--network", "kamal",
|
|
17
|
+
"--detach",
|
|
18
|
+
"--restart", "unless-stopped",
|
|
19
|
+
"--publish", "80:80",
|
|
20
|
+
"--publish", "443:443",
|
|
21
|
+
"--label", label,
|
|
22
|
+
*volume_mounts))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def start
|
|
26
|
+
docker :container, :start, container_name
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def stop(name: container_name)
|
|
30
|
+
docker :container, :stop, name
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def start_or_run
|
|
34
|
+
combine start, run, by: "||"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def deploy(targets: [])
|
|
38
|
+
target_args = targets.map { |t| "#{t}:80" }
|
|
39
|
+
|
|
40
|
+
hosts = loadbalancer_config.hosts
|
|
41
|
+
|
|
42
|
+
options = []
|
|
43
|
+
options << "--target=#{target_args.join(',')}"
|
|
44
|
+
options << "--host=#{hosts.join(',')}"
|
|
45
|
+
options << "--tls" if loadbalancer_config.ssl?
|
|
46
|
+
|
|
47
|
+
docker :exec, container_name, "kamal-proxy", "deploy", loadbalancer_config.config.service, *options
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def info
|
|
51
|
+
docker :ps, "--filter", "'name=^#{container_name}$'"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def version
|
|
55
|
+
pipe \
|
|
56
|
+
docker(:inspect, container_name, "--format '{{.Config.Image}}'"),
|
|
57
|
+
[ :cut, "-d:", "-f2" ]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
|
|
61
|
+
pipe \
|
|
62
|
+
docker(:logs, container_name, ("--since #{since}" if since), ("--tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
|
|
63
|
+
("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def follow_logs(host:, timestamps: true, grep: nil, grep_options: nil)
|
|
67
|
+
run_over_ssh pipe(
|
|
68
|
+
docker(:logs, container_name, ("--timestamps" if timestamps), "--tail", "10", "--follow", "2>&1"),
|
|
69
|
+
(%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
|
|
70
|
+
).join(" "), host: host
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def remove_container
|
|
74
|
+
docker :container, :prune, "--force", "--filter", "label=org.opencontainers.image.title=kamal-loadbalancer"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def remove_image
|
|
78
|
+
docker :image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=kamal-loadbalancer"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def ensure_directory
|
|
82
|
+
make_directory loadbalancer_config.directory
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def ensure_apps_config_directory
|
|
86
|
+
make_directory config.proxy_boot.apps_directory
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def remove_directory
|
|
90
|
+
super(loadbalancer_config.directory)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def container_name
|
|
94
|
+
loadbalancer_config.container_name
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
def proxy_image
|
|
99
|
+
[
|
|
100
|
+
loadbalancer_config.config.proxy_boot.image_default,
|
|
101
|
+
Kamal::Configuration::Proxy::Run::MINIMUM_VERSION
|
|
102
|
+
].join(":")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def on_proxy_host?
|
|
106
|
+
loadbalancer_config.on_proxy_host?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def label
|
|
110
|
+
if on_proxy_host?
|
|
111
|
+
"org.opencontainers.image.title=kamal-proxy"
|
|
112
|
+
else
|
|
113
|
+
"org.opencontainers.image.title=kamal-loadbalancer"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def volume_mounts
|
|
118
|
+
if on_proxy_host?
|
|
119
|
+
# When on a proxy host, use same volume mounts as proxy for app deployments
|
|
120
|
+
[
|
|
121
|
+
"--volume", "kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy",
|
|
122
|
+
"--volume", "$PWD/#{config.proxy_boot.apps_directory}:/home/kamal-proxy/.apps-config"
|
|
123
|
+
]
|
|
124
|
+
else
|
|
125
|
+
[
|
|
126
|
+
"--volume", "kamal-loadbalancer-config:/home/kamal-loadbalancer/.config/kamal-loadbalancer"
|
|
127
|
+
]
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
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
|