Compare commits

...

24 Commits

Author SHA1 Message Date
Donal McBreen
459ba95bbf Revert "Simplify builders config" 2024-08-29 20:16:34 +01:00
Donal McBreen
6adf3c117f Merge pull request #905 from basecamp/simplify-builders-config
Simplify builders config
2024-08-29 09:28:51 +01:00
Donal McBreen
9f0b10425c Fix aliases tests 2024-08-29 09:16:07 +01:00
Donal McBreen
5f2384f123 Use docker info to get arch 2024-08-29 08:46:18 +01:00
Donal McBreen
eab7d3adc5 Keep buildx build, in case of old docker versions which don't default to buildkit 2024-08-29 08:45:51 +01:00
Donal McBreen
d2d0223c37 Require an arch to be set, and default to amd64 in the template 2024-08-29 08:45:51 +01:00
Donal McBreen
56268d724d Simplify the builders configuration
1. Add driver as an option, defaulting to `docker-container`. For a
   "native" build you can set it to `docker`
2. Set arch as a array of architectures to build for, defaulting to
   `[ "amd64", "arm64" ]` unless you are using the docker driver in
   which case we default to not setting a platform
3. Remote is now just a connection string for the remote builder
4. If remote is set, we only use it for non-local arches, if we are
   only building for the local arch, we'll ignore it.

Examples:

On arm64, build for arm64 locally, amd64 remotely or
On amd64, build for amd64 locally, arm64 remotely:

```yaml
builder:
  remote: ssh://docker@docker-builder
```

On arm64, build amd64 on remote,
On amd64 build locally:

```yaml
builder:
  arch:
    - amd64
  remote:
    host: ssh://docker@docker-builder
```

Build amd64 on local:

```yaml
builder:
  arch:
    - amd64
```

Use docker driver, building for local arch:

```yaml
builder:
  driver: docker
```
2024-08-29 08:45:48 +01:00
Donal McBreen
cffb6c3d7e Allow the driver to be set 2024-08-29 08:44:11 +01:00
Donal McBreen
bd1726f305 docker buildx build -> docker build 2024-08-29 08:44:11 +01:00
Donal McBreen
7ddb122a22 Get tests passing 2024-08-29 08:44:11 +01:00
Donal McBreen
98c951bbdb Simplfy choosing a builder 2024-08-29 08:44:11 +01:00
Donal McBreen
374c117b79 Validate multiarch configuration
Remote and local are only allowed when multiarch is enabled.
Remote requires a host and arch, local only requires an arch.
2024-08-29 08:44:11 +01:00
Donal McBreen
d6a5cf3c78 Rip out context_hosts checks
The remote host is now encoded in the builder name so we don't need
to check it. We'll just do an inspect to confirm the builder exists.
2024-08-29 08:44:11 +01:00
Donal McBreen
2aeabda455 Move multiarch remote builder to hybrid builder
Include the host name in the builder name, so we can have one builder
per host/arch across all kamal projects.

Inherit from the remote builder. The difference in the hybrid builder
is that we create a local buildx instance and append the remote context
to it.
2024-08-29 08:44:11 +01:00
Donal McBreen
c048c097ed Create a context for local builds
This ensures we use the docker-container driver and not whatever the
local default is.
2024-08-29 08:44:11 +01:00
Donal McBreen
ed148628fb Local build doesn't need a builder 2024-08-29 08:44:11 +01:00
Donal McBreen
d48080c772 Dump native builder
We already ensure that buildx is installed, so let's always use it.
2024-08-29 08:44:11 +01:00
Donal McBreen
3f64338929 Move native remote to just remote
It's just a remote builder, that will build whichever platform is asked
for, so let's remove the "native" part.

We'll also remove the service name from the builder name, so multiple
services can share the same builder.
2024-08-29 08:44:11 +01:00
Donal McBreen
0ab838bc25 Combine multiarch and native/cache builders
Combine the two builders, as they are almost identical. The only
difference was whether the platforms were set.

The native cached builder wasn't using the context it created, so now
we do.

We'll set the driver to `docker-container` - it seems to be the default
but the Docker docs claim it is `docker`.
2024-08-29 08:44:11 +01:00
Donal McBreen
b7382ceeaf Merge pull request #912 from basecamp/alias
Add aliases to Kamal
2024-08-29 08:43:35 +01:00
Donal McBreen
69367fbc6b Merge pull request #917 from basecamp/v2.0-alpha
Switch the version on main to 2.0.0.alpha
2024-08-29 08:43:19 +01:00
Donal McBreen
2515bd705c Switch the version on main to 2.0.0.alpha
All development is now for the 2.0.0 release.
2024-08-29 08:33:21 +01:00
Donal McBreen
579e169be2 Allow multiple arguments for exec commands
If you can have an alias like:

```
aliases:
  rails: app exec -p rails
```

Then `kamal rails db:migrate:status` will execute
`kamal app exec -p rails db:migrate:status`.

So this works, we'll allow multiple arguments `app exec` and
`server exec` to accept multiple arguments.

The arguments are combined by simply joining them with a space. This
means that these are equivalent:

```
kamal app exec -p rails db:migrate:status
kamal app exec -p "rails db:migrate:status"
```

If you want to pass an argument with spaces, you'll need to quote it:

```
kamal app exec -p "git commit -am \"My comment\""
kamal app exec -p git commit -am "\"My comment\""
```
2024-08-28 10:58:25 +01:00
Donal McBreen
b8af719bb7 Add aliases to Kamal
Aliases are defined in the configuration file under the `aliases` key.

The configuration is a map of alias name to command. When we run the
command the we just do a literal replacement of the alias with the
string.

So if we have:

```yaml
aliases:
  console: app exec -r console -i --reuse "rails console"
```

Then running `kamal console -r workers` will run the command

```sh
$ kamal app exec -r console -i --reuse "rails console" -r workers
```

Because of the order Thor parses the arguments, this allows us to
override the role from the alias command.

There might be cases where we need to munge the command a bit more but
that would involve getting into Thor command parsing internals,
which are complicated and possibly subject to change.

There's a chance that your aliases could conflict with future built-in
commands, but there's not likely to be many of those and if it happens
you'll get a validation error when you upgrade.

Thanks to @dhnaranjo for the idea!
2024-08-26 10:47:43 +01:00
25 changed files with 249 additions and 53 deletions

View File

@@ -1,7 +1,7 @@
PATH PATH
remote: . remote: .
specs: specs:
kamal (1.8.2) kamal (2.0.0.alpha)
activesupport (>= 7.0) activesupport (>= 7.0)
base64 (~> 0.2) base64 (~> 0.2)
bcrypt_pbkdf (~> 1.0) bcrypt_pbkdf (~> 1.0)
@@ -10,7 +10,7 @@ PATH
ed25519 (~> 1.2) ed25519 (~> 1.2)
net-ssh (~> 7.0) net-ssh (~> 7.0)
sshkit (>= 1.23.0, < 2.0) sshkit (>= 1.23.0, < 2.0)
thor (~> 1.2) thor (~> 1.3)
zeitwerk (~> 2.5) zeitwerk (~> 2.5)
GEM GEM

View File

@@ -17,6 +17,7 @@ end
DOCS = { DOCS = {
"accessory" => "Accessories", "accessory" => "Accessories",
"alias" => "Aliases",
"boot" => "Booting", "boot" => "Booting",
"builder" => "Builders", "builder" => "Builders",
"configuration" => "Configuration overview", "configuration" => "Configuration overview",
@@ -67,26 +68,27 @@ class DocWriter
output.puts output.puts
place = :new_section place = :new_section
elsif line =~ /^ *#/ elsif line =~ /^ *#/
generate_line(line, place: place) generate_line(line, heading: place == :new_section)
place = :in_section place = :in_section
else else
output.puts "```yaml" output.puts "```yaml"
output.print line output.puts line
place = :in_yaml place = :in_yaml
end end
when :in_yaml when :in_yaml, :in_empty_line_yaml
if line =~ /^ *#/ if line =~ /^ *#/
output.puts "```" output.puts "```"
generate_line(line, place: :new_section) generate_line(line, heading: place == :in_empty_line_yaml)
place = :in_section place = :in_section
elsif line.empty?
place = :in_empty_line_yaml
else else
output.puts output.puts line
output.print line
end end
end end
end end
output.puts "\n```" if place == :in_yaml output.puts "```" if place == :in_yaml
end end
def generate_header def generate_header
@@ -98,7 +100,7 @@ class DocWriter
output.puts output.puts
end end
def generate_line(line, place: :in_section) def generate_line(line, heading: false)
line = line.gsub(/^ *#\s?/, "") line = line.gsub(/^ *#\s?/, "")
if line =~ /(.*)kamal docs ([a-z]*)(.*)/ if line =~ /(.*)kamal docs ([a-z]*)(.*)/
@@ -109,7 +111,7 @@ class DocWriter
line = "#{$1}[#{titlify($2.split("/").last)}](#{$2})#{$3}" line = "#{$1}[#{titlify($2.split("/").last)}](#{$2})#{$3}"
end end
if place == :new_section if heading
output.puts "## [#{line}](##{linkify(line)})" output.puts "## [#{line}](##{linkify(line)})"
else else
output.puts line output.puts line

View File

@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
spec.add_dependency "activesupport", ">= 7.0" spec.add_dependency "activesupport", ">= 7.0"
spec.add_dependency "sshkit", ">= 1.23.0", "< 2.0" spec.add_dependency "sshkit", ">= 1.23.0", "< 2.0"
spec.add_dependency "net-ssh", "~> 7.0" spec.add_dependency "net-ssh", "~> 7.0"
spec.add_dependency "thor", "~> 1.2" spec.add_dependency "thor", "~> 1.3"
spec.add_dependency "dotenv", "~> 2.8" spec.add_dependency "dotenv", "~> 2.8"
spec.add_dependency "zeitwerk", "~> 2.5" spec.add_dependency "zeitwerk", "~> 2.5"
spec.add_dependency "ed25519", "~> 1.2" spec.add_dependency "ed25519", "~> 1.2"

View File

@@ -0,0 +1,9 @@
class Kamal::Cli::Alias::Command < Thor::DynamicCommand
def run(instance, args = [])
if (_alias = KAMAL.config.aliases[name])
Kamal::Cli::Main.start(Shellwords.split(_alias.command) + ARGV[1..-1])
else
super
end
end
end

View File

@@ -71,11 +71,12 @@ class Kamal::Cli::App < Kamal::Cli::Base
end end
end end
desc "exec [CMD]", "Execute a custom command on servers within the app container (use --help to show options)" desc "exec [CMD...]", "Execute a custom command on servers within the app container (use --help to show options)"
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)" option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one" option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command" option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
def exec(cmd) def exec(*cmd)
cmd = Kamal::Utils.join_commands(cmd)
env = options[:env] env = options[:env]
case case
when options[:interactive] && options[:reuse] when options[:interactive] && options[:reuse]

View File

@@ -6,7 +6,8 @@ module Kamal::Cli
class Base < Thor class Base < Thor
include SSHKit::DSL include SSHKit::DSL
def self.exit_on_failure?() true end def self.exit_on_failure?() false end
def self.dynamic_command_class() Kamal::Cli::Alias::Command end
class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging" class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
class_option :quiet, type: :boolean, aliases: "-q", desc: "Minimal logging" class_option :quiet, type: :boolean, aliases: "-q", desc: "Minimal logging"
@@ -22,8 +23,14 @@ module Kamal::Cli
class_option :skip_hooks, aliases: "-H", type: :boolean, default: false, desc: "Don't run hooks" class_option :skip_hooks, aliases: "-H", type: :boolean, default: false, desc: "Don't run hooks"
def initialize(*) def initialize(args = [], local_options = {}, config = {})
super if config[:current_command].is_a?(Kamal::Cli::Alias::Command)
# When Thor generates a dynamic command, it doesn't attempt to parse the arguments.
# For our purposes, it means the arguments are passed in args rather than local_options.
super([], args, config)
else
super
end
@original_env = ENV.to_h.dup @original_env = ENV.to_h.dup
load_env load_env
initialize_commander(options_with_subcommand_class_options) initialize_commander(options_with_subcommand_class_options)

View File

@@ -1,7 +1,8 @@
class Kamal::Cli::Server < Kamal::Cli::Base class Kamal::Cli::Server < Kamal::Cli::Base
desc "exec", "Run a custom command on the server (use --help to show options)" desc "exec", "Run a custom command on the server (use --help to show options)"
option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)" option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)"
def exec(cmd) def exec(*cmd)
cmd = Kamal::Utils.join_commands(cmd)
hosts = KAMAL.hosts | KAMAL.accessory_hosts hosts = KAMAL.hosts | KAMAL.accessory_hosts
case case

View File

@@ -27,7 +27,11 @@ class Kamal::Commander
def specific_primary! def specific_primary!
@specifics = nil @specifics = nil
self.specific_hosts = [ config.primary_host ] if specific_roles.present?
self.specific_hosts = [ specific_roles.first.primary_host ]
else
self.specific_hosts = [ config.primary_host ]
end
end end
def specific_roles=(role_names) def specific_roles=(role_names)
@@ -113,6 +117,10 @@ class Kamal::Commander
@traefik ||= Kamal::Commands::Traefik.new(config) @traefik ||= Kamal::Commands::Traefik.new(config)
end end
def alias(name)
config.aliases[name]
end
def with_verbosity(level) def with_verbosity(level)
old_level = self.verbosity old_level = self.verbosity

View File

@@ -11,7 +11,7 @@ class Kamal::Configuration
delegate :argumentize, :optionize, to: Kamal::Utils delegate :argumentize, :optionize, to: Kamal::Utils
attr_reader :destination, :raw_config attr_reader :destination, :raw_config
attr_reader :accessories, :boot, :builder, :env, :healthcheck, :logging, :traefik, :servers, :ssh, :sshkit, :registry attr_reader :accessories, :aliases, :boot, :builder, :env, :healthcheck, :logging, :traefik, :servers, :ssh, :sshkit, :registry
include Validation include Validation
@@ -54,6 +54,7 @@ class Kamal::Configuration
@registry = Registry.new(config: self) @registry = Registry.new(config: self)
@accessories = @raw_config.accessories&.keys&.collect { |name| Accessory.new(name, config: self) } || [] @accessories = @raw_config.accessories&.keys&.collect { |name| Accessory.new(name, config: self) } || []
@aliases = @raw_config.aliases&.keys&.to_h { |name| [ name, Alias.new(name, config: self) ] } || {}
@boot = Boot.new(config: self) @boot = Boot.new(config: self)
@builder = Builder.new(config: self) @builder = Builder.new(config: self)
@env = Env.new(config: @raw_config.env || {}) @env = Env.new(config: @raw_config.env || {})

View File

@@ -0,0 +1,15 @@
class Kamal::Configuration::Alias
include Kamal::Configuration::Validation
attr_reader :name, :command
def initialize(name, config:)
@name, @command = name.inquiry, config.raw_config["aliases"][name]
validate! \
command,
example: validation_yml["aliases"]["uname"],
context: "aliases/#{name}",
with: Kamal::Configuration::Validator::Alias
end
end

View File

@@ -0,0 +1,26 @@
# Aliases
#
# Aliases are shortcuts for Kamal commands.
#
# For example, for a Rails app, you might open a console with:
#
# ```shell
# kamal app exec -i -r console "rails console"
# ```
#
# By defining an alias, like this:
aliases:
console: app exec -r console -i "rails console"
# You can now open the console with:
# ```shell
# kamal console
# ```
# Configuring aliases
#
# Aliases are defined in the root config under the alias key
#
# Each alias is named and can only contain lowercase letters, numbers, dashes and underscores.
aliases:
uname: app exec -p -q -r web "uname -a"

View File

@@ -166,3 +166,9 @@ healthcheck:
# Docker logging configuration, see kamal docs logging # Docker logging configuration, see kamal docs logging
logging: logging:
... ...
# Aliases
#
# Alias configuration, see kamal docs alias
aliases:
...

View File

@@ -13,32 +13,34 @@ class Kamal::Configuration::Validator
private private
def validate_against_example!(validation_config, example) def validate_against_example!(validation_config, example)
validate_type! validation_config, Hash validate_type! validation_config, example.class
check_unknown_keys! validation_config, example if example.class == Hash
check_unknown_keys! validation_config, example
validation_config.each do |key, value| validation_config.each do |key, value|
next if extension?(key) next if extension?(key)
with_context(key) do with_context(key) do
example_value = example[key] example_value = example[key]
if example_value == "..." if example_value == "..."
validate_type! value, *(Array if key == :servers), Hash validate_type! value, *(Array if key == :servers), Hash
elsif key == "hosts" elsif key == "hosts"
validate_servers! value validate_servers! value
elsif example_value.is_a?(Array) elsif example_value.is_a?(Array)
validate_array_of! value, example_value.first.class validate_array_of! value, example_value.first.class
elsif example_value.is_a?(Hash) elsif example_value.is_a?(Hash)
case key.to_s case key.to_s
when "options", "args" when "options", "args"
validate_type! value, Hash validate_type! value, Hash
when "labels" when "labels"
validate_hash_of! value, example_value.first[1].class validate_hash_of! value, example_value.first[1].class
else
validate_against_example! value, example_value
end
else else
validate_against_example! value, example_value validate_type! value, example_value.class
end end
else
validate_type! value, example_value.class
end end
end end
end end

View File

@@ -0,0 +1,15 @@
class Kamal::Configuration::Validator::Alias < Kamal::Configuration::Validator
def validate!
super
name = context.delete_prefix("aliases/")
if name !~ /\A[a-z0-9_-]+\z/
error "Invalid alias name: '#{name}'. Must only contain lowercase letters, alphanumeric, hyphens and underscores."
end
if Kamal::Cli::Main.commands.include?(name)
error "Alias '#{name}' conflicts with a built-in command."
end
end
end

View File

@@ -77,4 +77,8 @@ module Kamal::Utils
def stable_sort!(elements, &block) def stable_sort!(elements, &block)
elements.sort_by!.with_index { |element, index| [ block.call(element), index ] } elements.sort_by!.with_index { |element, index| [ block.call(element), index ] }
end end
def join_commands(commands)
commands.map(&:strip).join(" ")
end
end end

View File

@@ -1,3 +1,3 @@
module Kamal module Kamal
VERSION = "1.8.2" VERSION = "2.0.0.alpha"
end end

View File

@@ -247,6 +247,12 @@ class CliAppTest < CliTestCase
end end
end end
test "exec separate arguments" do
run_command("exec", "ruby", " -v").tap do |output|
assert_match "docker run --rm --env-file .kamal/env/roles/app-web.env dhh/app:latest ruby -v", output
end
end
test "exec with reuse" do test "exec with reuse" do
run_command("exec", "--reuse", "ruby -v").tap do |output| run_command("exec", "--reuse", "ruby -v").tap do |output|
assert_match "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest --format '\\''{{.ID}}'\\'') ; docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", output # Get current version assert_match "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest --format '\\''{{.ID}}'\\'') ; docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", output # Get current version

View File

@@ -63,4 +63,12 @@ class CliTestCase < ActiveSupport::TestCase
assert_match expected, output assert_match expected, output
end end
def with_argv(*argv)
old_argv = ARGV
ARGV.replace(*argv)
yield
ensure
ARGV.replace(old_argv)
end
end end

View File

@@ -537,9 +537,47 @@ class CliMainTest < CliTestCase
assert_equal Kamal::VERSION, version assert_equal Kamal::VERSION, version
end end
test "run an alias for details" do
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:traefik:details")
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:details")
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:details", [ "all" ])
run_command("info", config_file: "deploy_with_aliases")
end
test "run an alias for a console" do
run_command("console", config_file: "deploy_with_aliases").tap do |output|
assert_match "docker exec app-console-999 bin/console on 1.1.1.5", output
assert_match "App Host: 1.1.1.5", output
end
end
test "run an alias for a console overriding role" do
run_command("console", "-r", "workers", config_file: "deploy_with_aliases").tap do |output|
assert_match "docker exec app-workers-999 bin/console on 1.1.1.3", output
assert_match "App Host: 1.1.1.3", output
end
end
test "run an alias for a console passing command" do
run_command("exec", "bin/job", config_file: "deploy_with_aliases").tap do |output|
assert_match "docker exec app-console-999 bin/job on 1.1.1.5", output
assert_match "App Host: 1.1.1.5", output
end
end
test "append to command with an alias" do
run_command("rails", "db:migrate:status", config_file: "deploy_with_aliases").tap do |output|
assert_match "docker exec app-console-999 rails db:migrate:status on 1.1.1.5", output
assert_match "App Host: 1.1.1.5", output
end
end
private private
def run_command(*command, config_file: "deploy_simple") def run_command(*command, config_file: "deploy_simple")
stdouted { Kamal::Cli::Main.start([ *command, "-c", "test/fixtures/#{config_file}.yml" ]) } with_argv([ *command, "-c", "test/fixtures/#{config_file}.yml" ]) do
stdouted { Kamal::Cli::Main.start }
end
end end
def with_test_dotenv(**files) def with_test_dotenv(**files)

View File

@@ -3,8 +3,8 @@ require_relative "cli_test_case"
class CliServerTest < CliTestCase class CliServerTest < CliTestCase
test "running a command with exec" do test "running a command with exec" do
SSHKit::Backend::Abstract.any_instance.stubs(:capture) SSHKit::Backend::Abstract.any_instance.stubs(:capture)
.with("date", verbosity: 1) .with("date", verbosity: 1)
.returns("Today") .returns("Today")
hosts = "1.1.1.1".."1.1.1.4" hosts = "1.1.1.1".."1.1.1.4"
run_command("exec", "date").tap do |output| run_command("exec", "date").tap do |output|
@@ -15,6 +15,20 @@ class CliServerTest < CliTestCase
end end
end end
test "running a command with exec multiple arguments" do
SSHKit::Backend::Abstract.any_instance.stubs(:capture)
.with("date -j", verbosity: 1)
.returns("Today")
hosts = "1.1.1.1".."1.1.1.4"
run_command("exec", "date", "-j").tap do |output|
hosts.map do |host|
assert_match "Running 'date -j' on #{hosts.to_a.join(', ')}...", output
assert_match "App Host: #{host}\nToday", output
end
end
end
test "bootstrap already installed" do test "bootstrap already installed" do
stub_setup stub_setup
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(true).at_least_once SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(true).at_least_once

21
test/fixtures/deploy_with_aliases.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
service: app
image: dhh/app
servers:
web:
- 1.1.1.1
- 1.1.1.2
workers:
hosts:
- 1.1.1.3
- 1.1.1.4
console:
hosts:
- 1.1.1.5
registry:
username: user
password: pw
aliases:
info: details
console: app exec --reuse -p -r console "bin/console"
exec: app exec --reuse -p -r console
rails: app exec --reuse -p -r console rails

View File

@@ -1,8 +1,4 @@
#!/bin/sh #!/bin/sh
echo "About to lock..." echo "About to lock..."
if [ "$KAMAL_HOSTS" != "vm1,vm2" ]; then
echo "Expected hosts to be 'vm1,vm2', got $KAMAL_HOSTS"
exit 1
fi
mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/pre-connect mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/pre-connect

View File

@@ -1,8 +1,4 @@
#!/bin/sh #!/bin/sh
echo "About to lock..." echo "About to lock..."
if [ "$KAMAL_HOSTS" != "vm1,vm2,vm3" ]; then
echo "Expected hosts to be 'vm1,vm2,vm3', got $KAMAL_HOSTS"
exit 1
fi
mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/pre-connect mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/pre-connect

View File

@@ -37,3 +37,7 @@ accessories:
- web - web
stop_wait_time: 1 stop_wait_time: 1
readiness_delay: 0 readiness_delay: 0
aliases:
whome: version
worker_hostname: app exec -r workers -q --reuse hostname
uname: server exec -q -p uname

View File

@@ -82,6 +82,22 @@ class MainTest < IntegrationTest
assert_equal({ "cmd"=>"wget -qO- http://localhost > /dev/null || exit 1", "interval"=>"1s", "max_attempts"=>3, "port"=>3000, "path"=>"/up", "cord"=>"/tmp/kamal-cord", "log_lines"=>50 }, config[:healthcheck]) assert_equal({ "cmd"=>"wget -qO- http://localhost > /dev/null || exit 1", "interval"=>"1s", "max_attempts"=>3, "port"=>3000, "path"=>"/up", "cord"=>"/tmp/kamal-cord", "log_lines"=>50 }, config[:healthcheck])
end end
test "aliases" do
@app = "app_with_roles"
kamal :envify
kamal :deploy
output = kamal :whome, capture: true
assert_equal Kamal::VERSION, output
output = kamal :worker_hostname, capture: true
assert_match /App Host: vm3\nvm3-[0-9a-f]{12}$/, output
output = kamal :uname, "-o", capture: true
assert_match "App Host: vm1\nGNU/Linux", output
end
test "setup and remove" do test "setup and remove" do
# Check remove completes when nothing has been setup yet # Check remove completes when nothing has been setup yet
kamal :remove, "-y" kamal :remove, "-y"