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!
This commit is contained in:
committed by
Donal McBreen
parent
f48987aa03
commit
b8af719bb7
9
lib/kamal/cli/alias/command.rb
Normal file
9
lib/kamal/cli/alias/command.rb
Normal 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
|
||||
@@ -6,7 +6,8 @@ module Kamal::Cli
|
||||
class Base < Thor
|
||||
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 :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"
|
||||
|
||||
def initialize(*)
|
||||
super
|
||||
def initialize(args = [], local_options = {}, config = {})
|
||||
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
|
||||
load_env
|
||||
initialize_commander(options_with_subcommand_class_options)
|
||||
|
||||
@@ -27,7 +27,11 @@ class Kamal::Commander
|
||||
|
||||
def specific_primary!
|
||||
@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
|
||||
|
||||
def specific_roles=(role_names)
|
||||
@@ -113,6 +117,10 @@ class Kamal::Commander
|
||||
@traefik ||= Kamal::Commands::Traefik.new(config)
|
||||
end
|
||||
|
||||
def alias(name)
|
||||
config.aliases[name]
|
||||
end
|
||||
|
||||
|
||||
def with_verbosity(level)
|
||||
old_level = self.verbosity
|
||||
|
||||
@@ -11,7 +11,7 @@ class Kamal::Configuration
|
||||
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||
|
||||
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
|
||||
|
||||
@@ -54,6 +54,7 @@ class Kamal::Configuration
|
||||
@registry = Registry.new(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)
|
||||
@builder = Builder.new(config: self)
|
||||
@env = Env.new(config: @raw_config.env || {})
|
||||
|
||||
15
lib/kamal/configuration/alias.rb
Normal file
15
lib/kamal/configuration/alias.rb
Normal 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
|
||||
26
lib/kamal/configuration/docs/alias.yml
Normal file
26
lib/kamal/configuration/docs/alias.yml
Normal 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"
|
||||
@@ -166,3 +166,9 @@ healthcheck:
|
||||
# Docker logging configuration, see kamal docs logging
|
||||
logging:
|
||||
...
|
||||
|
||||
# Aliases
|
||||
#
|
||||
# Alias configuration, see kamal docs alias
|
||||
aliases:
|
||||
...
|
||||
|
||||
@@ -13,32 +13,34 @@ class Kamal::Configuration::Validator
|
||||
|
||||
private
|
||||
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|
|
||||
next if extension?(key)
|
||||
with_context(key) do
|
||||
example_value = example[key]
|
||||
validation_config.each do |key, value|
|
||||
next if extension?(key)
|
||||
with_context(key) do
|
||||
example_value = example[key]
|
||||
|
||||
if example_value == "..."
|
||||
validate_type! value, *(Array if key == :servers), Hash
|
||||
elsif key == "hosts"
|
||||
validate_servers! value
|
||||
elsif example_value.is_a?(Array)
|
||||
validate_array_of! value, example_value.first.class
|
||||
elsif example_value.is_a?(Hash)
|
||||
case key.to_s
|
||||
when "options", "args"
|
||||
validate_type! value, Hash
|
||||
when "labels"
|
||||
validate_hash_of! value, example_value.first[1].class
|
||||
if example_value == "..."
|
||||
validate_type! value, *(Array if key == :servers), Hash
|
||||
elsif key == "hosts"
|
||||
validate_servers! value
|
||||
elsif example_value.is_a?(Array)
|
||||
validate_array_of! value, example_value.first.class
|
||||
elsif example_value.is_a?(Hash)
|
||||
case key.to_s
|
||||
when "options", "args"
|
||||
validate_type! value, Hash
|
||||
when "labels"
|
||||
validate_hash_of! value, example_value.first[1].class
|
||||
else
|
||||
validate_against_example! value, example_value
|
||||
end
|
||||
else
|
||||
validate_against_example! value, example_value
|
||||
validate_type! value, example_value.class
|
||||
end
|
||||
else
|
||||
validate_type! value, example_value.class
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
15
lib/kamal/configuration/validator/alias.rb
Normal file
15
lib/kamal/configuration/validator/alias.rb
Normal 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
|
||||
Reference in New Issue
Block a user