kamal 1.8.2 → 2.0.0.alpha

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: 7ae26abb4fc1400cdea434a6208bdbd1cfe91b3a11ba395205e38772ecc0256a
4
- data.tar.gz: d1e1e6a9c89b5397b5c8049b99a39c66900e2f92cba464cdd8210fea9d2ca839
3
+ metadata.gz: 4df682847a5becbc623450203537d42aa29fb4b001026a6b39ab266913660d4d
4
+ data.tar.gz: 97ceb4c375f99605f1d874637273168f7274e95c615611d1d324cb0d635523e3
5
5
  SHA512:
6
- metadata.gz: 6fd1140905bef700c2ffa580510b1cd85e4d1961deae2f11f838a27cba88169f1262e5e51a75d94fee431c12a19ab43d789706d366ecc944c5cfbf1e94f0a751
7
- data.tar.gz: 460f1e3af9f7d5ff7d3f044b1e0941132eff81164d34c8ef97698893dcf2f48b8aa6b7b50ca4f713fef0816ecb7bd56ab5cdab8a220c6ae8a810ae8c50715b50
6
+ metadata.gz: 619e959821a7bd1590f0dcf6d3d3956e02c0832a51528b8f15d655ddcfe636a29a6c82fd0080a2d7d3104437368c63f607fd2f61623f230e547693a4accf65ea
7
+ data.tar.gz: 1733b8db747dd71b1cb41ef7ca359856af278e5fb7ef29b0ed0cb677f67cdc82039246fb836bfd7c4260e992830f9a309315d5f98e83dcc3a2a7c5a2656e47a3
@@ -0,0 +1,9 @@
1
+ class Kamal::Cli::Alias::Command < Thor::DynamicCommand
2
+ def run(instance, args = [])
3
+ if (_alias = KAMAL.config.aliases[name])
4
+ Kamal::Cli::Main.start(Shellwords.split(_alias.command) + ARGV[1..-1])
5
+ else
6
+ super
7
+ end
8
+ end
9
+ end
data/lib/kamal/cli/app.rb CHANGED
@@ -71,11 +71,12 @@ class Kamal::Cli::App < Kamal::Cli::Base
71
71
  end
72
72
  end
73
73
 
74
- desc "exec [CMD]", "Execute a custom command on servers within the app container (use --help to show options)"
74
+ desc "exec [CMD...]", "Execute a custom command on servers within the app container (use --help to show options)"
75
75
  option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
76
76
  option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
77
77
  option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
78
- def exec(cmd)
78
+ def exec(*cmd)
79
+ cmd = Kamal::Utils.join_commands(cmd)
79
80
  env = options[:env]
80
81
  case
81
82
  when options[:interactive] && options[:reuse]
@@ -6,7 +6,8 @@ module Kamal::Cli
6
6
  class Base < Thor
7
7
  include SSHKit::DSL
8
8
 
9
- def self.exit_on_failure?() true end
9
+ def self.exit_on_failure?() false end
10
+ def self.dynamic_command_class() Kamal::Cli::Alias::Command end
10
11
 
11
12
  class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
12
13
  class_option :quiet, type: :boolean, aliases: "-q", desc: "Minimal logging"
@@ -22,8 +23,14 @@ module Kamal::Cli
22
23
 
23
24
  class_option :skip_hooks, aliases: "-H", type: :boolean, default: false, desc: "Don't run hooks"
24
25
 
25
- def initialize(*)
26
- super
26
+ def initialize(args = [], local_options = {}, config = {})
27
+ if config[:current_command].is_a?(Kamal::Cli::Alias::Command)
28
+ # When Thor generates a dynamic command, it doesn't attempt to parse the arguments.
29
+ # For our purposes, it means the arguments are passed in args rather than local_options.
30
+ super([], args, config)
31
+ else
32
+ super
33
+ end
27
34
  @original_env = ENV.to_h.dup
28
35
  load_env
29
36
  initialize_commander(options_with_subcommand_class_options)
@@ -30,18 +30,9 @@ class Kamal::Cli::Build < Kamal::Cli::Base
30
30
  say "Building with uncommitted changes:\n #{uncommitted_changes}", :yellow
31
31
  end
32
32
 
33
- # Get the command here to ensure the Dir.chdir doesn't interfere with it
34
- push = KAMAL.builder.push
35
-
36
33
  run_locally do
37
34
  begin
38
- context_hosts = capture_with_info(*KAMAL.builder.context_hosts).split("\n")
39
-
40
- if context_hosts != KAMAL.builder.config_context_hosts
41
- warn "Context hosts have changed, so re-creating builder, was: #{context_hosts.join(", ")}], now: #{KAMAL.builder.config_context_hosts.join(", ")}"
42
- cli.remove
43
- cli.create
44
- end
35
+ execute *KAMAL.builder.buildx_inspect
45
36
  rescue SSHKit::Command::Failed => e
46
37
  if e.message =~ /(context not found|no builder|does not exist)/
47
38
  warn "Missing compatible builder, so creating a new one first"
@@ -51,6 +42,9 @@ class Kamal::Cli::Build < Kamal::Cli::Base
51
42
  end
52
43
  end
53
44
 
45
+ # Get the command here to ensure the Dir.chdir doesn't interfere with it
46
+ push = KAMAL.builder.push
47
+
54
48
  KAMAL.with_verbosity(:debug) do
55
49
  Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
56
50
  end
@@ -72,7 +66,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
72
66
 
73
67
  desc "create", "Create a build setup"
74
68
  def create
75
- if (remote_host = KAMAL.config.builder.remote_host)
69
+ if (remote_host = KAMAL.config.builder.remote)
76
70
  connect_to_remote_host(remote_host)
77
71
  end
78
72
 
@@ -1,7 +1,8 @@
1
1
  class Kamal::Cli::Server < Kamal::Cli::Base
2
2
  desc "exec", "Run a custom command on the server (use --help to show options)"
3
3
  option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)"
4
- def exec(cmd)
4
+ def exec(*cmd)
5
+ cmd = Kamal::Utils.join_commands(cmd)
5
6
  hosts = KAMAL.hosts | KAMAL.accessory_hosts
6
7
 
7
8
  case
@@ -18,6 +18,10 @@ registry:
18
18
  password:
19
19
  - KAMAL_REGISTRY_PASSWORD
20
20
 
21
+ # Configure builder setup.
22
+ builder:
23
+ arch: amd64
24
+
21
25
  # Inject ENV variables into containers (secrets come from .env).
22
26
  # Remember to run `kamal env push` after making changes!
23
27
  # env:
@@ -30,16 +34,6 @@ registry:
30
34
  # ssh:
31
35
  # user: app
32
36
 
33
- # Configure builder setup.
34
- # builder:
35
- # args:
36
- # RUBY_VERSION: 3.2.0
37
- # secrets:
38
- # - GITHUB_TOKEN
39
- # remote:
40
- # arch: amd64
41
- # host: ssh://app@192.168.0.1
42
-
43
37
  # Use accessory services (secrets come from .env).
44
38
  # accessories:
45
39
  # db:
@@ -27,7 +27,11 @@ class Kamal::Commander
27
27
 
28
28
  def specific_primary!
29
29
  @specifics = nil
30
- self.specific_hosts = [ config.primary_host ]
30
+ if specific_roles.present?
31
+ self.specific_hosts = [ specific_roles.first.primary_host ]
32
+ else
33
+ self.specific_hosts = [ config.primary_host ]
34
+ end
31
35
  end
32
36
 
33
37
  def specific_roles=(role_names)
@@ -113,6 +117,10 @@ class Kamal::Commander
113
117
  @traefik ||= Kamal::Commands::Traefik.new(config)
114
118
  end
115
119
 
120
+ def alias(name)
121
+ config.aliases[name]
122
+ end
123
+
116
124
 
117
125
  def with_verbosity(level)
118
126
  old_level = self.verbosity
@@ -1,20 +1,41 @@
1
-
2
1
  class Kamal::Commands::Builder::Base < Kamal::Commands::Base
3
2
  class BuilderError < StandardError; end
4
3
 
5
4
  ENDPOINT_DOCKER_HOST_INSPECT = "'{{.Endpoints.docker.Host}}'"
6
5
 
7
6
  delegate :argumentize, to: Kamal::Utils
8
- delegate :args, :secrets, :dockerfile, :target, :local_arch, :local_host, :remote_arch, :remote_host, :cache_from, :cache_to, :ssh, to: :builder_config
7
+ delegate \
8
+ :args, :secrets, :dockerfile, :target, :arches, :local_arches, :remote_arches, :remote,
9
+ :cache_from, :cache_to, :ssh, :driver, :docker_driver?,
10
+ to: :builder_config
9
11
 
10
12
  def clean
11
13
  docker :image, :rm, "--force", config.absolute_image
12
14
  end
13
15
 
16
+ def push
17
+ docker :buildx, :build,
18
+ "--push",
19
+ *platform_options(arches),
20
+ *([ "--builder", builder_name ] unless docker_driver?),
21
+ *build_options,
22
+ build_context
23
+ end
24
+
14
25
  def pull
15
26
  docker :pull, config.absolute_image
16
27
  end
17
28
 
29
+ def info
30
+ combine \
31
+ docker(:context, :ls),
32
+ docker(:buildx, :ls)
33
+ end
34
+
35
+ def buildx_inspect
36
+ docker :buildx, :inspect, builder_name unless docker_driver?
37
+ end
38
+
18
39
  def build_options
19
40
  [ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh ]
20
41
  end
@@ -32,14 +53,6 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
32
53
  )
33
54
  end
34
55
 
35
- def context_hosts
36
- :true
37
- end
38
-
39
- def config_context_hosts
40
- []
41
- end
42
-
43
56
  def first_mirror
44
57
  docker(:info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
45
58
  end
@@ -91,4 +104,8 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
91
104
  def context_host(builder_name)
92
105
  docker :context, :inspect, builder_name, "--format", ENDPOINT_DOCKER_HOST_INSPECT
93
106
  end
107
+
108
+ def platform_options(arches)
109
+ argumentize "--platform", arches.map { |arch| "linux/#{arch}" }.join(",") if arches.any?
110
+ end
94
111
  end
@@ -0,0 +1,21 @@
1
+ class Kamal::Commands::Builder::Hybrid < Kamal::Commands::Builder::Remote
2
+ def create
3
+ combine \
4
+ create_local_buildx,
5
+ create_remote_context,
6
+ append_remote_buildx
7
+ end
8
+
9
+ private
10
+ def builder_name
11
+ "kamal-hybrid-#{driver}-#{remote.gsub(/[^a-z0-9_-]/, "-")}"
12
+ end
13
+
14
+ def create_local_buildx
15
+ docker :buildx, :create, *platform_options(local_arches), "--name", builder_name, "--driver=#{driver}"
16
+ end
17
+
18
+ def append_remote_buildx
19
+ docker :buildx, :create, *platform_options(remote_arches), "--append", "--name", builder_name, builder_name
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ class Kamal::Commands::Builder::Local < Kamal::Commands::Builder::Base
2
+ def create
3
+ docker :buildx, :create, "--name", builder_name, "--driver=#{driver}" unless docker_driver?
4
+ end
5
+
6
+ def remove
7
+ docker :buildx, :rm, builder_name unless docker_driver?
8
+ end
9
+
10
+ private
11
+ def builder_name
12
+ "kamal-local-#{driver}"
13
+ end
14
+ end
@@ -0,0 +1,40 @@
1
+ class Kamal::Commands::Builder::Remote < Kamal::Commands::Builder::Base
2
+ def create
3
+ chain \
4
+ create_remote_context,
5
+ create_buildx
6
+ end
7
+
8
+ def remove
9
+ chain \
10
+ remove_remote_context,
11
+ remove_buildx
12
+ end
13
+
14
+ def info
15
+ chain \
16
+ docker(:context, :ls),
17
+ docker(:buildx, :ls)
18
+ end
19
+
20
+ private
21
+ def builder_name
22
+ "kamal-remote-#{driver}-#{remote.gsub(/[^a-z0-9_-]/, "-")}"
23
+ end
24
+
25
+ def create_remote_context
26
+ docker :context, :create, builder_name, "--description", "'#{builder_name} host'", "--docker", "'host=#{remote}'"
27
+ end
28
+
29
+ def remove_remote_context
30
+ docker :context, :rm, builder_name
31
+ end
32
+
33
+ def create_buildx
34
+ docker :buildx, :create, "--name", builder_name, builder_name
35
+ end
36
+
37
+ def remove_buildx
38
+ docker :buildx, :rm, builder_name
39
+ end
40
+ end
@@ -1,8 +1,8 @@
1
1
  require "active_support/core_ext/string/filters"
2
2
 
3
3
  class Kamal::Commands::Builder < Kamal::Commands::Base
4
- delegate :create, :remove, :push, :clean, :pull, :info, :context_hosts, :config_context_hosts, :validate_image,
5
- :first_mirror, to: :target
4
+ delegate :create, :remove, :push, :clean, :pull, :info, :buildx_inspect, :validate_image, :first_mirror, to: :target
5
+ delegate :local?, :remote?, to: "config.builder"
6
6
 
7
7
  include Clone
8
8
 
@@ -11,43 +11,27 @@ class Kamal::Commands::Builder < Kamal::Commands::Base
11
11
  end
12
12
 
13
13
  def target
14
- if config.builder.multiarch?
15
- if config.builder.remote?
16
- if config.builder.local?
17
- multiarch_remote
18
- else
19
- native_remote
20
- end
14
+ if remote?
15
+ if local?
16
+ hybrid
21
17
  else
22
- multiarch
18
+ remote
23
19
  end
24
20
  else
25
- if config.builder.cached?
26
- native_cached
27
- else
28
- native
29
- end
21
+ local
30
22
  end
31
23
  end
32
24
 
33
- def native
34
- @native ||= Kamal::Commands::Builder::Native.new(config)
35
- end
36
-
37
- def native_cached
38
- @native ||= Kamal::Commands::Builder::Native::Cached.new(config)
39
- end
40
-
41
- def native_remote
42
- @native ||= Kamal::Commands::Builder::Native::Remote.new(config)
25
+ def remote
26
+ @remote ||= Kamal::Commands::Builder::Remote.new(config)
43
27
  end
44
28
 
45
- def multiarch
46
- @multiarch ||= Kamal::Commands::Builder::Multiarch.new(config)
29
+ def local
30
+ @local ||= Kamal::Commands::Builder::Local.new(config)
47
31
  end
48
32
 
49
- def multiarch_remote
50
- @multiarch_remote ||= Kamal::Commands::Builder::Multiarch::Remote.new(config)
33
+ def hybrid
34
+ @hybrid ||= Kamal::Commands::Builder::Hybrid.new(config)
51
35
  end
52
36
 
53
37
 
@@ -0,0 +1,15 @@
1
+ class Kamal::Configuration::Alias
2
+ include Kamal::Configuration::Validation
3
+
4
+ attr_reader :name, :command
5
+
6
+ def initialize(name, config:)
7
+ @name, @command = name.inquiry, config.raw_config["aliases"][name]
8
+
9
+ validate! \
10
+ command,
11
+ example: validation_yml["aliases"]["uname"],
12
+ context: "aliases/#{name}",
13
+ with: Kamal::Configuration::Validator::Alias
14
+ end
15
+ end
@@ -19,16 +19,36 @@ class Kamal::Configuration::Builder
19
19
  builder_config
20
20
  end
21
21
 
22
- def multiarch?
23
- builder_config["multiarch"] != false
22
+ def remote
23
+ builder_config["remote"]
24
24
  end
25
25
 
26
- def local?
27
- !!builder_config["local"]
26
+ def arches
27
+ Array(builder_config.fetch("arch", default_arch))
28
+ end
29
+
30
+ def local_arches
31
+ @local_arches ||= if remote
32
+ arches & [ Kamal::Utils.docker_arch ]
33
+ else
34
+ arches
35
+ end
36
+ end
37
+
38
+ def remote_arches
39
+ @remote_arches ||= if remote
40
+ arches - local_arches
41
+ else
42
+ []
43
+ end
28
44
  end
29
45
 
30
46
  def remote?
31
- !!builder_config["remote"]
47
+ remote_arches.any?
48
+ end
49
+
50
+ def local?
51
+ arches.empty? || local_arches.any?
32
52
  end
33
53
 
34
54
  def cached?
@@ -55,20 +75,8 @@ class Kamal::Configuration::Builder
55
75
  builder_config["context"] || "."
56
76
  end
57
77
 
58
- def local_arch
59
- builder_config["local"]["arch"] if local?
60
- end
61
-
62
- def local_host
63
- builder_config["local"]["host"] if local?
64
- end
65
-
66
- def remote_arch
67
- builder_config["remote"]["arch"] if remote?
68
- end
69
-
70
- def remote_host
71
- builder_config["remote"]["host"] if remote?
78
+ def driver
79
+ builder_config.fetch("driver", "docker-container")
72
80
  end
73
81
 
74
82
  def cache_from
@@ -114,7 +122,23 @@ class Kamal::Configuration::Builder
114
122
  end
115
123
  end
116
124
 
125
+ def docker_driver?
126
+ driver == "docker"
127
+ end
128
+
117
129
  private
130
+ def valid?
131
+ if docker_driver?
132
+ raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support remote builders" if remote
133
+ raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support caching" if cached?
134
+ raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support multiple arches" if arches.many?
135
+ end
136
+
137
+ if @options["cache"] && @options["cache"]["type"]
138
+ raise ArgumentError, "Invalid cache type: #{@options["cache"]["type"]}" unless [ "gha", "registry" ].include?(@options["cache"]["type"])
139
+ end
140
+ end
141
+
118
142
  def cache_image
119
143
  builder_config["cache"]&.fetch("image", nil) || "#{image}-build-cache"
120
144
  end
@@ -150,4 +174,8 @@ class Kamal::Configuration::Builder
150
174
  def pwd_sha
151
175
  Digest::SHA256.hexdigest(Dir.pwd)[0..12]
152
176
  end
177
+
178
+ def default_arch
179
+ docker_driver? ? [] : [ "amd64", "arm64" ]
180
+ end
153
181
  end
@@ -0,0 +1,26 @@
1
+ # Aliases
2
+ #
3
+ # Aliases are shortcuts for Kamal commands.
4
+ #
5
+ # For example, for a Rails app, you might open a console with:
6
+ #
7
+ # ```shell
8
+ # kamal app exec -i -r console "rails console"
9
+ # ```
10
+ #
11
+ # By defining an alias, like this:
12
+ aliases:
13
+ console: app exec -r console -i "rails console"
14
+ # You can now open the console with:
15
+ # ```shell
16
+ # kamal console
17
+ # ```
18
+
19
+ # Configuring aliases
20
+ #
21
+ # Aliases are defined in the root config under the alias key
22
+ #
23
+ # Each alias is named and can only contain lowercase letters, numbers, dashes and underscores.
24
+
25
+ aliases:
26
+ uname: app exec -p -q -r web "uname -a"
@@ -1,10 +1,10 @@
1
1
  # Builder
2
2
  #
3
- # The builder configuration controls how the application is built with `docker build` or `docker buildx build`
3
+ # The builder configuration controls how the application is built with `docker build`
4
4
  #
5
5
  # If no configuration is specified, Kamal will:
6
- # 1. Create a buildx context called `kamal-<service>-multiarch`
7
- # 2. Use `docker buildx build` to build a multiarch image for linux/amd64,linux/arm64 with that context
6
+ # 1. Create a buildx context called `kamal-local-docker-container`, using the docker-container driver
7
+ # 2. Use `docker build` to build a multiarch image for linux/amd64,linux/arm64 with that context
8
8
  #
9
9
  # See https://kamal-deploy.org/docs/configuration/builder-examples/ for more information
10
10
 
@@ -12,36 +12,29 @@
12
12
  #
13
13
  # Options go under the builder key in the root configuration.
14
14
  builder:
15
-
16
- # Multiarch
15
+ # Driver
17
16
  #
18
- # Enables multiarch builds, defaults to `true`
19
- multiarch: false
17
+ # The build driver to use, defaults to `docker-container`
18
+ driver: docker
20
19
 
21
- # Local configuration
22
- #
23
- # The build configuration for local builds, only used if multiarch is enabled (the default)
20
+ # Arch
24
21
  #
25
- # If there is no remote configuration, by default we build for amd64 and arm64.
26
- # If you only want to build for one architecture, you can specify it here.
27
- # The docker socket is optional and uses the default docker host socket when not specified
28
- local:
29
- arch: amd64
30
- host: /var/run/docker.sock
22
+ # The architectures to build for, defaults to `[ amd64, arm64 ]`
23
+ # Unless you are using the docker driver, when it defaults to the local architecture
24
+ # You can set an array or just a single value
25
+ arch:
26
+ - amd64
31
27
 
32
28
  # Remote configuration
33
29
  #
34
- # The build configuration for remote builds, also only used if multiarch is enabled.
35
- # The arch is required and can be either amd64 or arm64.
36
- remote:
37
- arch: arm64
38
- host: ssh://docker@docker-builder
30
+ # If you have a remote builder, you can configure it here
31
+ remote: ssh://docker@docker-builder
39
32
 
40
33
  # Builder cache
41
34
  #
42
35
  # The type must be either 'gha' or 'registry'
43
36
  #
44
- # The image is only used for registry cache
37
+ # The image is only used for registry cache. Not compatible with the docker driver
45
38
  cache:
46
39
  type: registry
47
40
  options: mode=max
@@ -166,3 +166,9 @@ healthcheck:
166
166
  # Docker logging configuration, see kamal docs logging
167
167
  logging:
168
168
  ...
169
+
170
+ # Aliases
171
+ #
172
+ # Alias configuration, see kamal docs alias
173
+ aliases:
174
+ ...
@@ -0,0 +1,15 @@
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.commands.include?(name)
12
+ error "Alias '#{name}' conflicts with a built-in command."
13
+ end
14
+ end
15
+ end
@@ -5,5 +5,7 @@ class Kamal::Configuration::Validator::Builder < Kamal::Configuration::Validator
5
5
  if config["cache"] && config["cache"]["type"]
6
6
  error "Invalid cache type: #{config["cache"]["type"]}" unless [ "gha", "registry" ].include?(config["cache"]["type"])
7
7
  end
8
+
9
+ error "Builder arch not set" unless config["arch"].present?
8
10
  end
9
11
  end
@@ -13,32 +13,38 @@ class Kamal::Configuration::Validator
13
13
 
14
14
  private
15
15
  def validate_against_example!(validation_config, example)
16
- validate_type! validation_config, Hash
17
-
18
- check_unknown_keys! validation_config, example
19
-
20
- validation_config.each do |key, value|
21
- next if extension?(key)
22
- with_context(key) do
23
- example_value = example[key]
24
-
25
- if example_value == "..."
26
- validate_type! value, *(Array if key == :servers), Hash
27
- elsif key == "hosts"
28
- validate_servers! value
29
- elsif example_value.is_a?(Array)
30
- validate_array_of! value, example_value.first.class
31
- elsif example_value.is_a?(Hash)
32
- case key.to_s
33
- when "options", "args"
34
- validate_type! value, Hash
35
- when "labels"
36
- validate_hash_of! value, example_value.first[1].class
16
+ validate_type! validation_config, example.class
17
+
18
+ if example.class == Hash
19
+ check_unknown_keys! validation_config, example
20
+
21
+ validation_config.each do |key, value|
22
+ next if extension?(key)
23
+ with_context(key) do
24
+ example_value = example[key]
25
+
26
+ if example_value == "..."
27
+ validate_type! value, *(Array if key == :servers), Hash
28
+ elsif key == "hosts"
29
+ validate_servers! value
30
+ elsif example_value.is_a?(Array)
31
+ if key == "arch"
32
+ validate_array_of_or_type! value, example_value.first.class
33
+ else
34
+ validate_array_of! value, example_value.first.class
35
+ end
36
+ elsif example_value.is_a?(Hash)
37
+ case key.to_s
38
+ when "options", "args"
39
+ validate_type! value, Hash
40
+ when "labels"
41
+ validate_hash_of! value, example_value.first[1].class
42
+ else
43
+ validate_against_example! value, example_value
44
+ end
37
45
  else
38
- validate_against_example! value, example_value
46
+ validate_type! value, example_value.class
39
47
  end
40
- else
41
- validate_type! value, example_value.class
42
48
  end
43
49
  end
44
50
  end
@@ -69,6 +75,16 @@ class Kamal::Configuration::Validator
69
75
  value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
70
76
  end
71
77
 
78
+ def validate_array_of_or_type!(value, type)
79
+ if value.is_a?(Array)
80
+ validate_array_of! value, type
81
+ else
82
+ validate_type! value, type
83
+ end
84
+ rescue Kamal::ConfigurationError
85
+ type_error(Array, type)
86
+ end
87
+
72
88
  def validate_array_of!(array, type)
73
89
  validate_type! array, Array
74
90
 
@@ -11,7 +11,7 @@ class Kamal::Configuration
11
11
  delegate :argumentize, :optionize, to: Kamal::Utils
12
12
 
13
13
  attr_reader :destination, :raw_config
14
- attr_reader :accessories, :boot, :builder, :env, :healthcheck, :logging, :traefik, :servers, :ssh, :sshkit, :registry
14
+ attr_reader :accessories, :aliases, :boot, :builder, :env, :healthcheck, :logging, :traefik, :servers, :ssh, :sshkit, :registry
15
15
 
16
16
  include Validation
17
17
 
@@ -54,6 +54,7 @@ class Kamal::Configuration
54
54
  @registry = Registry.new(config: self)
55
55
 
56
56
  @accessories = @raw_config.accessories&.keys&.collect { |name| Accessory.new(name, config: self) } || []
57
+ @aliases = @raw_config.aliases&.keys&.to_h { |name| [ name, Alias.new(name, config: self) ] } || {}
57
58
  @boot = Boot.new(config: self)
58
59
  @builder = Builder.new(config: self)
59
60
  @env = Env.new(config: @raw_config.env || {})
data/lib/kamal/utils.rb CHANGED
@@ -77,4 +77,20 @@ module Kamal::Utils
77
77
  def stable_sort!(elements, &block)
78
78
  elements.sort_by!.with_index { |element, index| [ block.call(element), index ] }
79
79
  end
80
+
81
+ def join_commands(commands)
82
+ commands.map(&:strip).join(" ")
83
+ end
84
+
85
+ def docker_arch
86
+ arch = `docker info --format '{{.Architecture}}'`.strip
87
+ case arch
88
+ when /aarch64/
89
+ "arm64"
90
+ when /x86_64/
91
+ "amd64"
92
+ else
93
+ arch
94
+ end
95
+ end
80
96
  end
data/lib/kamal/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kamal
2
- VERSION = "1.8.2"
2
+ VERSION = "2.0.0.alpha"
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.8.2
4
+ version: 2.0.0.alpha
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-08-28 00:00:00.000000000 Z
11
+ date: 2024-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -64,14 +64,14 @@ dependencies:
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '1.2'
67
+ version: '1.3'
68
68
  type: :runtime
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '1.2'
74
+ version: '1.3'
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: dotenv
77
77
  requirement: !ruby/object:Gem::Requirement
@@ -211,6 +211,7 @@ files:
211
211
  - lib/kamal.rb
212
212
  - lib/kamal/cli.rb
213
213
  - lib/kamal/cli/accessory.rb
214
+ - lib/kamal/cli/alias/command.rb
214
215
  - lib/kamal/cli/app.rb
215
216
  - lib/kamal/cli/app/boot.rb
216
217
  - lib/kamal/cli/app/prepare_assets.rb
@@ -252,11 +253,9 @@ files:
252
253
  - lib/kamal/commands/builder.rb
253
254
  - lib/kamal/commands/builder/base.rb
254
255
  - lib/kamal/commands/builder/clone.rb
255
- - lib/kamal/commands/builder/multiarch.rb
256
- - lib/kamal/commands/builder/multiarch/remote.rb
257
- - lib/kamal/commands/builder/native.rb
258
- - lib/kamal/commands/builder/native/cached.rb
259
- - lib/kamal/commands/builder/native/remote.rb
256
+ - lib/kamal/commands/builder/hybrid.rb
257
+ - lib/kamal/commands/builder/local.rb
258
+ - lib/kamal/commands/builder/remote.rb
260
259
  - lib/kamal/commands/docker.rb
261
260
  - lib/kamal/commands/hook.rb
262
261
  - lib/kamal/commands/lock.rb
@@ -266,9 +265,11 @@ files:
266
265
  - lib/kamal/commands/traefik.rb
267
266
  - lib/kamal/configuration.rb
268
267
  - lib/kamal/configuration/accessory.rb
268
+ - lib/kamal/configuration/alias.rb
269
269
  - lib/kamal/configuration/boot.rb
270
270
  - lib/kamal/configuration/builder.rb
271
271
  - lib/kamal/configuration/docs/accessory.yml
272
+ - lib/kamal/configuration/docs/alias.yml
272
273
  - lib/kamal/configuration/docs/boot.yml
273
274
  - lib/kamal/configuration/docs/builder.yml
274
275
  - lib/kamal/configuration/docs/configuration.yml
@@ -294,6 +295,7 @@ files:
294
295
  - lib/kamal/configuration/validation.rb
295
296
  - lib/kamal/configuration/validator.rb
296
297
  - lib/kamal/configuration/validator/accessory.rb
298
+ - lib/kamal/configuration/validator/alias.rb
297
299
  - lib/kamal/configuration/validator/builder.rb
298
300
  - lib/kamal/configuration/validator/configuration.rb
299
301
  - lib/kamal/configuration/validator/env.rb
@@ -327,7 +329,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
327
329
  - !ruby/object:Gem::Version
328
330
  version: '0'
329
331
  requirements: []
330
- rubygems_version: 3.5.11
332
+ rubygems_version: 3.5.17
331
333
  signing_key:
332
334
  specification_version: 4
333
335
  summary: Deploy web apps in containers to servers running Docker with zero downtime.
@@ -1,61 +0,0 @@
1
- class Kamal::Commands::Builder::Multiarch::Remote < Kamal::Commands::Builder::Multiarch
2
- def create
3
- combine \
4
- create_contexts,
5
- create_local_buildx,
6
- append_remote_buildx
7
- end
8
-
9
- def remove
10
- combine \
11
- remove_contexts,
12
- super
13
- end
14
-
15
- def context_hosts
16
- chain \
17
- context_host(builder_name_with_arch(local_arch)),
18
- context_host(builder_name_with_arch(remote_arch))
19
- end
20
-
21
- def config_context_hosts
22
- [ local_host, remote_host ].compact
23
- end
24
-
25
- private
26
- def builder_name
27
- super + "-remote"
28
- end
29
-
30
- def builder_name_with_arch(arch)
31
- "#{builder_name}-#{arch}"
32
- end
33
-
34
- def create_local_buildx
35
- docker :buildx, :create, "--name", builder_name, builder_name_with_arch(local_arch), "--platform", "linux/#{local_arch}"
36
- end
37
-
38
- def append_remote_buildx
39
- docker :buildx, :create, "--append", "--name", builder_name, builder_name_with_arch(remote_arch), "--platform", "linux/#{remote_arch}"
40
- end
41
-
42
- def create_contexts
43
- combine \
44
- create_context(local_arch, local_host),
45
- create_context(remote_arch, remote_host)
46
- end
47
-
48
- def create_context(arch, host)
49
- docker :context, :create, builder_name_with_arch(arch), "--description", "'#{builder_name} #{arch} native host'", "--docker", "'host=#{host}'"
50
- end
51
-
52
- def remove_contexts
53
- combine \
54
- remove_context(local_arch),
55
- remove_context(remote_arch)
56
- end
57
-
58
- def remove_context(arch)
59
- docker :context, :rm, builder_name_with_arch(arch)
60
- end
61
- end
@@ -1,41 +0,0 @@
1
- class Kamal::Commands::Builder::Multiarch < Kamal::Commands::Builder::Base
2
- def create
3
- docker :buildx, :create, "--use", "--name", builder_name
4
- end
5
-
6
- def remove
7
- docker :buildx, :rm, builder_name
8
- end
9
-
10
- def info
11
- combine \
12
- docker(:context, :ls),
13
- docker(:buildx, :ls)
14
- end
15
-
16
- def push
17
- docker :buildx, :build,
18
- "--push",
19
- "--platform", platform_names,
20
- "--builder", builder_name,
21
- *build_options,
22
- build_context
23
- end
24
-
25
- def context_hosts
26
- docker :buildx, :inspect, builder_name, "> /dev/null"
27
- end
28
-
29
- private
30
- def builder_name
31
- "kamal-#{config.service}-multiarch"
32
- end
33
-
34
- def platform_names
35
- if local_arch
36
- "linux/#{local_arch}"
37
- else
38
- "linux/amd64,linux/arm64"
39
- end
40
- end
41
- end
@@ -1,25 +0,0 @@
1
- class Kamal::Commands::Builder::Native::Cached < Kamal::Commands::Builder::Native
2
- def create
3
- docker :buildx, :create, "--name", builder_name, "--use", "--driver=docker-container"
4
- end
5
-
6
- def remove
7
- docker :buildx, :rm, builder_name
8
- end
9
-
10
- def push
11
- docker :buildx, :build,
12
- "--push",
13
- *build_options,
14
- build_context
15
- end
16
-
17
- def context_hosts
18
- docker :buildx, :inspect, builder_name, "> /dev/null"
19
- end
20
-
21
- private
22
- def builder_name
23
- "kamal-#{config.service}-native-cached"
24
- end
25
- end
@@ -1,67 +0,0 @@
1
- class Kamal::Commands::Builder::Native::Remote < Kamal::Commands::Builder::Native
2
- def create
3
- chain \
4
- create_context,
5
- create_buildx
6
- end
7
-
8
- def remove
9
- chain \
10
- remove_context,
11
- remove_buildx
12
- end
13
-
14
- def info
15
- chain \
16
- docker(:context, :ls),
17
- docker(:buildx, :ls)
18
- end
19
-
20
- def push
21
- docker :buildx, :build,
22
- "--push",
23
- "--platform", platform,
24
- "--builder", builder_name,
25
- *build_options,
26
- build_context
27
- end
28
-
29
- def context_hosts
30
- context_host(builder_name_with_arch)
31
- end
32
-
33
- def config_context_hosts
34
- [ remote_host ]
35
- end
36
-
37
-
38
- private
39
- def builder_name
40
- "kamal-#{config.service}-native-remote"
41
- end
42
-
43
- def builder_name_with_arch
44
- "#{builder_name}-#{remote_arch}"
45
- end
46
-
47
- def platform
48
- "linux/#{remote_arch}"
49
- end
50
-
51
- def create_context
52
- docker :context, :create,
53
- builder_name_with_arch, "--description", "'#{builder_name} #{remote_arch} native host'", "--docker", "'host=#{remote_host}'"
54
- end
55
-
56
- def remove_context
57
- docker :context, :rm, builder_name_with_arch
58
- end
59
-
60
- def create_buildx
61
- docker :buildx, :create, "--name", builder_name, builder_name_with_arch, "--platform", platform
62
- end
63
-
64
- def remove_buildx
65
- docker :buildx, :rm, builder_name
66
- end
67
- end
@@ -1,20 +0,0 @@
1
- class Kamal::Commands::Builder::Native < Kamal::Commands::Builder::Base
2
- def create
3
- # No-op on native without cache
4
- end
5
-
6
- def remove
7
- # No-op on native without cache
8
- end
9
-
10
- def info
11
- # No-op on native
12
- end
13
-
14
- def push
15
- combine \
16
- docker(:build, *build_options, build_context),
17
- docker(:push, config.absolute_image),
18
- docker(:push, config.latest_image)
19
- end
20
- end