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.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +13 -0
  4. data/bin/kamal +18 -0
  5. data/lib/kamal/cli/accessory.rb +287 -0
  6. data/lib/kamal/cli/alias/command.rb +9 -0
  7. data/lib/kamal/cli/app/boot.rb +125 -0
  8. data/lib/kamal/cli/app/prepare_assets.rb +24 -0
  9. data/lib/kamal/cli/app.rb +335 -0
  10. data/lib/kamal/cli/base.rb +198 -0
  11. data/lib/kamal/cli/build/clone.rb +61 -0
  12. data/lib/kamal/cli/build.rb +162 -0
  13. data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
  14. data/lib/kamal/cli/healthcheck/error.rb +2 -0
  15. data/lib/kamal/cli/healthcheck/poller.rb +42 -0
  16. data/lib/kamal/cli/lock.rb +45 -0
  17. data/lib/kamal/cli/main.rb +279 -0
  18. data/lib/kamal/cli/proxy.rb +257 -0
  19. data/lib/kamal/cli/prune.rb +34 -0
  20. data/lib/kamal/cli/registry.rb +17 -0
  21. data/lib/kamal/cli/secrets.rb +43 -0
  22. data/lib/kamal/cli/server.rb +48 -0
  23. data/lib/kamal/cli/templates/deploy.yml +98 -0
  24. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
  25. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
  26. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  27. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
  28. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
  29. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +109 -0
  30. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
  31. data/lib/kamal/cli/templates/secrets +17 -0
  32. data/lib/kamal/cli.rb +8 -0
  33. data/lib/kamal/commander/specifics.rb +54 -0
  34. data/lib/kamal/commander.rb +176 -0
  35. data/lib/kamal/commands/accessory.rb +113 -0
  36. data/lib/kamal/commands/app/assets.rb +51 -0
  37. data/lib/kamal/commands/app/containers.rb +31 -0
  38. data/lib/kamal/commands/app/execution.rb +30 -0
  39. data/lib/kamal/commands/app/images.rb +13 -0
  40. data/lib/kamal/commands/app/logging.rb +18 -0
  41. data/lib/kamal/commands/app/proxy.rb +16 -0
  42. data/lib/kamal/commands/app.rb +115 -0
  43. data/lib/kamal/commands/auditor.rb +33 -0
  44. data/lib/kamal/commands/base.rb +98 -0
  45. data/lib/kamal/commands/builder/base.rb +111 -0
  46. data/lib/kamal/commands/builder/clone.rb +31 -0
  47. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  48. data/lib/kamal/commands/builder/local.rb +14 -0
  49. data/lib/kamal/commands/builder/remote.rb +63 -0
  50. data/lib/kamal/commands/builder.rb +56 -0
  51. data/lib/kamal/commands/docker.rb +34 -0
  52. data/lib/kamal/commands/hook.rb +20 -0
  53. data/lib/kamal/commands/lock.rb +70 -0
  54. data/lib/kamal/commands/proxy.rb +87 -0
  55. data/lib/kamal/commands/prune.rb +38 -0
  56. data/lib/kamal/commands/registry.rb +14 -0
  57. data/lib/kamal/commands/server.rb +15 -0
  58. data/lib/kamal/commands.rb +2 -0
  59. data/lib/kamal/configuration/accessory.rb +186 -0
  60. data/lib/kamal/configuration/alias.rb +15 -0
  61. data/lib/kamal/configuration/boot.rb +25 -0
  62. data/lib/kamal/configuration/builder.rb +191 -0
  63. data/lib/kamal/configuration/docs/accessory.yml +100 -0
  64. data/lib/kamal/configuration/docs/alias.yml +26 -0
  65. data/lib/kamal/configuration/docs/boot.yml +19 -0
  66. data/lib/kamal/configuration/docs/builder.yml +110 -0
  67. data/lib/kamal/configuration/docs/configuration.yml +178 -0
  68. data/lib/kamal/configuration/docs/env.yml +85 -0
  69. data/lib/kamal/configuration/docs/logging.yml +21 -0
  70. data/lib/kamal/configuration/docs/proxy.yml +110 -0
  71. data/lib/kamal/configuration/docs/registry.yml +52 -0
  72. data/lib/kamal/configuration/docs/role.yml +53 -0
  73. data/lib/kamal/configuration/docs/servers.yml +27 -0
  74. data/lib/kamal/configuration/docs/ssh.yml +70 -0
  75. data/lib/kamal/configuration/docs/sshkit.yml +23 -0
  76. data/lib/kamal/configuration/env/tag.rb +13 -0
  77. data/lib/kamal/configuration/env.rb +29 -0
  78. data/lib/kamal/configuration/logging.rb +33 -0
  79. data/lib/kamal/configuration/proxy.rb +63 -0
  80. data/lib/kamal/configuration/registry.rb +32 -0
  81. data/lib/kamal/configuration/role.rb +220 -0
  82. data/lib/kamal/configuration/servers.rb +18 -0
  83. data/lib/kamal/configuration/ssh.rb +57 -0
  84. data/lib/kamal/configuration/sshkit.rb +22 -0
  85. data/lib/kamal/configuration/validation.rb +27 -0
  86. data/lib/kamal/configuration/validator/accessory.rb +9 -0
  87. data/lib/kamal/configuration/validator/alias.rb +15 -0
  88. data/lib/kamal/configuration/validator/builder.rb +13 -0
  89. data/lib/kamal/configuration/validator/configuration.rb +6 -0
  90. data/lib/kamal/configuration/validator/env.rb +54 -0
  91. data/lib/kamal/configuration/validator/proxy.rb +15 -0
  92. data/lib/kamal/configuration/validator/registry.rb +25 -0
  93. data/lib/kamal/configuration/validator/role.rb +11 -0
  94. data/lib/kamal/configuration/validator/servers.rb +7 -0
  95. data/lib/kamal/configuration/validator.rb +171 -0
  96. data/lib/kamal/configuration/volume.rb +22 -0
  97. data/lib/kamal/configuration.rb +393 -0
  98. data/lib/kamal/env_file.rb +44 -0
  99. data/lib/kamal/git.rb +27 -0
  100. data/lib/kamal/secrets/adapters/base.rb +23 -0
  101. data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
  102. data/lib/kamal/secrets/adapters/last_pass.rb +39 -0
  103. data/lib/kamal/secrets/adapters/one_password.rb +70 -0
  104. data/lib/kamal/secrets/adapters/test.rb +14 -0
  105. data/lib/kamal/secrets/adapters.rb +14 -0
  106. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +32 -0
  107. data/lib/kamal/secrets.rb +42 -0
  108. data/lib/kamal/sshkit_with_ext.rb +142 -0
  109. data/lib/kamal/tags.rb +40 -0
  110. data/lib/kamal/utils/sensitive.rb +20 -0
  111. data/lib/kamal/utils.rb +110 -0
  112. data/lib/kamal/version.rb +3 -0
  113. data/lib/kamal.rb +14 -0
  114. metadata +349 -0
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # A sample pre-deploy hook
4
+ #
5
+ # Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds.
6
+ #
7
+ # Fails unless the combined status is "success"
8
+ #
9
+ # These environment variables are available:
10
+ # KAMAL_RECORDED_AT
11
+ # KAMAL_PERFORMER
12
+ # KAMAL_VERSION
13
+ # KAMAL_HOSTS
14
+ # KAMAL_COMMAND
15
+ # KAMAL_SUBCOMMAND
16
+ # KAMAL_ROLE (if set)
17
+ # KAMAL_DESTINATION (if set)
18
+
19
+ # Only check the build status for production deployments
20
+ if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production"
21
+ exit 0
22
+ end
23
+
24
+ require "bundler/inline"
25
+
26
+ # true = install gems so this is fast on repeat invocations
27
+ gemfile(true, quiet: true) do
28
+ source "https://rubygems.org"
29
+
30
+ gem "octokit"
31
+ gem "faraday-retry"
32
+ end
33
+
34
+ MAX_ATTEMPTS = 72
35
+ ATTEMPTS_GAP = 10
36
+
37
+ def exit_with_error(message)
38
+ $stderr.puts message
39
+ exit 1
40
+ end
41
+
42
+ class GithubStatusChecks
43
+ attr_reader :remote_url, :git_sha, :github_client, :combined_status
44
+
45
+ def initialize
46
+ @remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/")
47
+ @git_sha = `git rev-parse HEAD`.strip
48
+ @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
49
+ refresh!
50
+ end
51
+
52
+ def refresh!
53
+ @combined_status = github_client.combined_status(remote_url, git_sha)
54
+ end
55
+
56
+ def state
57
+ combined_status[:state]
58
+ end
59
+
60
+ def first_status_url
61
+ first_status = combined_status[:statuses].find { |status| status[:state] == state }
62
+ first_status && first_status[:target_url]
63
+ end
64
+
65
+ def complete_count
66
+ combined_status[:statuses].count { |status| status[:state] != "pending"}
67
+ end
68
+
69
+ def total_count
70
+ combined_status[:statuses].count
71
+ end
72
+
73
+ def current_status
74
+ if total_count > 0
75
+ "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..."
76
+ else
77
+ "Build not started..."
78
+ end
79
+ end
80
+ end
81
+
82
+
83
+ $stdout.sync = true
84
+
85
+ puts "Checking build status..."
86
+ attempts = 0
87
+ checks = GithubStatusChecks.new
88
+
89
+ begin
90
+ loop do
91
+ case checks.state
92
+ when "success"
93
+ puts "Checks passed, see #{checks.first_status_url}"
94
+ exit 0
95
+ when "failure"
96
+ exit_with_error "Checks failed, see #{checks.first_status_url}"
97
+ when "pending"
98
+ attempts += 1
99
+ end
100
+
101
+ exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS
102
+
103
+ puts checks.current_status
104
+ sleep(ATTEMPTS_GAP)
105
+ checks.refresh!
106
+ end
107
+ rescue Octokit::NotFound
108
+ exit_with_error "Build status could not be found"
109
+ end
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ echo "Rebooting kamal-proxy on $KAMAL_HOSTS..."
@@ -0,0 +1,17 @@
1
+ # Secrets defined here are available for reference under registry/password, env/secret, builder/secrets,
2
+ # and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either
3
+ # password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git.
4
+
5
+ # Option 1: Read secrets from the environment
6
+ KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
7
+
8
+ # Option 2: Read secrets via a command
9
+ # RAILS_MASTER_KEY=$(cat config/master.key)
10
+
11
+ # Option 3: Read secrets via kamal secrets helpers
12
+ # These will handle logging in and fetching the secrets in as few calls as possible
13
+ # There are adapters for 1Password, LastPass + Bitwarden
14
+ #
15
+ # SECRETS=$(kamal secrets fetch --adapter 1password --account my-account --from MyVault/MyItem KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY)
16
+ # KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD $SECRETS)
17
+ # RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY $SECRETS)
data/lib/kamal/cli.rb ADDED
@@ -0,0 +1,8 @@
1
+ module Kamal::Cli
2
+ class BootError < StandardError; end
3
+ class HookError < StandardError; end
4
+ class LockError < StandardError; end
5
+ end
6
+
7
+ # SSHKit uses instance eval, so we need a global const for ergonomics
8
+ KAMAL = Kamal::Commander.new
@@ -0,0 +1,54 @@
1
+ class Kamal::Commander::Specifics
2
+ attr_reader :primary_host, :primary_role, :hosts, :roles
3
+ delegate :stable_sort!, to: Kamal::Utils
4
+
5
+ def initialize(config, specific_hosts, specific_roles)
6
+ @config, @specific_hosts, @specific_roles = config, specific_hosts, specific_roles
7
+
8
+ @roles, @hosts = specified_roles, specified_hosts
9
+
10
+ @primary_host = specific_hosts&.first || primary_specific_role&.primary_host || config.primary_host
11
+ @primary_role = primary_or_first_role(roles_on(primary_host))
12
+
13
+ stable_sort!(roles) { |role| role == primary_role ? 0 : 1 }
14
+ stable_sort!(hosts) { |host| roles_on(host).any? { |role| role == primary_role } ? 0 : 1 }
15
+ end
16
+
17
+ def roles_on(host)
18
+ roles.select { |role| role.hosts.include?(host.to_s) }
19
+ end
20
+
21
+ def proxy_hosts
22
+ config.proxy_hosts & specified_hosts
23
+ end
24
+
25
+ def accessory_hosts
26
+ config.accessories.flat_map(&:hosts) & specified_hosts
27
+ end
28
+
29
+ private
30
+ attr_reader :config, :specific_hosts, :specific_roles
31
+
32
+ def primary_specific_role
33
+ primary_or_first_role(specific_roles) if specific_roles.present?
34
+ end
35
+
36
+ def primary_or_first_role(roles)
37
+ roles.detect { |role| role == config.primary_role } || roles.first
38
+ end
39
+
40
+ def specified_roles
41
+ (specific_roles || config.roles) \
42
+ .select { |role| ((specific_hosts || config.all_hosts) & role.hosts).any? }
43
+ end
44
+
45
+ def specified_hosts
46
+ specified_hosts = specific_hosts || config.all_hosts
47
+
48
+ if (specific_role_hosts = specific_roles&.flat_map(&:hosts)).present?
49
+ specified_hosts.select { |host| specific_role_hosts.include?(host) }
50
+ else
51
+ specified_hosts
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,176 @@
1
+ require "active_support/core_ext/enumerable"
2
+ require "active_support/core_ext/module/delegation"
3
+ require "active_support/core_ext/object/blank"
4
+
5
+ class Kamal::Commander
6
+ attr_accessor :verbosity, :holding_lock, :connected
7
+ delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :proxy_hosts, :accessory_hosts, to: :specifics
8
+
9
+ def initialize
10
+ self.verbosity = :info
11
+ self.holding_lock = false
12
+ self.connected = false
13
+ @specifics = nil
14
+ end
15
+
16
+ def config
17
+ @config ||= Kamal::Configuration.create_from(**@config_kwargs).tap do |config|
18
+ @config_kwargs = nil
19
+ configure_sshkit_with(config)
20
+ end
21
+ end
22
+
23
+ def configure(**kwargs)
24
+ @config, @config_kwargs = nil, kwargs
25
+ end
26
+
27
+ def configured?
28
+ @config || @config_kwargs
29
+ end
30
+
31
+ attr_reader :specific_roles, :specific_hosts
32
+
33
+ def specific_primary!
34
+ @specifics = nil
35
+ if specific_roles.present?
36
+ self.specific_hosts = [ specific_roles.first.primary_host ]
37
+ else
38
+ self.specific_hosts = [ config.primary_host ]
39
+ end
40
+ end
41
+
42
+ def specific_roles=(role_names)
43
+ @specifics = nil
44
+ if role_names.present?
45
+ @specific_roles = Kamal::Utils.filter_specific_items(role_names, config.roles)
46
+
47
+ if @specific_roles.empty?
48
+ raise ArgumentError, "No --roles match for #{role_names.join(',')}"
49
+ end
50
+
51
+ @specific_roles
52
+ end
53
+ end
54
+
55
+ def specific_hosts=(hosts)
56
+ @specifics = nil
57
+ if hosts.present?
58
+ @specific_hosts = Kamal::Utils.filter_specific_items(hosts, config.all_hosts)
59
+
60
+ if @specific_hosts.empty?
61
+ raise ArgumentError, "No --hosts match for #{hosts.join(',')}"
62
+ end
63
+
64
+ @specific_hosts
65
+ end
66
+ end
67
+
68
+ def with_specific_hosts(hosts)
69
+ original_hosts, self.specific_hosts = specific_hosts, hosts
70
+ yield
71
+ ensure
72
+ self.specific_hosts = original_hosts
73
+ end
74
+
75
+ def accessory_names
76
+ config.accessories&.collect(&:name) || []
77
+ end
78
+
79
+ def accessories_on(host)
80
+ config.accessories.select { |accessory| accessory.hosts.include?(host.to_s) }.map(&:name)
81
+ end
82
+
83
+
84
+ def app(role: nil, host: nil)
85
+ Kamal::Commands::App.new(config, role: role, host: host)
86
+ end
87
+
88
+ def accessory(name)
89
+ Kamal::Commands::Accessory.new(config, name: name)
90
+ end
91
+
92
+ def auditor(**details)
93
+ Kamal::Commands::Auditor.new(config, **details)
94
+ end
95
+
96
+ def builder
97
+ @builder ||= Kamal::Commands::Builder.new(config)
98
+ end
99
+
100
+ def docker
101
+ @docker ||= Kamal::Commands::Docker.new(config)
102
+ end
103
+
104
+ def hook
105
+ @hook ||= Kamal::Commands::Hook.new(config)
106
+ end
107
+
108
+ def lock
109
+ @lock ||= Kamal::Commands::Lock.new(config)
110
+ end
111
+
112
+ def proxy
113
+ @proxy ||= Kamal::Commands::Proxy.new(config)
114
+ end
115
+
116
+ def prune
117
+ @prune ||= Kamal::Commands::Prune.new(config)
118
+ end
119
+
120
+ def registry
121
+ @registry ||= Kamal::Commands::Registry.new(config)
122
+ end
123
+
124
+ def server
125
+ @server ||= Kamal::Commands::Server.new(config)
126
+ end
127
+
128
+ def alias(name)
129
+ config.aliases[name]
130
+ end
131
+
132
+
133
+ def with_verbosity(level)
134
+ old_level = self.verbosity
135
+
136
+ self.verbosity = level
137
+ SSHKit.config.output_verbosity = level
138
+
139
+ yield
140
+ ensure
141
+ self.verbosity = old_level
142
+ SSHKit.config.output_verbosity = old_level
143
+ end
144
+
145
+ def boot_strategy
146
+ if config.boot.limit.present?
147
+ { in: :groups, limit: config.boot.limit, wait: config.boot.wait }
148
+ else
149
+ {}
150
+ end
151
+ end
152
+
153
+ def holding_lock?
154
+ self.holding_lock
155
+ end
156
+
157
+ def connected?
158
+ self.connected
159
+ end
160
+
161
+ private
162
+ # Lazy setup of SSHKit
163
+ def configure_sshkit_with(config)
164
+ SSHKit::Backend::Netssh.pool.idle_timeout = config.sshkit.pool_idle_timeout
165
+ SSHKit::Backend::Netssh.configure do |sshkit|
166
+ sshkit.max_concurrent_starts = config.sshkit.max_concurrent_starts
167
+ sshkit.ssh_options = config.ssh.options
168
+ end
169
+ SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs
170
+ SSHKit.config.output_verbosity = verbosity
171
+ end
172
+
173
+ def specifics
174
+ @specifics ||= Kamal::Commander::Specifics.new(config, specific_hosts, specific_roles)
175
+ end
176
+ end
@@ -0,0 +1,113 @@
1
+ class Kamal::Commands::Accessory < Kamal::Commands::Base
2
+ attr_reader :accessory_config
3
+ delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd,
4
+ :network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args,
5
+ :secrets_io, :secrets_path, :env_directory,
6
+ to: :accessory_config
7
+
8
+ def initialize(config, name:)
9
+ super(config)
10
+ @accessory_config = config.accessory(name)
11
+ end
12
+
13
+ def run
14
+ docker :run,
15
+ "--name", service_name,
16
+ "--detach",
17
+ "--restart", "unless-stopped",
18
+ *network_args,
19
+ *config.logging_args,
20
+ *publish_args,
21
+ *env_args,
22
+ *volume_args,
23
+ *label_args,
24
+ *option_args,
25
+ image,
26
+ cmd
27
+ end
28
+
29
+ def start
30
+ docker :container, :start, service_name
31
+ end
32
+
33
+ def stop
34
+ docker :container, :stop, service_name
35
+ end
36
+
37
+ def info
38
+ docker :ps, *service_filter
39
+ end
40
+
41
+
42
+ def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
43
+ pipe \
44
+ docker(:logs, service_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
45
+ ("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
46
+ end
47
+
48
+ def follow_logs(timestamps: true, grep: nil, grep_options: nil)
49
+ run_over_ssh \
50
+ pipe \
51
+ docker(:logs, service_name, ("--timestamps" if timestamps), "--tail", "10", "--follow", "2>&1"),
52
+ (%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
53
+ end
54
+
55
+
56
+ def execute_in_existing_container(*command, interactive: false)
57
+ docker :exec,
58
+ ("-it" if interactive),
59
+ service_name,
60
+ *command
61
+ end
62
+
63
+ def execute_in_new_container(*command, interactive: false)
64
+ docker :run,
65
+ ("-it" if interactive),
66
+ "--rm",
67
+ *network_args,
68
+ *env_args,
69
+ *volume_args,
70
+ image,
71
+ *command
72
+ end
73
+
74
+ def execute_in_existing_container_over_ssh(*command)
75
+ run_over_ssh execute_in_existing_container(*command, interactive: true)
76
+ end
77
+
78
+ def execute_in_new_container_over_ssh(*command)
79
+ run_over_ssh execute_in_new_container(*command, interactive: true)
80
+ end
81
+
82
+ def run_over_ssh(command)
83
+ super command, host: hosts.first
84
+ end
85
+
86
+
87
+ def ensure_local_file_present(local_file)
88
+ if !local_file.is_a?(StringIO) && !Pathname.new(local_file).exist?
89
+ raise "Missing file: #{local_file}"
90
+ end
91
+ end
92
+
93
+ def remove_service_directory
94
+ [ :rm, "-rf", service_name ]
95
+ end
96
+
97
+ def remove_container
98
+ docker :container, :prune, "--force", *service_filter
99
+ end
100
+
101
+ def remove_image
102
+ docker :image, :rm, "--force", image
103
+ end
104
+
105
+ def ensure_env_directory
106
+ make_directory env_directory
107
+ end
108
+
109
+ private
110
+ def service_filter
111
+ [ "--filter", "label=service=#{service_name}" ]
112
+ end
113
+ end
@@ -0,0 +1,51 @@
1
+ module Kamal::Commands::App::Assets
2
+ def extract_assets
3
+ asset_container = "#{role.container_prefix}-assets"
4
+
5
+ combine \
6
+ make_directory(role.asset_extracted_directory),
7
+ [ *docker(:stop, "-t 1", asset_container, "2> /dev/null"), "|| true" ],
8
+ docker(:run, "--name", asset_container, "--detach", "--rm", "--entrypoint", "sleep", config.absolute_image, "1000000"),
9
+ docker(:cp, "-L", "#{asset_container}:#{role.asset_path}/.", role.asset_extracted_directory),
10
+ docker(:stop, "-t 1", asset_container),
11
+ by: "&&"
12
+ end
13
+
14
+ def sync_asset_volumes(old_version: nil)
15
+ new_extracted_path, new_volume_path = role.asset_extracted_directory(config.version), role.asset_volume.host_path
16
+ if old_version.present?
17
+ old_extracted_path, old_volume_path = role.asset_extracted_directory(old_version), role.asset_volume(old_version).host_path
18
+ end
19
+
20
+ commands = [ make_directory(new_volume_path), copy_contents(new_extracted_path, new_volume_path) ]
21
+
22
+ if old_version.present?
23
+ commands << copy_contents(new_extracted_path, old_volume_path, continue_on_error: true)
24
+ commands << copy_contents(old_extracted_path, new_volume_path, continue_on_error: true)
25
+ end
26
+
27
+ chain *commands
28
+ end
29
+
30
+ def clean_up_assets
31
+ chain \
32
+ find_and_remove_older_siblings(role.asset_extracted_directory),
33
+ find_and_remove_older_siblings(role.asset_volume_directory)
34
+ end
35
+
36
+ private
37
+ def find_and_remove_older_siblings(path)
38
+ [
39
+ :find,
40
+ Pathname.new(path).dirname.to_s,
41
+ "-maxdepth 1",
42
+ "-name", "'#{role.name}-*'",
43
+ "!", "-name", Pathname.new(path).basename.to_s,
44
+ "-exec rm -rf \"{}\" +"
45
+ ]
46
+ end
47
+
48
+ def copy_contents(source, destination, continue_on_error: false)
49
+ [ :cp, "-rnT", "#{source}", destination, *("|| true" if continue_on_error) ]
50
+ end
51
+ end
@@ -0,0 +1,31 @@
1
+ module Kamal::Commands::App::Containers
2
+ DOCKER_HEALTH_LOG_FORMAT = "'{{json .State.Health}}'"
3
+
4
+ def list_containers
5
+ docker :container, :ls, "--all", *filter_args
6
+ end
7
+
8
+ def list_container_names
9
+ [ *list_containers, "--format", "'{{ .Names }}'" ]
10
+ end
11
+
12
+ def remove_container(version:)
13
+ pipe \
14
+ container_id_for(container_name: container_name(version)),
15
+ xargs(docker(:container, :rm))
16
+ end
17
+
18
+ def rename_container(version:, new_version:)
19
+ docker :rename, container_name(version), container_name(new_version)
20
+ end
21
+
22
+ def remove_containers
23
+ docker :container, :prune, "--force", *filter_args
24
+ end
25
+
26
+ def container_health_log(version:)
27
+ pipe \
28
+ container_id_for(container_name: container_name(version)),
29
+ xargs(docker(:inspect, "--format", DOCKER_HEALTH_LOG_FORMAT))
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ module Kamal::Commands::App::Execution
2
+ def execute_in_existing_container(*command, interactive: false, env:)
3
+ docker :exec,
4
+ ("-it" if interactive),
5
+ *argumentize("--env", env),
6
+ container_name,
7
+ *command
8
+ end
9
+
10
+ def execute_in_new_container(*command, interactive: false, env:)
11
+ docker :run,
12
+ ("-it" if interactive),
13
+ "--rm",
14
+ "--network", "kamal",
15
+ *role&.env_args(host),
16
+ *argumentize("--env", env),
17
+ *config.volume_args,
18
+ *role&.option_args,
19
+ config.absolute_image,
20
+ *command
21
+ end
22
+
23
+ def execute_in_existing_container_over_ssh(*command, env:)
24
+ run_over_ssh execute_in_existing_container(*command, interactive: true, env: env), host: host
25
+ end
26
+
27
+ def execute_in_new_container_over_ssh(*command, env:)
28
+ run_over_ssh execute_in_new_container(*command, interactive: true, env: env), host: host
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ module Kamal::Commands::App::Images
2
+ def list_images
3
+ docker :image, :ls, config.repository
4
+ end
5
+
6
+ def remove_images
7
+ docker :image, :prune, "--all", "--force", *filter_args
8
+ end
9
+
10
+ def tag_latest_image
11
+ docker :tag, config.absolute_image, config.latest_image
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ module Kamal::Commands::App::Logging
2
+ def logs(version: nil, timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
3
+ pipe \
4
+ version ? container_id_for_version(version) : current_running_container_id,
5
+ "xargs docker logs#{" --timestamps" if timestamps}#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1",
6
+ ("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
7
+ end
8
+
9
+ def follow_logs(host:, timestamps: true, lines: nil, grep: nil, grep_options: nil)
10
+ run_over_ssh \
11
+ pipe(
12
+ current_running_container_id,
13
+ "xargs docker logs#{" --timestamps" if timestamps}#{" --tail #{lines}" if lines} --follow 2>&1",
14
+ (%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
15
+ ),
16
+ host: host
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ module Kamal::Commands::App::Proxy
2
+ delegate :proxy_container_name, to: :config
3
+
4
+ def deploy(target:)
5
+ proxy_exec :deploy, role.container_prefix, *role.proxy.deploy_command_args(target: target)
6
+ end
7
+
8
+ def remove
9
+ proxy_exec :remove, role.container_prefix
10
+ end
11
+
12
+ private
13
+ def proxy_exec(*command)
14
+ docker :exec, proxy_container_name, "kamal-proxy", *command
15
+ end
16
+ end