kamal 1.7.2 → 1.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88a235b000a74c9b34e55924be1c372ebd7e59b0c0bde140f0b8c8b311614e96
4
- data.tar.gz: 7193b72065b8eeab2dbe761ae93383d9c44663104d46b37f93b32fbef00be84a
3
+ metadata.gz: 3cf788692b64036e48979bd965c6cc205442f308173c921f1b7e7f3d53e9f279
4
+ data.tar.gz: c81142cad28e8fdfab7ae68259569f1754791952993c323cbc46760252a8b42c
5
5
  SHA512:
6
- metadata.gz: 58d4e970cd573e766a1580eb5f4d343941d2bf68518fc43840776422ba9d04c89210ad25b4f72b07268920e79c483f1f2ab57c2cb440ad96c372c0447775a0ab
7
- data.tar.gz: 98e87cc46ad4e88fadb38257c5cc48b7a71c4b87121a75aa8f6bfcac290e97c7cc357351564f6b0167e35f6a2472b4a20e9e27ed21721a285c794dc7b9ab5cbe
6
+ metadata.gz: b461f6c47a6b77d3cd35aff8198dc85e8db8b840a14ffd492127042d5acb3cd2603578f59eba9629726dfd918cddceed978b7554b00045a5fe64a0ec656b6d44
7
+ data.tar.gz: 1d446485c62f5285c82d92a49f3b8b821fefd7695ac651bc5e8531a16e8c1739bd4c20e4ff4012a4a42b8c73f0d681cfd64a9f6b90da791ab27df97679ae2d0b
@@ -25,12 +25,17 @@ module Kamal::Cli
25
25
  def initialize(*)
26
26
  super
27
27
  @original_env = ENV.to_h.dup
28
- load_envs
28
+ load_env
29
29
  initialize_commander(options_with_subcommand_class_options)
30
30
  end
31
31
 
32
32
  private
33
- def load_envs
33
+ def reload_env
34
+ reset_env
35
+ load_env
36
+ end
37
+
38
+ def load_env
34
39
  if destination = options[:destination]
35
40
  Dotenv.load(".env.#{destination}", ".env")
36
41
  else
@@ -38,10 +43,27 @@ module Kamal::Cli
38
43
  end
39
44
  end
40
45
 
41
- def reload_envs
46
+ def reset_env
47
+ replace_env @original_env
48
+ end
49
+
50
+ def replace_env(env)
42
51
  ENV.clear
43
- ENV.update(@original_env)
44
- load_envs
52
+ ENV.update(env)
53
+ end
54
+
55
+ def with_original_env
56
+ keeping_current_env do
57
+ reset_env
58
+ yield
59
+ end
60
+ end
61
+
62
+ def keeping_current_env
63
+ current_env = ENV.to_h.dup
64
+ yield
65
+ ensure
66
+ replace_env(current_env)
45
67
  end
46
68
 
47
69
  def options_with_subcommand_class_options
@@ -59,11 +59,14 @@ class Kamal::Cli::Build < Kamal::Cli::Base
59
59
 
60
60
  desc "pull", "Pull app image from registry onto servers"
61
61
  def pull
62
- on(KAMAL.hosts) do
63
- execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
64
- execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
65
- execute *KAMAL.builder.pull
66
- execute *KAMAL.builder.validate_image
62
+ if (first_hosts = mirror_hosts).any?
63
+ #  Pull on a single host per mirror first to seed them
64
+ say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
65
+ pull_on_hosts(first_hosts)
66
+ say "Pulling image on remaining hosts...", :magenta
67
+ pull_on_hosts(KAMAL.hosts - first_hosts)
68
+ else
69
+ pull_on_hosts(KAMAL.hosts)
67
70
  end
68
71
  end
69
72
 
@@ -131,4 +134,28 @@ class Kamal::Cli::Build < Kamal::Cli::Base
131
134
  end
132
135
  end
133
136
  end
137
+
138
+ def mirror_hosts
139
+ if KAMAL.hosts.many?
140
+ mirror_hosts = Concurrent::Hash.new
141
+ on(KAMAL.hosts) do |host|
142
+ first_mirror = capture_with_info(*KAMAL.builder.first_mirror).strip.presence
143
+ mirror_hosts[first_mirror] ||= host if first_mirror
144
+ rescue SSHKit::Command::Failed => e
145
+ raise unless e.message =~ /error calling index: reflect: slice index out of range/
146
+ end
147
+ mirror_hosts.values
148
+ else
149
+ []
150
+ end
151
+ end
152
+
153
+ def pull_on_hosts(hosts)
154
+ on(hosts) do
155
+ execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
156
+ execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
157
+ execute *KAMAL.builder.pull
158
+ execute *KAMAL.builder.validate_image
159
+ end
160
+ end
134
161
  end
@@ -191,10 +191,12 @@ class Kamal::Cli::Main < Kamal::Cli::Base
191
191
  end
192
192
 
193
193
  if Pathname.new(File.expand_path(env_template_path)).exist?
194
- File.write(env_path, ERB.new(File.read(env_template_path), trim_mode: "-").result, perm: 0600)
194
+ # Ensure existing env doesn't pollute template evaluation
195
+ content = with_original_env { ERB.new(File.read(env_template_path), trim_mode: "-").result }
196
+ File.write(env_path, content, perm: 0600)
195
197
 
196
198
  unless options[:skip_push]
197
- reload_envs
199
+ reload_env
198
200
  invoke "kamal:cli:env:push", options
199
201
  end
200
202
  else
@@ -1,7 +1,13 @@
1
- #!/bin/sh
1
+ #!/usr/bin/env ruby
2
2
 
3
3
  # A sample docker-setup hook
4
4
  #
5
- # Sets up a Docker network which can then be used by the application’s containers
5
+ # Sets up a Docker network on defined hosts which can then be used by the application’s containers
6
6
 
7
- ssh user@example.com docker network create kamal
7
+ hosts = ENV["KAMAL_HOSTS"].split(",")
8
+
9
+ hosts.each do |ip|
10
+ destination = "root@#{ip}"
11
+ puts "Creating a Docker network \"kamal\" on #{destination}"
12
+ `ssh #{destination} docker network create kamal`
13
+ end
@@ -9,7 +9,7 @@ class Kamal::Commands::Auditor < Kamal::Commands::Base
9
9
  # Runs remotely
10
10
  def record(line, **details)
11
11
  append \
12
- [ :echo, audit_tags(**details).except(:version, :service_version).to_s, line ],
12
+ [ :echo, audit_tags(**details).except(:version, :service_version, :service).to_s, line ],
13
13
  audit_log_file
14
14
  end
15
15
 
@@ -40,6 +40,10 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
40
40
  []
41
41
  end
42
42
 
43
+ def first_mirror
44
+ docker(:info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
45
+ end
46
+
43
47
  private
44
48
  def build_tags
45
49
  [ "-t", config.absolute_image, "-t", config.latest_image ]
@@ -2,7 +2,7 @@ require "active_support/core_ext/string/filters"
2
2
 
3
3
  class Kamal::Commands::Builder < Kamal::Commands::Base
4
4
  delegate :create, :remove, :push, :clean, :pull, :info, :context_hosts, :config_context_hosts, :validate_image,
5
- to: :target
5
+ :first_mirror, to: :target
6
6
 
7
7
  include Clone
8
8
 
@@ -44,3 +44,23 @@ ssh:
44
44
  # Defaults to `fatal`. Set this to debug if you are having
45
45
  # SSH connection issues.
46
46
  log_level: debug
47
+
48
+ # Keys Only
49
+ #
50
+ # Set to true to use only private keys from keys and key_data parameters,
51
+ # even if ssh-agent offers more identities. This option is intended for
52
+ # situations where ssh-agent offers many different identites or you have
53
+ # a need to overwrite all identites and force a single one.
54
+ keys_only: false
55
+
56
+ # Keys
57
+ #
58
+ # An array of file names of private keys to use for publickey
59
+ # and hostbased authentication
60
+ keys: [ "~/.ssh/id.pem" ]
61
+
62
+ # Key Data
63
+ #
64
+ # An array of strings, with each element of the array being
65
+ # a raw private key in PEM format.
66
+ key_data: [ "-----BEGIN OPENSSH PRIVATE KEY-----" ]
@@ -26,8 +26,20 @@ class Kamal::Configuration::Ssh
26
26
  end
27
27
  end
28
28
 
29
+ def keys_only
30
+ ssh_config["keys_only"]
31
+ end
32
+
33
+ def keys
34
+ ssh_config["keys"]
35
+ end
36
+
37
+ def key_data
38
+ ssh_config["key_data"]
39
+ end
40
+
29
41
  def options
30
- { user: user, port: port, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30 }.compact
42
+ { user: user, port: port, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30, keys_only: keys_only, keys: keys, key_data: key_data }.compact
31
43
  end
32
44
 
33
45
  def to_h
@@ -31,9 +31,9 @@ class Kamal::Configuration::Validator
31
31
  validate_array_of! value, example_value.first.class
32
32
  elsif example_value.is_a?(Hash)
33
33
  case key.to_s
34
- when "options"
34
+ when "options", "args"
35
35
  validate_type! value, Hash
36
- when "args", "labels"
36
+ when "labels"
37
37
  validate_hash_of! value, example_value.first[1].class
38
38
  else
39
39
  validate_against_example! value, example_value
data/lib/kamal/git.rb CHANGED
@@ -9,6 +9,10 @@ module Kamal::Git
9
9
  `git config user.name`.strip
10
10
  end
11
11
 
12
+ def email
13
+ `git config user.email`.strip
14
+ end
15
+
12
16
  def revision
13
17
  `git rev-parse HEAD`.strip
14
18
  end
data/lib/kamal/tags.rb CHANGED
@@ -10,10 +10,11 @@ class Kamal::Tags
10
10
 
11
11
  def default_tags(config)
12
12
  { recorded_at: Time.now.utc.iso8601,
13
- performer: `whoami`.chomp,
13
+ performer: Kamal::Git.email.presence || `whoami`.chomp,
14
14
  destination: config.destination,
15
15
  version: config.version,
16
- service_version: service_version(config) }
16
+ service_version: service_version(config),
17
+ service: config.service }
17
18
  end
18
19
 
19
20
  def service_version(config)
data/lib/kamal/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kamal
2
- VERSION = "1.7.2"
2
+ VERSION = "1.8.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kamal
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.2
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-24 00:00:00.000000000 Z
11
+ date: 2024-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -30,7 +30,7 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 1.22.2
33
+ version: 1.23.0
34
34
  - - "<"
35
35
  - !ruby/object:Gem::Version
36
36
  version: '2.0'
@@ -40,7 +40,7 @@ dependencies:
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: 1.22.2
43
+ version: 1.23.0
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '2.0'
@@ -114,26 +114,6 @@ dependencies:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
116
  version: '1.2'
117
- - !ruby/object:Gem::Dependency
118
- name: x25519
119
- requirement: !ruby/object:Gem::Requirement
120
- requirements:
121
- - - "~>"
122
- - !ruby/object:Gem::Version
123
- version: '1.0'
124
- - - ">="
125
- - !ruby/object:Gem::Version
126
- version: 1.0.10
127
- type: :runtime
128
- prerelease: false
129
- version_requirements: !ruby/object:Gem::Requirement
130
- requirements:
131
- - - "~>"
132
- - !ruby/object:Gem::Version
133
- version: '1.0'
134
- - - ">="
135
- - !ruby/object:Gem::Version
136
- version: 1.0.10
137
117
  - !ruby/object:Gem::Dependency
138
118
  name: bcrypt_pbkdf
139
119
  requirement: !ruby/object:Gem::Requirement
@@ -314,7 +294,6 @@ files:
314
294
  - lib/kamal/configuration/validation.rb
315
295
  - lib/kamal/configuration/validator.rb
316
296
  - lib/kamal/configuration/validator/accessory.rb
317
- - lib/kamal/configuration/validator/alias.rb
318
297
  - lib/kamal/configuration/validator/builder.rb
319
298
  - lib/kamal/configuration/validator/env.rb
320
299
  - lib/kamal/configuration/validator/registry.rb
@@ -1,19 +0,0 @@
1
- class Kamal::Configuration::Validator::Alias < Kamal::Configuration::Validator
2
- def validate!
3
- super
4
-
5
- name = context.delete_prefix("aliases/")
6
-
7
- if name !~ /\A[a-z0-9_-]+\z/
8
- error "Invalid alias name: '#{name}'. Must only contain lowercase letters, alphanumeric, hyphens and underscores."
9
- end
10
-
11
- if Kamal::Cli::Main.original_commands.include?(name)
12
- error "Alias '#{name}' conflicts with a built-in command."
13
- end
14
-
15
- if config["command"].empty?
16
- error "Alias '#{name}': command is required."
17
- end
18
- end
19
- end