From c8100d1f26a4f1b4b09d6c43b9964b43a33ca093 Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Wed, 3 Apr 2024 15:41:54 +0100 Subject: [PATCH] Tidy up role and host commander setup Extract Kamal::Commander::Specifics to deal with host and role setup and ensure that primary hosts and roles always come first. This means that in a rolling deploy we deploy to the primary ones first. This will be important when we remove the healthcheck step as we want to confirm the primary host can be deployed to before completing a deployment for other roles. By setting the hosts and roles all together in one place we can sort the primary ones to the front without creating infinite loops. --- lib/kamal/commander.rb | 42 ++++++--------------------- lib/kamal/commander/specifics.rb | 49 ++++++++++++++++++++++++++++++++ lib/kamal/utils.rb | 4 +++ test/commander_test.rb | 3 +- 4 files changed, 64 insertions(+), 34 deletions(-) create mode 100644 lib/kamal/commander/specifics.rb diff --git a/lib/kamal/commander.rb b/lib/kamal/commander.rb index 19e4c6cd..042e8429 100644 --- a/lib/kamal/commander.rb +++ b/lib/kamal/commander.rb @@ -3,11 +3,13 @@ require "active_support/core_ext/module/delegation" class Kamal::Commander attr_accessor :verbosity, :holding_lock, :hold_lock_on_error + delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :traefik_hosts, :accessory_hosts, to: :specifics def initialize self.verbosity = :info self.holding_lock = false self.hold_lock_on_error = false + @specifics = nil end def config @@ -24,10 +26,12 @@ class Kamal::Commander attr_reader :specific_roles, :specific_hosts def specific_primary! + @specifics = nil self.specific_hosts = [ config.primary_host ] end def specific_roles=(role_names) + @specifics = nil if role_names.present? @specific_roles = Kamal::Utils.filter_specific_items(role_names, config.roles) @@ -40,6 +44,7 @@ class Kamal::Commander end def specific_hosts=(hosts) + @specifics = nil if hosts.present? @specific_hosts = Kamal::Utils.filter_specific_items(hosts, config.all_hosts) @@ -51,39 +56,6 @@ class Kamal::Commander end end - def primary_host - # Given a list of specific roles, make an effort to match up with the primary_role - specific_hosts&.first || specific_roles&.detect { |role| role == config.primary_role }&.primary_host || specific_roles&.first&.primary_host || config.primary_host - end - - def primary_role - roles_on(primary_host).first - end - - def roles - (specific_roles || config.roles).select do |role| - ((specific_hosts || config.all_hosts) & role.hosts).any? - end - end - - def hosts - (specific_hosts || config.all_hosts).select do |host| - (specific_roles || config.roles).flat_map(&:hosts).include?(host) - end - end - - def roles_on(host) - roles.select { |role| role.hosts.include?(host.to_s) } - end - - def traefik_hosts - specific_hosts || config.traefik_hosts - end - - def accessory_hosts - specific_hosts || config.accessories.flat_map(&:hosts) - end - def accessory_names config.accessories&.collect(&:name) || [] end @@ -181,4 +153,8 @@ class Kamal::Commander SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs SSHKit.config.output_verbosity = verbosity end + + def specifics + @specifics ||= Kamal::Commander::Specifics.new(config, specific_hosts, specific_roles) + end end diff --git a/lib/kamal/commander/specifics.rb b/lib/kamal/commander/specifics.rb new file mode 100644 index 00000000..88d89a72 --- /dev/null +++ b/lib/kamal/commander/specifics.rb @@ -0,0 +1,49 @@ +class Kamal::Commander::Specifics + attr_reader :primary_host, :primary_role, :hosts, :roles + delegate :stable_sort!, to: Kamal::Utils + + def initialize(config, specific_hosts, specific_roles) + @config, @specific_hosts, @specific_roles = config, specific_hosts, specific_roles + + @roles, @hosts = specified_roles, specified_hosts + + @primary_host = specific_hosts&.first || primary_specific_role&.primary_host || config.primary_host + @primary_role = primary_or_first_role(roles_on(primary_host)) + + stable_sort!(roles) { |role| role == primary_role ? 0 : 1 } + stable_sort!(hosts) { |host| roles_on(host).any? { |role| role == primary_role } ? 0 : 1 } + end + + def roles_on(host) + roles.select { |role| role.hosts.include?(host.to_s) } + end + + def traefik_hosts + specific_hosts || config.traefik_hosts + end + + def accessory_hosts + specific_hosts || config.accessories.flat_map(&:hosts) + end + + private + attr_reader :config, :specific_hosts, :specific_roles + + def primary_specific_role + primary_or_first_role(specific_roles) if specific_roles.present? + end + + def primary_or_first_role(roles) + roles.detect { |role| role == config.primary_role } || roles.first + end + + def specified_roles + (specific_roles || config.roles) \ + .select { |role| ((specific_hosts || config.all_hosts) & role.hosts).any? } + end + + def specified_hosts + (specific_hosts || config.all_hosts) \ + .select { |host| (specific_roles || config.roles).flat_map(&:hosts).include?(host) } + end +end diff --git a/lib/kamal/utils.rb b/lib/kamal/utils.rb index 0229b8cb..af35edea 100644 --- a/lib/kamal/utils.rb +++ b/lib/kamal/utils.rb @@ -74,4 +74,8 @@ module Kamal::Utils matches end + + def stable_sort!(elements, &block) + elements.sort_by!.with_index { |element, index| [ block.call(element), index ] } + end end diff --git a/test/commander_test.rb b/test/commander_test.rb index 55d6cb1d..9eda1754 100644 --- a/test/commander_test.rb +++ b/test/commander_test.rb @@ -119,9 +119,10 @@ class CommanderTest < ActiveSupport::TestCase configure_with(:deploy_primary_web_role_override) @kamal.specific_roles = [ "web_*" ] - assert_equal [ "web_chicago", "web_tokyo" ], @kamal.roles.map(&:name) + assert_equal [ "web_tokyo", "web_chicago" ], @kamal.roles.map(&:name) assert_equal "web_tokyo", @kamal.primary_role.name assert_equal "1.1.1.3", @kamal.primary_host + assert_equal [ "1.1.1.3", "1.1.1.4", "1.1.1.1", "1.1.1.2" ], @kamal.hosts end private