diff --git a/lib/mrsk/cli/app.rb b/lib/mrsk/cli/app.rb index ffe44164..7fcf1c24 100644 --- a/lib/mrsk/cli/app.rb +++ b/lib/mrsk/cli/app.rb @@ -23,20 +23,20 @@ class Mrsk::Cli::App < Mrsk::Cli::Base option :version, desc: "Defaults to the most recent git-hash in local repository" def start if (version = options[:version]).present? - on(MRSK.config.hosts) { execute *MRSK.app.start(version: version) } + on(MRSK.hosts) { execute *MRSK.app.start(version: version) } else - on(MRSK.config.hosts) { execute *MRSK.app.start, raise_on_non_zero_exit: false } + on(MRSK.hosts) { execute *MRSK.app.start, raise_on_non_zero_exit: false } end end desc "stop", "Stop app on servers" def stop - on(MRSK.config.hosts) { execute *MRSK.app.stop, raise_on_non_zero_exit: false } + on(MRSK.hosts) { execute *MRSK.app.stop, raise_on_non_zero_exit: false } end desc "details", "Display details about app containers" def details - on(MRSK.config.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.info) } + on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.info) } end desc "exec [CMD]", "Execute a custom task on servers passed in as CMD='bin/rake some:task'" @@ -48,7 +48,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base if options[:once] on(MRSK.config.primary_host) { puts capture_with_info(*MRSK.app.send(runner, cmd)) } else - on(MRSK.config.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.send(runner, cmd)) } + on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.send(runner, cmd)) } end end @@ -80,18 +80,18 @@ class Mrsk::Cli::App < Mrsk::Cli::Base if options[:once] on(MRSK.config.primary_host) { puts capture_with_info(*MRSK.app.exec("bin/rails", "runner", "'#{expression}'")) } else - on(MRSK.config.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.exec("bin/rails", "runner", "'#{expression}'")) } + on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.exec("bin/rails", "runner", "'#{expression}'")) } end end desc "containers", "List all the app containers currently on servers" def containers - on(MRSK.config.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_containers) } + on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_containers) } end desc "current", "Return the current running container ID" def current - on(MRSK.config.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.current_container_id) } + on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.current_container_id) } end desc "logs", "Show last 100 log lines from app on servers" @@ -105,7 +105,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base lines = options[:lines] grep = options[:grep] - on(MRSK.config.hosts) do |host| + on(MRSK.hosts) do |host| begin puts_by_host host, capture_with_info(*MRSK.app.logs(since: since, lines: lines, grep: grep)) rescue SSHKit::Command::Failed @@ -119,12 +119,12 @@ class Mrsk::Cli::App < Mrsk::Cli::Base def remove case options[:only] when "containers" - on(MRSK.config.hosts) { execute *MRSK.app.remove_containers } + on(MRSK.hosts) { execute *MRSK.app.remove_containers } when "images" - on(MRSK.config.hosts) { execute *MRSK.app.remove_images } + on(MRSK.hosts) { execute *MRSK.app.remove_images } else - on(MRSK.config.hosts) { execute *MRSK.app.remove_containers } - on(MRSK.config.hosts) { execute *MRSK.app.remove_images } + on(MRSK.hosts) { execute *MRSK.app.remove_containers } + on(MRSK.hosts) { execute *MRSK.app.remove_images } end end end diff --git a/lib/mrsk/cli/base.rb b/lib/mrsk/cli/base.rb index 885449d6..e825d6c7 100644 --- a/lib/mrsk/cli/base.rb +++ b/lib/mrsk/cli/base.rb @@ -9,6 +9,9 @@ module Mrsk::Cli class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging" + class_option :hosts, aliases: "-h", desc: "Run commands on these hosts instead of all (separate by comma)" + class_option :roles, aliases: "-r", desc: "Run commands on these roles instead of all (separate by comma)" + class_option :config_file, aliases: "-c", default: "config/deploy.yml", desc: "Path to config file (default: config/deploy.yml)" class_option :destination, aliases: "-d", desc: "Specify destination to be used for config file (west -> deploy.west.yml)" @@ -22,6 +25,8 @@ module Mrsk::Cli MRSK.tap do |commander| commander.config_file = Pathname.new(File.expand_path(options[:config_file])) commander.destination = options[:destination] + commander.hosts = options[:hosts]&.split(",") + commander.roles = options[:roles]&.split(",") commander.verbose = options[:verbose] end end diff --git a/lib/mrsk/cli/build.rb b/lib/mrsk/cli/build.rb index 43017b13..89730f66 100644 --- a/lib/mrsk/cli/build.rb +++ b/lib/mrsk/cli/build.rb @@ -26,7 +26,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base desc "pull", "Pull app image from the registry onto servers" def pull - on(MRSK.config.hosts) { execute *MRSK.builder.pull } + on(MRSK.hosts) { execute *MRSK.builder.pull } end desc "create", "Create a local build setup" diff --git a/lib/mrsk/cli/main.rb b/lib/mrsk/cli/main.rb index a9bf9723..986d2c9a 100644 --- a/lib/mrsk/cli/main.rb +++ b/lib/mrsk/cli/main.rb @@ -32,7 +32,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base desc "rollback [VERSION]", "Rollback the app to VERSION (that must already be on servers)" def rollback(version) - on(MRSK.config.hosts) do + on(MRSK.hosts) do execute *MRSK.app.stop, raise_on_non_zero_exit: false execute *MRSK.app.start(version: version) end diff --git a/lib/mrsk/cli/prune.rb b/lib/mrsk/cli/prune.rb index 9dea45e6..37bb4f08 100644 --- a/lib/mrsk/cli/prune.rb +++ b/lib/mrsk/cli/prune.rb @@ -9,11 +9,11 @@ class Mrsk::Cli::Prune < Mrsk::Cli::Base desc "images", "Prune unused images older than 30 days" def images - on(MRSK.config.hosts) { execute *MRSK.prune.images } + on(MRSK.hosts) { execute *MRSK.prune.images } end desc "containers", "Prune stopped containers for the service older than 3 days" def containers - on(MRSK.config.hosts) { execute *MRSK.prune.containers } + on(MRSK.hosts) { execute *MRSK.prune.containers } end end diff --git a/lib/mrsk/cli/registry.rb b/lib/mrsk/cli/registry.rb index 958f1497..f86702ac 100644 --- a/lib/mrsk/cli/registry.rb +++ b/lib/mrsk/cli/registry.rb @@ -4,14 +4,14 @@ class Mrsk::Cli::Registry < Mrsk::Cli::Base desc "login", "Login to the registry locally and remotely" def login run_locally { execute *MRSK.registry.login } - on(MRSK.config.hosts) { execute *MRSK.registry.login } + on(MRSK.hosts) { execute *MRSK.registry.login } rescue ArgumentError => e puts e.message end desc "logout", "Logout of the registry remotely" def logout - on(MRSK.config.hosts) { execute *MRSK.registry.logout } + on(MRSK.hosts) { execute *MRSK.registry.logout } rescue ArgumentError => e puts e.message end diff --git a/lib/mrsk/cli/server.rb b/lib/mrsk/cli/server.rb index 46fcf754..542e26d6 100644 --- a/lib/mrsk/cli/server.rb +++ b/lib/mrsk/cli/server.rb @@ -3,6 +3,6 @@ require "mrsk/cli/base" class Mrsk::Cli::Server < Mrsk::Cli::Base desc "bootstrap", "Ensure Docker is installed on the servers" def bootstrap - on(MRSK.config.hosts) { execute "which docker || (apt-get update -y && apt-get install docker.io -y)" } + on(MRSK.hosts) { execute "which docker || (apt-get update -y && apt-get install docker.io -y)" } end end diff --git a/lib/mrsk/cli/traefik.rb b/lib/mrsk/cli/traefik.rb index 48fabfca..82f89267 100644 --- a/lib/mrsk/cli/traefik.rb +++ b/lib/mrsk/cli/traefik.rb @@ -3,17 +3,17 @@ require "mrsk/cli/base" class Mrsk::Cli::Traefik < Mrsk::Cli::Base desc "boot", "Boot Traefik on servers" def boot - on(MRSK.config.traefik_hosts) { execute *MRSK.traefik.run, raise_on_non_zero_exit: false } + on(MRSK.traefik_hosts) { execute *MRSK.traefik.run, raise_on_non_zero_exit: false } end desc "start", "Start existing Traefik on servers" def start - on(MRSK.config.traefik_hosts) { execute *MRSK.traefik.start, raise_on_non_zero_exit: false } + on(MRSK.traefik_hosts) { execute *MRSK.traefik.start, raise_on_non_zero_exit: false } end desc "stop", "Stop Traefik on servers" def stop - on(MRSK.config.traefik_hosts) { execute *MRSK.traefik.stop, raise_on_non_zero_exit: false } + on(MRSK.traefik_hosts) { execute *MRSK.traefik.stop, raise_on_non_zero_exit: false } end desc "restart", "Restart Traefik on servers" @@ -24,19 +24,19 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base desc "details", "Display details about Traefik containers from servers" def details - on(MRSK.config.traefik_hosts) { |host| puts_by_host host, capture_with_info(*MRSK.traefik.info), type: "Traefik" } + on(MRSK.traefik_hosts) { |host| puts_by_host host, capture_with_info(*MRSK.traefik.info), type: "Traefik" } end desc "logs", "Show last 100 log lines from Traefik on servers" def logs - on(MRSK.config.hosts) { |host| puts_by_host host, capture(*MRSK.traefik.logs), type: "Traefik" } + on(MRSK.hosts) { |host| puts_by_host host, capture(*MRSK.traefik.logs), type: "Traefik" } end desc "remove", "Remove Traefik container and image from servers" def remove invoke :stop - on(MRSK.config.traefik_hosts) do + on(MRSK.traefik_hosts) do execute *MRSK.traefik.remove_container execute *MRSK.traefik.remove_image end diff --git a/lib/mrsk/commander.rb b/lib/mrsk/commander.rb index 10e15b1d..dff49b0e 100644 --- a/lib/mrsk/commander.rb +++ b/lib/mrsk/commander.rb @@ -16,6 +16,22 @@ class Mrsk::Commander @config ||= Mrsk::Configuration.create_from(config_file, destination: destination).tap { |config| setup_with(config) } end + def hosts=(hosts) + @hosts = hosts if hosts.present? + end + + def roles=(role_names) + @hosts = config.roles.select { |r| role_names.include?(r.name) }.flat_map(&:hosts) if role_names.present? + end + + def hosts + @hosts || config.all_hosts + end + + def traefik_hosts + @hosts || config.traefik_hosts + end + def app @app ||= Mrsk::Commands::App.new(config) diff --git a/lib/mrsk/configuration.rb b/lib/mrsk/configuration.rb index 93ba4bcf..5891fb6d 100644 --- a/lib/mrsk/configuration.rb +++ b/lib/mrsk/configuration.rb @@ -48,23 +48,8 @@ class Mrsk::Configuration roles.detect { |r| r.name == name.to_s } end - def hosts - hosts = - case - when ENV["HOSTS"] - ENV["HOSTS"].split(",") - when ENV["ROLES"] - role_names = ENV["ROLES"].split(",") - roles.select { |r| role_names.include?(r.name) }.flat_map(&:hosts) - else - roles.flat_map(&:hosts) - end - - if hosts.any? - hosts - else - raise ArgumentError, "No hosts found" - end + def all_hosts + roles.flat_map(&:hosts) end def primary_host diff --git a/test/commander_test.rb b/test/commander_test.rb index 1c093599..558611c9 100644 --- a/test/commander_test.rb +++ b/test/commander_test.rb @@ -3,10 +3,27 @@ require "mrsk/commander" class CommanderTest < ActiveSupport::TestCase setup do - @mrsk = Mrsk::Commander.new config_file: Pathname.new(File.expand_path("fixtures/deploy.erb.yml", __dir__)) + @mrsk = Mrsk::Commander.new config_file: Pathname.new(File.expand_path("fixtures/deploy_with_roles.yml", __dir__)) end test "lazy configuration" do assert_equal Mrsk::Configuration, @mrsk.config.class end + + test "overwriting hosts" do + assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], @mrsk.hosts + + @mrsk.hosts = [ "1.2.3.4", "1.2.3.5" ] + assert_equal [ "1.2.3.4", "1.2.3.5" ], @mrsk.hosts + end + + test "overwriting hosts with roles" do + assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], @mrsk.hosts + + @mrsk.roles = [ "workers", "web" ] + assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], @mrsk.hosts + + @mrsk.roles = [ "workers" ] + assert_equal [ "1.1.1.3", "1.1.1.4" ], @mrsk.hosts + end end diff --git a/test/configuration_test.rb b/test/configuration_test.rb index 26450d24..d6ea9c5d 100644 --- a/test/configuration_test.rb +++ b/test/configuration_test.rb @@ -43,26 +43,9 @@ class ConfigurationTest < ActiveSupport::TestCase assert_nil @config.role(:missing) end - test "hosts" do - assert_equal [ "1.1.1.1", "1.1.1.2"], @config.hosts - assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], @config_with_roles.hosts - end - - test "hosts from ENV" do - ENV["HOSTS"] = "1.1.1.5,1.1.1.6" - assert_equal [ "1.1.1.5", "1.1.1.6"], @config.hosts - ensure - ENV["HOSTS"] = nil - end - - test "hosts from ENV roles" do - ENV["ROLES"] = "web,workers" - assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], @config_with_roles.hosts - - ENV["ROLES"] = "workers" - assert_equal [ "1.1.1.3", "1.1.1.4" ], @config_with_roles.hosts - ensure - ENV["ROLES"] = nil + test "all hosts" do + assert_equal [ "1.1.1.1", "1.1.1.2"], @config.all_hosts + assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], @config_with_roles.all_hosts end test "primary host" do @@ -127,10 +110,10 @@ class ConfigurationTest < ActiveSupport::TestCase dest_config_file = Pathname.new(File.expand_path("fixtures/deploy_for_dest.yml", __dir__)) config = Mrsk::Configuration.create_from dest_config_file, destination: "world" - assert_equal "1.1.1.1", config.hosts.first + assert_equal "1.1.1.1", config.all_hosts.first config = Mrsk::Configuration.create_from dest_config_file, destination: "mars" - assert_equal "1.1.1.3", config.hosts.first + assert_equal "1.1.1.3", config.all_hosts.first end test "destination yml config file missing" do diff --git a/test/fixtures/deploy_with_roles.yml b/test/fixtures/deploy_with_roles.yml new file mode 100644 index 00000000..345c6301 --- /dev/null +++ b/test/fixtures/deploy_with_roles.yml @@ -0,0 +1,15 @@ +service: app +image: dhh/app +servers: + web: + - 1.1.1.1 + - 1.1.1.2 + workers: + - 1.1.1.3 + - 1.1.1.4 +env: + REDIS_URL: redis://x/y +registry: + server: registry.digitalocean.com + username: user + password: pw