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,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,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,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
|