diff --git a/lib/mrsk.rb b/lib/mrsk.rb index 414680bf..af755fc7 100644 --- a/lib/mrsk.rb +++ b/lib/mrsk.rb @@ -3,6 +3,4 @@ end require "mrsk/version" require "mrsk/engine" - -require "mrsk/configuration" -require "mrsk/commands" +require "mrsk/commander" diff --git a/lib/mrsk/commander.rb b/lib/mrsk/commander.rb new file mode 100644 index 00000000..61d267b3 --- /dev/null +++ b/lib/mrsk/commander.rb @@ -0,0 +1,45 @@ +require "mrsk/configuration" +require "mrsk/commands/app" +require "mrsk/commands/prune" +require "mrsk/commands/traefik" +require "mrsk/commands/registry" + +class Mrsk::Commander + attr_reader :config_file, :config, :verbose + + def initialize(config_file:, verbose: false) + @config_file, @verbose = config_file, verbose + end + + def config + @config ||= Mrsk::Configuration.load_file(config_file).tap { |config| setup_with(config) } + end + + def app + @app ||= Mrsk::Commands::App.new(config) + end + + def traefik + @traefik ||= Mrsk::Commands::Traefik.new(config) + end + + def registry + @registry ||= Mrsk::Commands::Registry.new(config) + end + + def verbosity(level) + old_level = SSHKit.config.output_verbosity + SSHKit.config.output_verbosity = level + yield + ensure + SSHKit.config.output_verbosity = old_level + end + + private + # Lazy setup of SSHKit + def setup_with(config) + SSHKit::Backend::Netssh.configure { |ssh| ssh.ssh_options = config.ssh_options } + SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs + SSHKit.config.output_verbosity = :debug if verbose + end +end diff --git a/lib/tasks/mrsk/app.rake b/lib/tasks/mrsk/app.rake index 9f1f1b1d..a8422d34 100644 --- a/lib/tasks/mrsk/app.rake +++ b/lib/tasks/mrsk/app.rake @@ -1,7 +1,5 @@ require_relative "setup" -app = Mrsk::Commands::App.new(MRSK_CONFIG) - namespace :mrsk do namespace :app do desc "Deliver a newly built app image to servers" @@ -12,30 +10,30 @@ namespace :mrsk do run_locally do begin info "Building multi-architecture images may take a while (run with VERBOSE=1 for progress logging)" - execute *app.push + execute *MRSK.app.push rescue SSHKit::Command::Failed => e error "Missing compatible buildx builder, so creating a new one first" - execute *app.create_new_builder - execute *app.push + execute *MRSK.app.create_new_builder + execute *MRSK.app.push end end unless ENV["VERSION"] end desc "Pull app image from the registry onto servers" task :pull do - on(MRSK_CONFIG.hosts) { execute *app.pull } + on(MRSK.config.hosts) { execute *MRSK.app.pull } end desc "Run app on servers (or start them if they've already been run)" task :run do - MRSK_CONFIG.roles.each do |role| + MRSK.config.roles.each do |role| on(role.hosts) do |host| begin - execute *app.run(role: role.name) + execute *MRSK.app.run(role: role.name) rescue SSHKit::Command::Failed => e if e.message =~ /already in use/ error "Container with same version already deployed on #{host}, starting that instead" - execute *app.start, host: host + execute *MRSK.app.start, host: host else raise end @@ -46,12 +44,12 @@ namespace :mrsk do desc "Start existing app on servers (use VERSION= to designate which version)" task :start do - on(MRSK_CONFIG.hosts) { execute *app.start, raise_on_non_zero_exit: false } + on(MRSK.config.hosts) { execute *MRSK.app.start, raise_on_non_zero_exit: false } end desc "Stop app on servers" task :stop do - on(MRSK_CONFIG.hosts) { execute *app.stop, raise_on_non_zero_exit: false } + on(MRSK.config.hosts) { execute *MRSK.app.stop, raise_on_non_zero_exit: false } end desc "Start app on servers (use VERSION= to designate which version)" @@ -59,54 +57,54 @@ namespace :mrsk do desc "Display information about app containers" task :info do - on(MRSK_CONFIG.hosts) { |host| puts "App Host: #{host}\n" + capture(*app.info) + "\n\n" } + on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.info) + "\n\n" } end desc "Execute a custom task on servers passed in as CMD='bin/rake some:task'" task :exec do - on(MRSK_CONFIG.hosts) { |host| puts "App Host: #{host}\n" + capture(*app.exec(ENV["CMD"])) + "\n\n" } + on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.exec(ENV["CMD"])) + "\n\n" } end desc "Start Rails Console on primary host" task :console do - puts "Launching Rails console on #{MRSK_CONFIG.primary_host}..." + puts "Launching Rails console on #{MRSK.config.primary_host}..." exec app.console end namespace :exec do desc "Execute Rails command on servers, like CMD='runner \"puts %(Hello World)\"" task :rails do - on(MRSK_CONFIG.hosts) { |host| puts "App Host: #{host}\n" + capture(*app.exec("bin/rails", ENV["CMD"])) + "\n\n" } + on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.exec("bin/rails", ENV["CMD"])) + "\n\n" } end desc "Execute a custom task on the first defined server" task :once do - on(MRSK_CONFIG.primary_host) { puts capture(*app.exec(ENV["CMD"])) } + on(MRSK.config.primary_host) { puts capture(*MRSK.app.exec(ENV["CMD"])) } end namespace :once do desc "Execute Rails command on the first defined server, like CMD='runner \"puts %(Hello World)\"" task :rails do - on(MRSK_CONFIG.primary_host) { puts capture(*app.exec("bin/rails", ENV["CMD"])) } + on(MRSK.config.primary_host) { puts capture(*MRSK.app.exec("bin/rails", ENV["CMD"])) } end end end desc "List all the app containers currently on servers" task :containers do - on(MRSK_CONFIG.hosts) { |host| puts "App Host: #{host}\n" + capture(*app.list_containers) + "\n\n" } + on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.list_containers) + "\n\n" } end desc "Tail logs from app containers" task :logs do - on(MRSK_CONFIG.hosts) { execute *app.logs } + on(MRSK.config.hosts) { execute *MRSK.app.logs } end desc "Remove app containers and images from servers" task remove: %i[ stop ] do - on(MRSK_CONFIG.hosts) do - execute *app.remove_containers - execute *app.remove_images + on(MRSK.config.hosts) do + execute *MRSK.app.remove_containers + execute *MRSK.app.remove_images end end end diff --git a/lib/tasks/mrsk/prune.rake b/lib/tasks/mrsk/prune.rake index 0b6d1fbe..33b208e3 100644 --- a/lib/tasks/mrsk/prune.rake +++ b/lib/tasks/mrsk/prune.rake @@ -1,7 +1,5 @@ require_relative "setup" -prune = Mrsk::Commands::Prune.new(MRSK_CONFIG) - namespace :mrsk do desc "Prune unused images and stopped containers" task prune: %w[ prune:containers prune:images ] @@ -9,12 +7,12 @@ namespace :mrsk do namespace :prune do desc "Prune unused images older than 30 days" task :images do - on(MRSK_CONFIG.hosts) { verbosity(:debug) { execute *prune.images } } + on(MRSK.config.hosts) { MRSK.verbosity(:debug) { execute *MRSK.prune.images } } end desc "Prune stopped containers for the service older than 3 days" task :containers do - on(MRSK_CONFIG.hosts) { verbosity(:debug) { execute *prune.containers } } + on(MRSK.config.hosts) { MRSK.verbosity(:debug) { execute *MRSK.prune.containers } } end end end diff --git a/lib/tasks/mrsk/registry.rake b/lib/tasks/mrsk/registry.rake index c5750a94..773cd28f 100644 --- a/lib/tasks/mrsk/registry.rake +++ b/lib/tasks/mrsk/registry.rake @@ -1,18 +1,16 @@ require_relative "setup" -registry = Mrsk::Commands::Registry.new(MRSK_CONFIG) - namespace :mrsk do namespace :registry do desc "Login to the registry locally and remotely" task :login do - run_locally { execute *registry.login } - on(MRSK_CONFIG.hosts) { execute *registry.login } + run_locally { execute *MRSK.registry.login } + on(MRSK.config.hosts) { execute *MRSK.registry.login } end desc "Logout of the registry remotely" task :logout do - on(MRSK_CONFIG.hosts) { execute *registry.logout } + on(MRSK.config.hosts) { execute *MRSK.registry.logout } end end end diff --git a/lib/tasks/mrsk/server.rake b/lib/tasks/mrsk/server.rake index d5ac4f48..07620631 100644 --- a/lib/tasks/mrsk/server.rake +++ b/lib/tasks/mrsk/server.rake @@ -5,7 +5,7 @@ namespace :mrsk do desc "Setup Docker on the remote servers" task :bootstrap do # FIXME: Detect when apt-get is not available and use the appropriate alternative - on(MRSK_CONFIG.hosts) { execute "apt-get install docker.io -y" } + on(MRSK.config.hosts) { execute "apt-get install docker.io -y" } end end end diff --git a/lib/tasks/mrsk/setup.rb b/lib/tasks/mrsk/setup.rb index 483b8498..4ff2eb23 100644 --- a/lib/tasks/mrsk/setup.rb +++ b/lib/tasks/mrsk/setup.rb @@ -3,26 +3,4 @@ require "sshkit/dsl" include SSHKit::DSL -if (config_file = Rails.root.join("config/deploy.yml")).exist? - MRSK_CONFIG = Mrsk::Configuration.load_file(config_file) - - SSHKit::Backend::Netssh.configure { |ssh| ssh.ssh_options = MRSK_CONFIG.ssh_options } - - # No need to use /usr/bin/env, just clogs up the logs - SSHKit.config.command_map[:docker] = "docker" -else - # MRSK is missing config/deploy.yml – run 'rake mrsk:init' - MRSK_CONFIG = Mrsk::Configuration.new({}, validate: false) -end - -# Allow easy full verbosity mode -SSHKit.config.output_verbosity = :debug if ENV["VERBOSE"] - -# Set a different verbosity level for the duration of the yield -def verbosity(level) - old_level = SSHKit.config.output_verbosity - SSHKit.config.output_verbosity = level - yield -ensure - SSHKit.config.output_verbosity = old_level -end +MRSK = Mrsk::Commander.new config_file: Rails.root.join("config/deploy.yml"), verbose: ENV["VERBOSE"] diff --git a/lib/tasks/mrsk/traefik.rake b/lib/tasks/mrsk/traefik.rake index b8e3cbf7..cd136928 100644 --- a/lib/tasks/mrsk/traefik.rake +++ b/lib/tasks/mrsk/traefik.rake @@ -1,22 +1,20 @@ require_relative "setup" -traefik = Mrsk::Commands::Traefik.new(MRSK_CONFIG) - namespace :mrsk do namespace :traefik do desc "Run Traefik on servers" task :run do - on(MRSK_CONFIG.role(:web).hosts) { execute *traefik.run, raise_on_non_zero_exit: false } + on(MRSK.config.role(:web).hosts) { execute *MRSK.traefik.run, raise_on_non_zero_exit: false } end desc "Start existing Traefik on servers" task :start do - on(MRSK_CONFIG.role(:web).hosts) { execute *traefik.start, raise_on_non_zero_exit: false } + on(MRSK.config.role(:web).hosts) { execute *MRSK.traefik.start, raise_on_non_zero_exit: false } end desc "Stop Traefik on servers" task :stop do - on(MRSK_CONFIG.role(:web).hosts) { execute *traefik.stop, raise_on_non_zero_exit: false } + on(MRSK.config.role(:web).hosts) { execute *MRSK.traefik.stop, raise_on_non_zero_exit: false } end desc "Restart Traefik on servers" @@ -24,14 +22,14 @@ namespace :mrsk do desc "Display information about Traefik containers from servers" task :info do - on(MRSK_CONFIG.role(:web).hosts) { |host| puts "Traefik Host: #{host}\n" + capture(*traefik.info) + "\n\n" } + on(MRSK.config.role(:web).hosts) { |host| puts "Traefik Host: #{host}\n" + capture(*MRSK.traefik.info) + "\n\n" } end desc "Remove Traefik container and image from servers" task remove: %i[ stop ] do - on(MRSK_CONFIG.role(:web).hosts) do - execute *traefik.remove_container - execute *traefik.remove_image + on(MRSK.config.role(:web).hosts) do + execute *MRSK.traefik.remove_container + execute *MRSK.traefik.remove_image end end end diff --git a/test/app_command_test.rb b/test/app_command_test.rb index e2636a4e..3d989b72 100644 --- a/test/app_command_test.rb +++ b/test/app_command_test.rb @@ -1,6 +1,6 @@ require "test_helper" require "mrsk/configuration" -require "mrsk/commands" +require "mrsk/commands/app" ENV["VERSION"] = "123" ENV["RAILS_MASTER_KEY"] = "456" diff --git a/test/commander_test.rb b/test/commander_test.rb new file mode 100644 index 00000000..1c093599 --- /dev/null +++ b/test/commander_test.rb @@ -0,0 +1,12 @@ +require "test_helper" +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__)) + end + + test "lazy configuration" do + assert_equal Mrsk::Configuration, @mrsk.config.class + end +end