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.
Files changed (142) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +13 -0
  4. data/bin/dash +18 -0
  5. data/bin/kamal +18 -0
  6. data/lib/kamal/cli/accessory.rb +342 -0
  7. data/lib/kamal/cli/alias/command.rb +10 -0
  8. data/lib/kamal/cli/app/assets.rb +24 -0
  9. data/lib/kamal/cli/app/boot.rb +126 -0
  10. data/lib/kamal/cli/app/error_pages.rb +33 -0
  11. data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
  12. data/lib/kamal/cli/app.rb +368 -0
  13. data/lib/kamal/cli/base.rb +324 -0
  14. data/lib/kamal/cli/build/clone.rb +59 -0
  15. data/lib/kamal/cli/build/port_forwarding.rb +66 -0
  16. data/lib/kamal/cli/build.rb +242 -0
  17. data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
  18. data/lib/kamal/cli/healthcheck/error.rb +2 -0
  19. data/lib/kamal/cli/healthcheck/poller.rb +42 -0
  20. data/lib/kamal/cli/lock.rb +34 -0
  21. data/lib/kamal/cli/main.rb +299 -0
  22. data/lib/kamal/cli/proxy.rb +419 -0
  23. data/lib/kamal/cli/prune.rb +34 -0
  24. data/lib/kamal/cli/registry.rb +49 -0
  25. data/lib/kamal/cli/secrets.rb +50 -0
  26. data/lib/kamal/cli/server.rb +70 -0
  27. data/lib/kamal/cli/templates/deploy.yml +102 -0
  28. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
  29. data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
  30. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
  31. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  32. data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
  33. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
  34. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
  35. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +122 -0
  36. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
  37. data/lib/kamal/cli/templates/secrets +22 -0
  38. data/lib/kamal/cli.rb +9 -0
  39. data/lib/kamal/commander/specifics.rb +62 -0
  40. data/lib/kamal/commander.rb +230 -0
  41. data/lib/kamal/commands/accessory/proxy.rb +16 -0
  42. data/lib/kamal/commands/accessory.rb +118 -0
  43. data/lib/kamal/commands/app/assets.rb +51 -0
  44. data/lib/kamal/commands/app/containers.rb +31 -0
  45. data/lib/kamal/commands/app/error_pages.rb +9 -0
  46. data/lib/kamal/commands/app/execution.rb +38 -0
  47. data/lib/kamal/commands/app/images.rb +13 -0
  48. data/lib/kamal/commands/app/logging.rb +28 -0
  49. data/lib/kamal/commands/app/proxy.rb +32 -0
  50. data/lib/kamal/commands/app.rb +125 -0
  51. data/lib/kamal/commands/auditor.rb +39 -0
  52. data/lib/kamal/commands/base.rb +147 -0
  53. data/lib/kamal/commands/builder/base.rb +143 -0
  54. data/lib/kamal/commands/builder/clone.rb +32 -0
  55. data/lib/kamal/commands/builder/cloud.rb +22 -0
  56. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  57. data/lib/kamal/commands/builder/local.rb +20 -0
  58. data/lib/kamal/commands/builder/pack.rb +46 -0
  59. data/lib/kamal/commands/builder/remote.rb +75 -0
  60. data/lib/kamal/commands/builder.rb +54 -0
  61. data/lib/kamal/commands/docker.rb +50 -0
  62. data/lib/kamal/commands/hook.rb +20 -0
  63. data/lib/kamal/commands/loadbalancer.rb +130 -0
  64. data/lib/kamal/commands/lock.rb +70 -0
  65. data/lib/kamal/commands/proxy.rb +150 -0
  66. data/lib/kamal/commands/prune.rb +38 -0
  67. data/lib/kamal/commands/registry.rb +38 -0
  68. data/lib/kamal/commands/server.rb +15 -0
  69. data/lib/kamal/commands.rb +2 -0
  70. data/lib/kamal/configuration/accessory.rb +280 -0
  71. data/lib/kamal/configuration/alias.rb +15 -0
  72. data/lib/kamal/configuration/boot.rb +29 -0
  73. data/lib/kamal/configuration/builder.rb +218 -0
  74. data/lib/kamal/configuration/docs/accessory.yml +160 -0
  75. data/lib/kamal/configuration/docs/alias.yml +29 -0
  76. data/lib/kamal/configuration/docs/boot.yml +21 -0
  77. data/lib/kamal/configuration/docs/builder.yml +132 -0
  78. data/lib/kamal/configuration/docs/configuration.yml +228 -0
  79. data/lib/kamal/configuration/docs/env.yml +118 -0
  80. data/lib/kamal/configuration/docs/logging.yml +21 -0
  81. data/lib/kamal/configuration/docs/output.yml +25 -0
  82. data/lib/kamal/configuration/docs/proxy.yml +207 -0
  83. data/lib/kamal/configuration/docs/registry.yml +64 -0
  84. data/lib/kamal/configuration/docs/role.yml +54 -0
  85. data/lib/kamal/configuration/docs/servers.yml +27 -0
  86. data/lib/kamal/configuration/docs/ssh.yml +81 -0
  87. data/lib/kamal/configuration/docs/sshkit.yml +31 -0
  88. data/lib/kamal/configuration/env/tag.rb +13 -0
  89. data/lib/kamal/configuration/env.rb +42 -0
  90. data/lib/kamal/configuration/loadbalancer.rb +34 -0
  91. data/lib/kamal/configuration/logging.rb +33 -0
  92. data/lib/kamal/configuration/output.rb +34 -0
  93. data/lib/kamal/configuration/proxy/boot.rb +124 -0
  94. data/lib/kamal/configuration/proxy/run.rb +152 -0
  95. data/lib/kamal/configuration/proxy.rb +156 -0
  96. data/lib/kamal/configuration/registry.rb +40 -0
  97. data/lib/kamal/configuration/role.rb +247 -0
  98. data/lib/kamal/configuration/servers.rb +25 -0
  99. data/lib/kamal/configuration/ssh.rb +76 -0
  100. data/lib/kamal/configuration/sshkit.rb +26 -0
  101. data/lib/kamal/configuration/validation.rb +27 -0
  102. data/lib/kamal/configuration/validator/accessory.rb +13 -0
  103. data/lib/kamal/configuration/validator/alias.rb +15 -0
  104. data/lib/kamal/configuration/validator/builder.rb +15 -0
  105. data/lib/kamal/configuration/validator/configuration.rb +6 -0
  106. data/lib/kamal/configuration/validator/env.rb +54 -0
  107. data/lib/kamal/configuration/validator/proxy.rb +47 -0
  108. data/lib/kamal/configuration/validator/registry.rb +27 -0
  109. data/lib/kamal/configuration/validator/role.rb +13 -0
  110. data/lib/kamal/configuration/validator/servers.rb +7 -0
  111. data/lib/kamal/configuration/validator.rb +251 -0
  112. data/lib/kamal/configuration/volume.rb +29 -0
  113. data/lib/kamal/configuration.rb +465 -0
  114. data/lib/kamal/docker.rb +30 -0
  115. data/lib/kamal/env_file.rb +44 -0
  116. data/lib/kamal/git.rb +37 -0
  117. data/lib/kamal/otel_shipper.rb +176 -0
  118. data/lib/kamal/output/base_logger.rb +29 -0
  119. data/lib/kamal/output/file_logger.rb +51 -0
  120. data/lib/kamal/output/formatter.rb +36 -0
  121. data/lib/kamal/output/otel_logger.rb +70 -0
  122. data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +59 -0
  123. data/lib/kamal/secrets/adapters/base.rb +33 -0
  124. data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
  125. data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
  126. data/lib/kamal/secrets/adapters/doppler.rb +57 -0
  127. data/lib/kamal/secrets/adapters/enpass.rb +71 -0
  128. data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
  129. data/lib/kamal/secrets/adapters/last_pass.rb +40 -0
  130. data/lib/kamal/secrets/adapters/one_password.rb +104 -0
  131. data/lib/kamal/secrets/adapters/passbolt.rb +129 -0
  132. data/lib/kamal/secrets/adapters/test.rb +16 -0
  133. data/lib/kamal/secrets/adapters.rb +16 -0
  134. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +47 -0
  135. data/lib/kamal/secrets.rb +53 -0
  136. data/lib/kamal/sshkit_with_ext.rb +273 -0
  137. data/lib/kamal/tags.rb +40 -0
  138. data/lib/kamal/utils/sensitive.rb +20 -0
  139. data/lib/kamal/utils.rb +110 -0
  140. data/lib/kamal/version.rb +3 -0
  141. data/lib/kamal.rb +15 -0
  142. 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