kamal 1.8.3 → 2.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67aa8d9022e0dd44e1be0a0ab8c26f20dd458fb469cf5e9d461dd58871712d8d
4
- data.tar.gz: 286d81788711c43f783e7a4bebc861bf89eeb948f7bebb622686e9947151296d
3
+ metadata.gz: 4df682847a5becbc623450203537d42aa29fb4b001026a6b39ab266913660d4d
4
+ data.tar.gz: 97ceb4c375f99605f1d874637273168f7274e95c615611d1d324cb0d635523e3
5
5
  SHA512:
6
- metadata.gz: dad7512c1f60ab760bb4cbe5183f8af92b8388697d7778fef2def69bed50be1e90befa09e765e30ad60af58548db69e8184df82053c6f9c1c28e3fa1549373de
7
- data.tar.gz: 75b5950ba4d744e9c7040c0531efcc2c781d13b1910d14226f6241424a6aba87aa0b24dcc249e13248f9c6da64fe2248fadffffbda630033a71ba78a2b3dd4c6
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.3"
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.3
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-09-02 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,65 +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
-
62
- def platform_names
63
- "linux/#{local_arch},linux/#{remote_arch}"
64
- end
65
- 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