kamal-insecure 2.7.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/kamal +18 -0
- data/lib/kamal/cli/accessory.rb +313 -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 +400 -0
- data/lib/kamal/cli/base.rb +223 -0
- data/lib/kamal/cli/build/clone.rb +61 -0
- data/lib/kamal/cli/build.rb +204 -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 +277 -0
- data/lib/kamal/cli/proxy.rb +290 -0
- data/lib/kamal/cli/prune.rb +34 -0
- data/lib/kamal/cli/registry.rb +19 -0
- data/lib/kamal/cli/secrets.rb +49 -0
- data/lib/kamal/cli/server.rb +50 -0
- data/lib/kamal/cli/templates/deploy.yml +101 -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 +17 -0
- data/lib/kamal/cli.rb +9 -0
- data/lib/kamal/commander/specifics.rb +62 -0
- data/lib/kamal/commander.rb +167 -0
- data/lib/kamal/commands/accessory/proxy.rb +16 -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/error_pages.rb +9 -0
- data/lib/kamal/commands/app/execution.rb +32 -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 +124 -0
- data/lib/kamal/commands/auditor.rb +39 -0
- data/lib/kamal/commands/base.rb +134 -0
- data/lib/kamal/commands/builder/base.rb +124 -0
- data/lib/kamal/commands/builder/clone.rb +31 -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 +14 -0
- data/lib/kamal/commands/builder/pack.rb +46 -0
- data/lib/kamal/commands/builder/remote.rb +63 -0
- data/lib/kamal/commands/builder.rb +48 -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 +127 -0
- data/lib/kamal/commands/prune.rb +38 -0
- data/lib/kamal/commands/registry.rb +16 -0
- data/lib/kamal/commands/server.rb +15 -0
- data/lib/kamal/commands.rb +2 -0
- data/lib/kamal/configuration/accessory.rb +241 -0
- data/lib/kamal/configuration/alias.rb +15 -0
- data/lib/kamal/configuration/boot.rb +25 -0
- data/lib/kamal/configuration/builder.rb +211 -0
- data/lib/kamal/configuration/docs/accessory.yml +128 -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 +132 -0
- data/lib/kamal/configuration/docs/configuration.yml +184 -0
- data/lib/kamal/configuration/docs/env.yml +116 -0
- data/lib/kamal/configuration/docs/logging.yml +21 -0
- data/lib/kamal/configuration/docs/proxy.yml +164 -0
- data/lib/kamal/configuration/docs/registry.yml +56 -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 +38 -0
- data/lib/kamal/configuration/logging.rb +33 -0
- data/lib/kamal/configuration/proxy/boot.rb +129 -0
- data/lib/kamal/configuration/proxy.rb +124 -0
- data/lib/kamal/configuration/registry.rb +32 -0
- data/lib/kamal/configuration/role.rb +222 -0
- data/lib/kamal/configuration/servers.rb +25 -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 +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 +25 -0
- data/lib/kamal/configuration/validator/registry.rb +25 -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 +191 -0
- data/lib/kamal/configuration/volume.rb +22 -0
- data/lib/kamal/configuration.rb +372 -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/secrets/adapters/aws_secrets_manager.rb +51 -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 +130 -0
- data/lib/kamal/secrets/adapters/test.rb +14 -0
- data/lib/kamal/secrets/adapters.rb +16 -0
- data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +33 -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 +365 -0
@@ -0,0 +1,122 @@
|
|
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_ROLES (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 = github_repo_from_remote_url
|
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
|
+
|
81
|
+
private
|
82
|
+
def github_repo_from_remote_url
|
83
|
+
url = `git config --get remote.origin.url`.strip.delete_suffix(".git")
|
84
|
+
if url.start_with?("https://github.com/")
|
85
|
+
url.delete_prefix("https://github.com/")
|
86
|
+
elsif url.start_with?("git@github.com:")
|
87
|
+
url.delete_prefix("git@github.com:")
|
88
|
+
else
|
89
|
+
url
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
$stdout.sync = true
|
96
|
+
|
97
|
+
begin
|
98
|
+
puts "Checking build status..."
|
99
|
+
|
100
|
+
attempts = 0
|
101
|
+
checks = GithubStatusChecks.new
|
102
|
+
|
103
|
+
loop do
|
104
|
+
case checks.state
|
105
|
+
when "success"
|
106
|
+
puts "Checks passed, see #{checks.first_status_url}"
|
107
|
+
exit 0
|
108
|
+
when "failure"
|
109
|
+
exit_with_error "Checks failed, see #{checks.first_status_url}"
|
110
|
+
when "pending"
|
111
|
+
attempts += 1
|
112
|
+
end
|
113
|
+
|
114
|
+
exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS
|
115
|
+
|
116
|
+
puts checks.current_status
|
117
|
+
sleep(ATTEMPTS_GAP)
|
118
|
+
checks.refresh!
|
119
|
+
end
|
120
|
+
rescue Octokit::NotFound
|
121
|
+
exit_with_error "Build status could not be found"
|
122
|
+
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,9 @@
|
|
1
|
+
module Kamal::Cli
|
2
|
+
class BootError < StandardError; end
|
3
|
+
class HookError < StandardError; end
|
4
|
+
class LockError < StandardError; end
|
5
|
+
class DependencyError < StandardError; end
|
6
|
+
end
|
7
|
+
|
8
|
+
# SSHKit uses instance eval, so we need a global const for ergonomics
|
9
|
+
KAMAL = Kamal::Commander.new
|
@@ -0,0 +1,62 @@
|
|
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
|
+
sort_primary_role_hosts_first!(hosts)
|
15
|
+
end
|
16
|
+
|
17
|
+
def roles_on(host)
|
18
|
+
roles.select { |role| role.hosts.include?(host.to_s) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def app_hosts
|
22
|
+
@app_hosts ||= sort_primary_role_hosts_first!(config.app_hosts & specified_hosts)
|
23
|
+
end
|
24
|
+
|
25
|
+
def proxy_hosts
|
26
|
+
config.proxy_hosts & specified_hosts
|
27
|
+
end
|
28
|
+
|
29
|
+
def accessory_hosts
|
30
|
+
config.accessories.flat_map(&:hosts) & specified_hosts
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
attr_reader :config, :specific_hosts, :specific_roles
|
35
|
+
|
36
|
+
def primary_specific_role
|
37
|
+
primary_or_first_role(specific_roles) if specific_roles.present?
|
38
|
+
end
|
39
|
+
|
40
|
+
def primary_or_first_role(roles)
|
41
|
+
roles.detect { |role| role == config.primary_role } || roles.first
|
42
|
+
end
|
43
|
+
|
44
|
+
def specified_roles
|
45
|
+
(specific_roles || config.roles) \
|
46
|
+
.select { |role| ((specific_hosts || config.all_hosts) & role.hosts).any? }
|
47
|
+
end
|
48
|
+
|
49
|
+
def specified_hosts
|
50
|
+
specified_hosts = specific_hosts || config.all_hosts
|
51
|
+
|
52
|
+
if (specific_role_hosts = specific_roles&.flat_map(&:hosts)).present?
|
53
|
+
specified_hosts.select { |host| specific_role_hosts.include?(host) }
|
54
|
+
else
|
55
|
+
specified_hosts
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def sort_primary_role_hosts_first!(hosts)
|
60
|
+
stable_sort!(hosts) { |host| roles_on(host).any? { |role| role == primary_role } ? 0 : 1 }
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,167 @@
|
|
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
|
+
attr_reader :specific_roles, :specific_hosts
|
8
|
+
delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :app_hosts, :proxy_hosts, :accessory_hosts, to: :specifics
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
reset
|
12
|
+
end
|
13
|
+
|
14
|
+
def reset
|
15
|
+
self.verbosity = :info
|
16
|
+
self.holding_lock = ENV["KAMAL_LOCK"] == "true"
|
17
|
+
self.connected = false
|
18
|
+
@specifics = @specific_roles = @specific_hosts = nil
|
19
|
+
@config = @config_kwargs = nil
|
20
|
+
@commands = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def config
|
24
|
+
@config ||= Kamal::Configuration.create_from(**@config_kwargs).tap do |config|
|
25
|
+
@config_kwargs = nil
|
26
|
+
configure_sshkit_with(config)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def configure(**kwargs)
|
31
|
+
@config, @config_kwargs = nil, kwargs
|
32
|
+
end
|
33
|
+
|
34
|
+
def configured?
|
35
|
+
@config || @config_kwargs
|
36
|
+
end
|
37
|
+
|
38
|
+
def specific_primary!
|
39
|
+
@specifics = nil
|
40
|
+
if specific_roles.present?
|
41
|
+
self.specific_hosts = [ specific_roles.first.primary_host ]
|
42
|
+
else
|
43
|
+
self.specific_hosts = [ config.primary_host ]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def specific_roles=(role_names)
|
48
|
+
@specifics = nil
|
49
|
+
if role_names.present?
|
50
|
+
@specific_roles = Kamal::Utils.filter_specific_items(role_names, config.roles)
|
51
|
+
|
52
|
+
if @specific_roles.empty?
|
53
|
+
raise ArgumentError, "No --roles match for #{role_names.join(',')}"
|
54
|
+
end
|
55
|
+
|
56
|
+
@specific_roles
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def specific_hosts=(hosts)
|
61
|
+
@specifics = nil
|
62
|
+
if hosts.present?
|
63
|
+
@specific_hosts = Kamal::Utils.filter_specific_items(hosts, config.all_hosts)
|
64
|
+
|
65
|
+
if @specific_hosts.empty?
|
66
|
+
raise ArgumentError, "No --hosts match for #{hosts.join(',')}"
|
67
|
+
end
|
68
|
+
|
69
|
+
@specific_hosts
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def with_specific_hosts(hosts)
|
74
|
+
original_hosts, self.specific_hosts = specific_hosts, hosts
|
75
|
+
yield
|
76
|
+
ensure
|
77
|
+
self.specific_hosts = original_hosts
|
78
|
+
end
|
79
|
+
|
80
|
+
def accessory_names
|
81
|
+
config.accessories&.collect(&:name) || []
|
82
|
+
end
|
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
|
+
@commands[:builder] ||= Kamal::Commands::Builder.new(config)
|
98
|
+
end
|
99
|
+
|
100
|
+
def docker
|
101
|
+
@commands[:docker] ||= Kamal::Commands::Docker.new(config)
|
102
|
+
end
|
103
|
+
|
104
|
+
def hook
|
105
|
+
@commands[:hook] ||= Kamal::Commands::Hook.new(config)
|
106
|
+
end
|
107
|
+
|
108
|
+
def lock
|
109
|
+
@commands[:lock] ||= Kamal::Commands::Lock.new(config)
|
110
|
+
end
|
111
|
+
|
112
|
+
def proxy
|
113
|
+
@commands[:proxy] ||= Kamal::Commands::Proxy.new(config)
|
114
|
+
end
|
115
|
+
|
116
|
+
def prune
|
117
|
+
@commands[:prune] ||= Kamal::Commands::Prune.new(config)
|
118
|
+
end
|
119
|
+
|
120
|
+
def registry
|
121
|
+
@commands[:registry] ||= Kamal::Commands::Registry.new(config)
|
122
|
+
end
|
123
|
+
|
124
|
+
def server
|
125
|
+
@commands[:server] ||= Kamal::Commands::Server.new(config)
|
126
|
+
end
|
127
|
+
|
128
|
+
def alias(name)
|
129
|
+
config.aliases[name]
|
130
|
+
end
|
131
|
+
|
132
|
+
def with_verbosity(level)
|
133
|
+
old_level = self.verbosity
|
134
|
+
|
135
|
+
self.verbosity = level
|
136
|
+
SSHKit.config.output_verbosity = level
|
137
|
+
|
138
|
+
yield
|
139
|
+
ensure
|
140
|
+
self.verbosity = old_level
|
141
|
+
SSHKit.config.output_verbosity = old_level
|
142
|
+
end
|
143
|
+
|
144
|
+
def holding_lock?
|
145
|
+
self.holding_lock
|
146
|
+
end
|
147
|
+
|
148
|
+
def connected?
|
149
|
+
self.connected
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
# Lazy setup of SSHKit
|
154
|
+
def configure_sshkit_with(config)
|
155
|
+
SSHKit::Backend::Netssh.pool.idle_timeout = config.sshkit.pool_idle_timeout
|
156
|
+
SSHKit::Backend::Netssh.configure do |sshkit|
|
157
|
+
sshkit.max_concurrent_starts = config.sshkit.max_concurrent_starts
|
158
|
+
sshkit.ssh_options = config.ssh.options
|
159
|
+
end
|
160
|
+
SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs
|
161
|
+
SSHKit.config.output_verbosity = verbosity
|
162
|
+
end
|
163
|
+
|
164
|
+
def specifics
|
165
|
+
@specifics ||= Kamal::Commander::Specifics.new(config, specific_hosts, specific_roles)
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Kamal::Commands::Accessory::Proxy
|
2
|
+
delegate :container_name, to: :"config.proxy_boot", prefix: :proxy
|
3
|
+
|
4
|
+
def deploy(target:)
|
5
|
+
proxy_exec :deploy, service_name, *proxy.deploy_command_args(target: target)
|
6
|
+
end
|
7
|
+
|
8
|
+
def remove
|
9
|
+
proxy_exec :remove, service_name
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def proxy_exec(*command)
|
14
|
+
docker :exec, proxy_container_name, "kamal-proxy", *command
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
class Kamal::Commands::Accessory < Kamal::Commands::Base
|
2
|
+
include Proxy
|
3
|
+
|
4
|
+
attr_reader :accessory_config
|
5
|
+
delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd,
|
6
|
+
:network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args,
|
7
|
+
:secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, :registry,
|
8
|
+
to: :accessory_config
|
9
|
+
|
10
|
+
def initialize(config, name:)
|
11
|
+
super(config)
|
12
|
+
@accessory_config = config.accessory(name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(host: nil)
|
16
|
+
docker :run,
|
17
|
+
"--name", service_name,
|
18
|
+
"--detach",
|
19
|
+
"--restart", "unless-stopped",
|
20
|
+
*network_args,
|
21
|
+
*config.logging_args,
|
22
|
+
*publish_args,
|
23
|
+
*([ "--env", "KAMAL_HOST=\"#{host}\"" ] if host),
|
24
|
+
*env_args,
|
25
|
+
*volume_args,
|
26
|
+
*label_args,
|
27
|
+
*option_args,
|
28
|
+
image,
|
29
|
+
cmd
|
30
|
+
end
|
31
|
+
|
32
|
+
def start
|
33
|
+
docker :container, :start, service_name
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop
|
37
|
+
docker :container, :stop, service_name
|
38
|
+
end
|
39
|
+
|
40
|
+
def info(all: false, quiet: false)
|
41
|
+
docker :ps, *("-a" if all), *("-q" if quiet), *service_filter
|
42
|
+
end
|
43
|
+
|
44
|
+
def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
|
45
|
+
pipe \
|
46
|
+
docker(:logs, service_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
|
47
|
+
("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
|
48
|
+
end
|
49
|
+
|
50
|
+
def follow_logs(timestamps: true, grep: nil, grep_options: nil)
|
51
|
+
run_over_ssh \
|
52
|
+
pipe \
|
53
|
+
docker(:logs, service_name, ("--timestamps" if timestamps), "--tail", "10", "--follow", "2>&1"),
|
54
|
+
(%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
|
55
|
+
end
|
56
|
+
|
57
|
+
def execute_in_existing_container(*command, interactive: false)
|
58
|
+
docker :exec,
|
59
|
+
(docker_interactive_args if interactive),
|
60
|
+
service_name,
|
61
|
+
*command
|
62
|
+
end
|
63
|
+
|
64
|
+
def execute_in_new_container(*command, interactive: false)
|
65
|
+
docker :run,
|
66
|
+
(docker_interactive_args if interactive),
|
67
|
+
"--rm",
|
68
|
+
*network_args,
|
69
|
+
*env_args,
|
70
|
+
*volume_args,
|
71
|
+
image,
|
72
|
+
*command
|
73
|
+
end
|
74
|
+
|
75
|
+
def execute_in_existing_container_over_ssh(*command)
|
76
|
+
run_over_ssh execute_in_existing_container(*command, interactive: true)
|
77
|
+
end
|
78
|
+
|
79
|
+
def execute_in_new_container_over_ssh(*command)
|
80
|
+
run_over_ssh execute_in_new_container(*command, interactive: true)
|
81
|
+
end
|
82
|
+
|
83
|
+
def run_over_ssh(command)
|
84
|
+
super command, host: hosts.first
|
85
|
+
end
|
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(:container, :rm, asset_container, "2> /dev/null"), "|| true" ],
|
8
|
+
docker(:container, :create, "--name", asset_container, config.absolute_image),
|
9
|
+
docker(:container, :cp, "-L", "#{asset_container}:#{role.asset_path}/.", role.asset_extracted_directory),
|
10
|
+
docker(:container, :rm, 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", *container_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", *container_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,9 @@
|
|
1
|
+
module Kamal::Commands::App::ErrorPages
|
2
|
+
def create_error_pages_directory
|
3
|
+
make_directory(config.proxy_boot.error_pages_directory)
|
4
|
+
end
|
5
|
+
|
6
|
+
def clean_up_error_pages
|
7
|
+
[ :find, config.proxy_boot.error_pages_directory, "-mindepth", "1", "-maxdepth", "1", "!", "-name", KAMAL.config.version, "-exec", "rm", "-rf", "{} +" ]
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Kamal::Commands::App::Execution
|
2
|
+
def execute_in_existing_container(*command, interactive: false, env:)
|
3
|
+
docker :exec,
|
4
|
+
(docker_interactive_args if interactive),
|
5
|
+
*argumentize("--env", env),
|
6
|
+
container_name,
|
7
|
+
*command
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute_in_new_container(*command, interactive: false, detach: false, env:)
|
11
|
+
docker :run,
|
12
|
+
(docker_interactive_args if interactive),
|
13
|
+
("--detach" if detach),
|
14
|
+
("--rm" unless detach),
|
15
|
+
"--network", "kamal",
|
16
|
+
*role&.env_args(host),
|
17
|
+
*argumentize("--env", env),
|
18
|
+
*role.logging_args,
|
19
|
+
*config.volume_args,
|
20
|
+
*role&.option_args,
|
21
|
+
config.absolute_image,
|
22
|
+
*command
|
23
|
+
end
|
24
|
+
|
25
|
+
def execute_in_existing_container_over_ssh(*command, env:)
|
26
|
+
run_over_ssh execute_in_existing_container(*command, interactive: true, env: env), host: host
|
27
|
+
end
|
28
|
+
|
29
|
+
def execute_in_new_container_over_ssh(*command, env:)
|
30
|
+
run_over_ssh execute_in_new_container(*command, interactive: true, env: env), host: host
|
31
|
+
end
|
32
|
+
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", *image_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,28 @@
|
|
1
|
+
module Kamal::Commands::App::Logging
|
2
|
+
def logs(container_id: nil, timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
|
3
|
+
pipe \
|
4
|
+
container_id_command(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:, container_id: nil, timestamps: true, lines: nil, grep: nil, grep_options: nil)
|
10
|
+
run_over_ssh \
|
11
|
+
pipe(
|
12
|
+
container_id_command(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
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def container_id_command(container_id)
|
22
|
+
case container_id
|
23
|
+
when Array then container_id
|
24
|
+
when String, Symbol then "echo #{container_id}"
|
25
|
+
else current_running_container_id
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|