From fed64ef244e64fe6b5e9353724263d024a11373b Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 14 Jan 2023 11:31:37 +0100 Subject: [PATCH 01/13] Switch to proper standalone executable with Thor --- Gemfile | 1 + Gemfile.lock | 10 +- bin/mrsk | 5 + lib/mrsk.rb | 1 - lib/mrsk/cli.rb | 9 ++ lib/mrsk/cli/app.rb | 98 +++++++++++++++++++ lib/mrsk/cli/base.rb | 27 +++++ lib/mrsk/cli/build.rb | 53 ++++++++++ lib/mrsk/cli/main.rb | 87 ++++++++++++++++ lib/mrsk/cli/prune.rb | 19 ++++ lib/mrsk/cli/registry.rb | 14 +++ lib/mrsk/cli/server.rb | 8 ++ .../mrsk => mrsk/cli}/templates/deploy.yml | 4 +- lib/mrsk/cli/traefik.rb | 44 +++++++++ lib/mrsk/commander.rb | 9 +- lib/mrsk/commands/app.rb | 8 +- lib/mrsk/configuration.rb | 4 +- lib/mrsk/engine.rb | 4 - lib/tasks/mrsk/app.rake | 97 ------------------ lib/tasks/mrsk/build.rake | 52 ---------- lib/tasks/mrsk/mrsk.rake | 37 ------- lib/tasks/mrsk/prune.rake | 18 ---- lib/tasks/mrsk/registry.rake | 16 --- lib/tasks/mrsk/server.rake | 10 -- lib/tasks/mrsk/setup.rb | 6 -- lib/tasks/mrsk/templates/mrsk | 8 -- lib/tasks/mrsk/traefik.rake | 41 -------- mrsk.gemspec | 4 +- 28 files changed, 387 insertions(+), 307 deletions(-) create mode 100755 bin/mrsk create mode 100644 lib/mrsk/cli.rb create mode 100644 lib/mrsk/cli/app.rb create mode 100644 lib/mrsk/cli/base.rb create mode 100644 lib/mrsk/cli/build.rb create mode 100644 lib/mrsk/cli/main.rb create mode 100644 lib/mrsk/cli/prune.rb create mode 100644 lib/mrsk/cli/registry.rb create mode 100644 lib/mrsk/cli/server.rb rename lib/{tasks/mrsk => mrsk/cli}/templates/deploy.yml (88%) create mode 100644 lib/mrsk/cli/traefik.rb delete mode 100755 lib/mrsk/engine.rb delete mode 100644 lib/tasks/mrsk/app.rake delete mode 100644 lib/tasks/mrsk/build.rake delete mode 100644 lib/tasks/mrsk/mrsk.rake delete mode 100644 lib/tasks/mrsk/prune.rake delete mode 100644 lib/tasks/mrsk/registry.rake delete mode 100644 lib/tasks/mrsk/server.rake delete mode 100644 lib/tasks/mrsk/setup.rb delete mode 100755 lib/tasks/mrsk/templates/mrsk delete mode 100644 lib/tasks/mrsk/traefik.rake diff --git a/Gemfile b/Gemfile index ceed4117..8e9e4f6e 100644 --- a/Gemfile +++ b/Gemfile @@ -4,3 +4,4 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } gemspec gem "debug" +gem "railties" diff --git a/Gemfile.lock b/Gemfile.lock index 46ad7d25..55c75b0a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,8 +2,9 @@ PATH remote: . specs: mrsk (0.0.3) - railties (>= 7.0.0) + activesupport (>= 7.0) sshkit (~> 1.21) + thor (~> 1.2) GEM remote: https://rubygems.org/ @@ -46,11 +47,11 @@ GEM net-scp (4.0.0) net-ssh (>= 2.6.5, < 8.0.0) net-ssh (7.0.1) - nokogiri (1.14.0.rc1-arm64-darwin) + nokogiri (1.14.0-arm64-darwin) racc (~> 1.4) - nokogiri (1.14.0.rc1-x86_64-darwin) + nokogiri (1.14.0-x86_64-darwin) racc (~> 1.4) - nokogiri (1.14.0.rc1-x86_64-linux) + nokogiri (1.14.0-x86_64-linux) racc (~> 1.4) racc (1.6.2) rack (2.2.5) @@ -90,6 +91,7 @@ PLATFORMS DEPENDENCIES debug mrsk! + railties BUNDLED WITH 2.4.3 diff --git a/bin/mrsk b/bin/mrsk new file mode 100755 index 00000000..11341e9b --- /dev/null +++ b/bin/mrsk @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby + +require "mrsk/cli" + +Mrsk::Cli::Main.start(ARGV) diff --git a/lib/mrsk.rb b/lib/mrsk.rb index af755fc7..3adf2bef 100644 --- a/lib/mrsk.rb +++ b/lib/mrsk.rb @@ -2,5 +2,4 @@ module Mrsk end require "mrsk/version" -require "mrsk/engine" require "mrsk/commander" diff --git a/lib/mrsk/cli.rb b/lib/mrsk/cli.rb new file mode 100644 index 00000000..5413e350 --- /dev/null +++ b/lib/mrsk/cli.rb @@ -0,0 +1,9 @@ +require "mrsk" + +MRSK = Mrsk::Commander.new \ + config_file: Pathname.new(File.expand_path("config/deploy.yml")) + +module Mrsk::Cli +end + +require "mrsk/cli/main" diff --git a/lib/mrsk/cli/app.rb b/lib/mrsk/cli/app.rb new file mode 100644 index 00000000..d374fcfd --- /dev/null +++ b/lib/mrsk/cli/app.rb @@ -0,0 +1,98 @@ +require "mrsk/cli/base" + +class Mrsk::Cli::App < Mrsk::Cli::Base + desc "boot", "Boot app on servers (or start them if they've already been booted)" + def boot + MRSK.config.roles.each do |role| + on(role.hosts) do |host| + begin + 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 *MRSK.app.start, host: host + else + raise + end + end + end + end + end + + desc "start", "Start existing app on servers (use --version= to designate specific version)" + 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), raise_on_non_zero_exit: false } + else + on(MRSK.config.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 } + end + + desc "restart", "Start app on servers (use VERSION= to designate which version)" + def restart + invoke :stop + invoke :start + end + + desc "details", "Display details about app containers" + def details + on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.info, verbosity: Logger::INFO) + "\n\n" } + end + + desc "exec [CMD]", "Execute a custom task on servers passed in as CMD='bin/rake some:task'" + option :once, type: :boolean, default: false + def exec(cmd) + if options[:once] + on(MRSK.config.primary_host) { puts capture(*MRSK.app.exec(cmd), verbosity: Logger::INFO) } + else + on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.exec(cmd), verbosity: Logger::INFO) + "\n\n" } + end + end + + desc "console [HOST]", "Start Rails Console on primary host (or designated HOST)" + def console(host = MRSK.config.primary_host) + puts "Launching Rails console on #{host}..." + exec MRSK.app.console(host: host) + end + + desc "runner [EXPRESSION]", "Execute Rails runner with given expression" + option :once, type: :boolean, default: false, desc: + def runner(expression) + if options[:once] + on(MRSK.config.primary_host) { puts capture(*MRSK.app.exec("bin/rails", "runner", "'#{expression}'"), verbosity: Logger::INFO) } + else + on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.exec("bin/rails", "runner", "'#{expression}'"), verbosity: Logger::INFO) + "\n\n" } + end + end + + desc "containers", "List all the app containers currently on servers" + def containers + on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.list_containers) + "\n\n" } + end + + desc "logs", "Show last 100 log lines from app on servers" + def logs + # FIXME: Catch when app containers aren't running + on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.logs) + "\n\n" } + end + + desc "remove", "Remove app containers and images from servers" + option :only, default: "", desc: "Use 'containers' or 'images'" + def remove + case options[:only] + when "containers" + on(MRSK.config.hosts) { execute *MRSK.app.remove_containers } + when "images" + on(MRSK.config.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 } + end + end +end diff --git a/lib/mrsk/cli/base.rb b/lib/mrsk/cli/base.rb new file mode 100644 index 00000000..a2821915 --- /dev/null +++ b/lib/mrsk/cli/base.rb @@ -0,0 +1,27 @@ +require "thor" +require "sshkit" +require "sshkit/dsl" + +module Mrsk::Cli + class Base < Thor + include SSHKit::DSL + + def self.exit_on_failure?() true end + + class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging" + + def initialize(*) + super + MRSK.verbose = options[:verbose] + end + + private + def print_runtime + started_at = Time.now + yield + ensure + runtime = Time.now - started_at + puts " Finished all in #{sprintf("%.1f seconds", runtime)}" + end + end +end diff --git a/lib/mrsk/cli/build.rb b/lib/mrsk/cli/build.rb new file mode 100644 index 00000000..715574d1 --- /dev/null +++ b/lib/mrsk/cli/build.rb @@ -0,0 +1,53 @@ +require "mrsk/cli/base" + +class Mrsk::Cli::Build < Mrsk::Cli::Base + desc "deliver", "Deliver a newly built app image to servers" + def deliver + invoke :push + invoke :pull + end + + desc "push", "Build locally and push app image to registry" + def push + run_locally do + begin + debug "Using builder: #{MRSK.builder.name}" + info "Building image may take a while (run with --verbose for progress logging)" + execute *MRSK.builder.push + rescue SSHKit::Command::Failed => e + error "Missing compatible builder, so creating a new one first" + execute *MRSK.builder.create + execute *MRSK.builder.push + end + end + end + + desc "pull", "Pull app image from the registry onto servers" + def pull + on(MRSK.config.hosts) { execute *MRSK.builder.pull } + end + + desc "create", "Create a local build setup" + def create + run_locally do + debug "Using builder: #{MRSK.builder.name}" + execute *MRSK.builder.create + end + end + + desc "remove", "Remove local build setup" + def remove + run_locally do + debug "Using builder: #{MRSK.builder.name}" + execute *MRSK.builder.remove + end + end + + desc "details", "Show the name of the configured builder" + def details + run_locally do + puts "Builder: #{MRSK.builder.name} (#{MRSK.builder.target.class.name})" + puts capture(*MRSK.builder.info) + end + end +end diff --git a/lib/mrsk/cli/main.rb b/lib/mrsk/cli/main.rb new file mode 100644 index 00000000..607c5fca --- /dev/null +++ b/lib/mrsk/cli/main.rb @@ -0,0 +1,87 @@ +require "mrsk/cli/base" + +require "mrsk/cli/app" +require "mrsk/cli/build" +require "mrsk/cli/prune" +require "mrsk/cli/registry" +require "mrsk/cli/server" +require "mrsk/cli/traefik" + +class Mrsk::Cli::Main < Mrsk::Cli::Base + desc "ship", "Ship the app to servers" + def ship + print_runtime do + invoke "mrsk:cli:server:bootstrap" + invoke "mrsk:cli:registry:login" + invoke "mrsk:cli:build:deliver" + invoke "mrsk:cli:traefik:boot" + invoke "mrsk:cli:app:stop" + invoke "mrsk:cli:app:boot" + invoke "mrsk:cli:prune:all" + end + end + + desc "reship", "Ship new version of the app to servers (without bootstrapping servers, starting Traefik, pruning, and registry login)" + def reship + print_runtime do + invoke "mrsk:cli:build:deliver" + invoke "mrsk:cli:app:stop" + invoke "mrsk:cli:app:boot" + end + end + + desc "rollback [VERSION]", "Rollback the app to VERSION (that must already be on servers)" + def rollback(version) + invoke "mrsk:cli:app:restart" + end + + desc "details", "Display details about Traefik and app containers" + def details + invoke "mrsk:cli:traefik:details" + invoke "mrsk:cli:app:details" + end + + desc "install", "Create config stub in config/deploy.yml and binstub in bin/mrsk" + def install + require "fileutils" + + if (deploy_file = Pathname.new(File.expand_path("config/deploy.yml"))).exist? + puts "Config file already exists in config/deploy.yml (remove first to create a new one)" + else + FileUtils.cp_r Pathname.new(File.expand_path("templates/deploy.yml", __dir__)), deploy_file + puts "Created configuration file in config/deploy.yml" + end + + if (binstub = Pathname.new(File.expand_path("bin/mrsk"))).exist? + puts "Binstub already exists in bin/mrsk (remove first to create a new one)" + else + `bundle binstubs mrsk` + puts "Created binstub file in bin/mrsk" + end + end + + desc "remove", "Remove Traefik, app, and registry session from servers" + def remove + invoke "mrsk:cli:traefik:remove" + invoke "mrsk:cli:app:remove" + invoke "mrsk:cli:registry:logout" + end + + desc "app", "Manage the application" + subcommand "app", Mrsk::Cli::App + + desc "build", "Build the application image" + subcommand "build", Mrsk::Cli::Build + + desc "prune", "Prune old application images and containers" + subcommand "prune", Mrsk::Cli::Prune + + desc "registry", "Login and out of the image registry" + subcommand "registry", Mrsk::Cli::Registry + + desc "server", "Bootstrap servers with Docker" + subcommand "server", Mrsk::Cli::Server + + desc "traefik", "Manage the Traefik load balancer" + subcommand "traefik", Mrsk::Cli::Traefik +end diff --git a/lib/mrsk/cli/prune.rb b/lib/mrsk/cli/prune.rb new file mode 100644 index 00000000..9dea45e6 --- /dev/null +++ b/lib/mrsk/cli/prune.rb @@ -0,0 +1,19 @@ +require "mrsk/cli/base" + +class Mrsk::Cli::Prune < Mrsk::Cli::Base + desc "all", "Prune unused images and stopped containers" + def all + invoke :containers + invoke :images + end + + desc "images", "Prune unused images older than 30 days" + def images + on(MRSK.config.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 } + end +end diff --git a/lib/mrsk/cli/registry.rb b/lib/mrsk/cli/registry.rb new file mode 100644 index 00000000..716e5f5e --- /dev/null +++ b/lib/mrsk/cli/registry.rb @@ -0,0 +1,14 @@ +require "mrsk/cli/base" + +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 } + end + + desc "logout", "Logout of the registry remotely" + def logout + on(MRSK.config.hosts) { execute *MRSK.registry.logout } + end +end diff --git a/lib/mrsk/cli/server.rb b/lib/mrsk/cli/server.rb new file mode 100644 index 00000000..9121d63e --- /dev/null +++ b/lib/mrsk/cli/server.rb @@ -0,0 +1,8 @@ +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 install docker.io -y" } + end +end diff --git a/lib/tasks/mrsk/templates/deploy.yml b/lib/mrsk/cli/templates/deploy.yml similarity index 88% rename from lib/tasks/mrsk/templates/deploy.yml rename to lib/mrsk/cli/templates/deploy.yml index 23ebb91c..1c813442 100644 --- a/lib/tasks/mrsk/templates/deploy.yml +++ b/lib/mrsk/cli/templates/deploy.yml @@ -18,7 +18,5 @@ env: registry: # Specify the registry server, if you're not using Docker Hub # server: registry.digitalocean.com / ghcr.io / ... - - # Set credentials with bin/rails credentials:edit username: my-user - password: my-password-should-go-in-credentials + password: my-password-should-go-somewhere-safe diff --git a/lib/mrsk/cli/traefik.rb b/lib/mrsk/cli/traefik.rb new file mode 100644 index 00000000..658ef7dc --- /dev/null +++ b/lib/mrsk/cli/traefik.rb @@ -0,0 +1,44 @@ +require "mrsk/cli/base" + +class Mrsk::Cli::Traefik < Mrsk::Cli::Base + desc "boot", "Boot Traefik on servers" + def boot + on(MRSK.config.role(:web).hosts) { execute *MRSK.traefik.run, raise_on_non_zero_exit: false } + end + + desc "start", "Start existing Traefik on servers" + def start + on(MRSK.config.role(:web).hosts) { execute *MRSK.traefik.start, raise_on_non_zero_exit: false } + end + + desc "stop", "Stop Traefik on servers" + def stop + on(MRSK.config.role(:web).hosts) { execute *MRSK.traefik.stop, raise_on_non_zero_exit: false } + end + + desc "restart", "Restart Traefik on servers" + def restart + invoke :stop + invoke :start + end + + desc "details", "Display details about Traefik containers from servers" + def details + on(MRSK.config.role(:web).hosts) { |host| puts "Traefik Host: #{host}\n" + capture(*MRSK.traefik.info) + "\n\n" } + end + + desc "logs", "Show last 100 log lines from Traefik on servers" + def logs + on(MRSK.config.hosts) { |host| puts "Traefik Host: #{host}\n" + capture(*MRSK.traefik.logs) + "\n\n" } + end + + desc "remove", "Remove Traefik container and image from servers" + def remove + invoke :stop + + on(MRSK.config.role(:web).hosts) do + execute *MRSK.traefik.remove_container + execute *MRSK.traefik.remove_image + end + end +end diff --git a/lib/mrsk/commander.rb b/lib/mrsk/commander.rb index e6cffd9b..26c87b5d 100644 --- a/lib/mrsk/commander.rb +++ b/lib/mrsk/commander.rb @@ -6,14 +6,15 @@ require "mrsk/commands/traefik" require "mrsk/commands/registry" class Mrsk::Commander - attr_reader :config_file, :config, :verbose + attr_reader :config + attr_accessor :verbose - def initialize(config_file:, verbose: false) - @config_file, @verbose = config_file, verbose + def initialize(config_file:) + @config_file = config_file end def config - @config ||= Mrsk::Configuration.load_file(config_file).tap { |config| setup_with(config) } + @config ||= Mrsk::Configuration.load_file(@config_file).tap { |config| setup_with(config) } end diff --git a/lib/mrsk/commands/app.rb b/lib/mrsk/commands/app.rb index 7e613507..42245ddd 100644 --- a/lib/mrsk/commands/app.rb +++ b/lib/mrsk/commands/app.rb @@ -15,8 +15,8 @@ class Mrsk::Commands::App < Mrsk::Commands::Base role.cmd end - def start - docker :start, config.service_with_version + def start(version: config.version) + docker :start, "#{config.service}-#{version}" end def stop @@ -40,8 +40,8 @@ class Mrsk::Commands::App < Mrsk::Commands::Base *command end - def console - "ssh -t #{config.ssh_user}@#{config.primary_host} '#{exec("bin/rails", "c", interactive: true).join(" ")}'" + def console(host: config.primary_host) + "ssh -t #{config.ssh_user}@#{host} '#{exec("bin/rails", "c", interactive: true).join(" ")}'" end def list_containers diff --git a/lib/mrsk/configuration.rb b/lib/mrsk/configuration.rb index a0e33d73..8c19808a 100644 --- a/lib/mrsk/configuration.rb +++ b/lib/mrsk/configuration.rb @@ -1,5 +1,7 @@ require "active_support/ordered_options" require "active_support/core_ext/string/inquiry" +require "active_support/core_ext/module/delegation" +require "pathname" require "erb" class Mrsk::Configuration @@ -91,7 +93,7 @@ class Mrsk::Configuration end def master_key - ENV["RAILS_MASTER_KEY"] || File.read(Rails.root.join("config/master.key")) + ENV["RAILS_MASTER_KEY"] || File.read(Pathname.new(File.expand_path("config/master.key"))) end diff --git a/lib/mrsk/engine.rb b/lib/mrsk/engine.rb deleted file mode 100755 index 87f2d298..00000000 --- a/lib/mrsk/engine.rb +++ /dev/null @@ -1,4 +0,0 @@ -module Mrsk - class Engine < ::Rails::Engine - end -end diff --git a/lib/tasks/mrsk/app.rake b/lib/tasks/mrsk/app.rake deleted file mode 100644 index 5f8aef3f..00000000 --- a/lib/tasks/mrsk/app.rake +++ /dev/null @@ -1,97 +0,0 @@ -require_relative "setup" - -namespace :mrsk do - namespace :app do - desc "Run app on servers (or start them if they've already been run)" - task :run do - MRSK.config.roles.each do |role| - on(role.hosts) do |host| - begin - 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 *MRSK.app.start, host: host - else - raise - end - end - end - end - end - - desc "Start existing app on servers (use VERSION= to designate which version)" - task :start do - 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 *MRSK.app.stop, raise_on_non_zero_exit: false } - end - - desc "Start app on servers (use VERSION= to designate which version)" - task restart: %i[ stop start ] - - desc "Display information about app containers" - task :info do - 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(*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}..." - 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(*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(*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(*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(*MRSK.app.list_containers) + "\n\n" } - end - - desc "Show last 100 log lines from app on servers" - task :logs do - # FIXME: Catch when app containers aren't running - on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.logs) + "\n\n" } - end - - desc "Remove app containers and images from servers" - task remove: %w[ remove:containers remove:images ] - - namespace :remove do - desc "Remove app containers from servers" - task :containers do - on(MRSK.config.hosts) { execute *MRSK.app.remove_containers } - end - - desc "Remove app images from servers" - task :images do - on(MRSK.config.hosts) { execute *MRSK.app.remove_images } - end - end - end -end diff --git a/lib/tasks/mrsk/build.rake b/lib/tasks/mrsk/build.rake deleted file mode 100644 index cf5bc639..00000000 --- a/lib/tasks/mrsk/build.rake +++ /dev/null @@ -1,52 +0,0 @@ -require_relative "setup" - -namespace :mrsk do - namespace :build do - desc "Deliver a newly built app image to servers" - task deliver: %i[ push pull ] - - desc "Build locally and push app image to registry" - task :push do - run_locally do - begin - debug "Using builder: #{MRSK.builder.name}" - info "Building image may take a while (run with VERBOSE=1 for progress logging)" - execute *MRSK.builder.push - rescue SSHKit::Command::Failed => e - error "Missing compatible builder, so creating a new one first" - execute *MRSK.builder.create - execute *MRSK.builder.push - end - end unless ENV["VERSION"] - end - - desc "Pull app image from the registry onto servers" - task :pull do - on(MRSK.config.hosts) { execute *MRSK.builder.pull } - end - - desc "Create a local build setup" - task :create do - run_locally do - debug "Using builder: #{MRSK.builder.name}" - execute *MRSK.builder.create - end - end - - desc "Remove local build setup" - task :remove do - run_locally do - debug "Using builder: #{MRSK.builder.name}" - execute *MRSK.builder.remove - end - end - - desc "Show the name of the configured builder" - task :info do - run_locally do - puts "Builder: #{MRSK.builder.name} (#{MRSK.builder.target.class.name})" - puts capture(*MRSK.builder.info) - end - end - end -end diff --git a/lib/tasks/mrsk/mrsk.rake b/lib/tasks/mrsk/mrsk.rake deleted file mode 100644 index 748dd18b..00000000 --- a/lib/tasks/mrsk/mrsk.rake +++ /dev/null @@ -1,37 +0,0 @@ -require_relative "setup" - -namespace :mrsk do - desc "Ship the app to servers that will have Docker installed if missing" - task ship: %w[ server:bootstrap deploy ] - - desc "Push the latest version of the app, ensure Traefik is running, then restart app" - task deploy: %w[ registry:login build:deliver traefik:run app:stop app:run prune ] - - desc "Rollback to VERSION=x that was already run as a container on servers" - task rollback: %w[ app:restart ] - - desc "Display information about Traefik and app containers" - task info: %w[ traefik:info app:info ] - - desc "Create config stub in config/deploy.yml" - task :init do - require "fileutils" - - if (deploy_file = Rails.root.join("config/deploy.yml")).exist? - puts "Config file already exists in config/deploy.yml (remove first to create a new one)" - else - FileUtils.cp_r Pathname.new(File.expand_path("templates/deploy.yml", __dir__)), deploy_file - puts "Created configuration file in config/deploy.yml" - end - - if (binstub = Rails.root.join("bin/mrsk")).exist? - puts "Binstub already exists in bin/mrsk (remove first to create a new one)" - else - FileUtils.cp_r Pathname.new(File.expand_path("templates/mrsk", __dir__)), binstub - puts "Created binstub file in bin/mrsk" - end - end - - desc "Remove Traefik, app, and registry session from servers" - task remove: %w[ traefik:remove app:remove registry:logout ] -end diff --git a/lib/tasks/mrsk/prune.rake b/lib/tasks/mrsk/prune.rake deleted file mode 100644 index 2c0f3a36..00000000 --- a/lib/tasks/mrsk/prune.rake +++ /dev/null @@ -1,18 +0,0 @@ -require_relative "setup" - -namespace :mrsk do - desc "Prune unused images and stopped containers" - task prune: %w[ prune:containers prune:images ] - - namespace :prune do - desc "Prune unused images older than 30 days" - task :images do - on(MRSK.config.hosts) { execute *MRSK.prune.images } - end - - desc "Prune stopped containers for the service older than 3 days" - task :containers do - on(MRSK.config.hosts) { execute *MRSK.prune.containers } - end - end -end diff --git a/lib/tasks/mrsk/registry.rake b/lib/tasks/mrsk/registry.rake deleted file mode 100644 index 773cd28f..00000000 --- a/lib/tasks/mrsk/registry.rake +++ /dev/null @@ -1,16 +0,0 @@ -require_relative "setup" - -namespace :mrsk do - namespace :registry do - desc "Login to the registry locally and remotely" - task :login do - 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 *MRSK.registry.logout } - end - end -end diff --git a/lib/tasks/mrsk/server.rake b/lib/tasks/mrsk/server.rake deleted file mode 100644 index 68747b0d..00000000 --- a/lib/tasks/mrsk/server.rake +++ /dev/null @@ -1,10 +0,0 @@ -require_relative "setup" - -namespace :mrsk do - namespace :server do - desc "Setup Docker on the remote servers" - task :bootstrap do - on(MRSK.config.hosts) { execute "which docker || apt-get install docker.io -y" } - end - end -end diff --git a/lib/tasks/mrsk/setup.rb b/lib/tasks/mrsk/setup.rb deleted file mode 100644 index 4ff2eb23..00000000 --- a/lib/tasks/mrsk/setup.rb +++ /dev/null @@ -1,6 +0,0 @@ -require "sshkit" -require "sshkit/dsl" - -include SSHKit::DSL - -MRSK = Mrsk::Commander.new config_file: Rails.root.join("config/deploy.yml"), verbose: ENV["VERBOSE"] diff --git a/lib/tasks/mrsk/templates/mrsk b/lib/tasks/mrsk/templates/mrsk deleted file mode 100755 index 6d11b783..00000000 --- a/lib/tasks/mrsk/templates/mrsk +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -if [ "${*}" == "" ]; then - # Improve so list matches - exec bin/rake -T mrsk -else - exec bin/rake "mrsk:$@" -fi diff --git a/lib/tasks/mrsk/traefik.rake b/lib/tasks/mrsk/traefik.rake deleted file mode 100644 index 3716283b..00000000 --- a/lib/tasks/mrsk/traefik.rake +++ /dev/null @@ -1,41 +0,0 @@ -require_relative "setup" - -namespace :mrsk do - namespace :traefik do - desc "Run Traefik on servers" - task :run do - 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 *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 *MRSK.traefik.stop, raise_on_non_zero_exit: false } - end - - desc "Restart Traefik on servers" - task restart: %i[ stop start ] - - desc "Display information about Traefik containers from servers" - task :info do - on(MRSK.config.role(:web).hosts) { |host| puts "Traefik Host: #{host}\n" + capture(*MRSK.traefik.info) + "\n\n" } - end - - desc "Show last 100 log lines from Traefik on servers" - task :logs do - on(MRSK.config.hosts) { |host| puts "Traefik Host: #{host}\n" + capture(*MRSK.traefik.logs) + "\n\n" } - end - - desc "Remove Traefik container and image from servers" - task remove: %i[ stop ] do - on(MRSK.config.role(:web).hosts) do - execute *MRSK.traefik.remove_container - execute *MRSK.traefik.remove_image - end - end - end -end diff --git a/mrsk.gemspec b/mrsk.gemspec index 090c8604..8bbc829b 100644 --- a/mrsk.gemspec +++ b/mrsk.gemspec @@ -10,7 +10,9 @@ Gem::Specification.new do |spec| spec.license = "MIT" spec.files = Dir["lib/**/*", "MIT-LICENSE", "README.md"] + spec.executables = %w[ mrsk ] - spec.add_dependency "railties", ">= 7.0.0" + spec.add_dependency "activesupport", ">= 7.0" spec.add_dependency "sshkit", "~> 1.21" + spec.add_dependency "thor", "~> 1.2" end From c44e2245873604ae3e7105daf38ece8d17b157c3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 14 Jan 2023 11:44:16 +0100 Subject: [PATCH 02/13] Add option to skip binstubs for older apps --- lib/mrsk/cli/main.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/mrsk/cli/main.rb b/lib/mrsk/cli/main.rb index 607c5fca..4a04ed6e 100644 --- a/lib/mrsk/cli/main.rb +++ b/lib/mrsk/cli/main.rb @@ -42,6 +42,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base end desc "install", "Create config stub in config/deploy.yml and binstub in bin/mrsk" + option :skip_binstub, type: :boolean, default: false, desc: "Skip adding MRSK to the Gemfile and creating bin/mrsk binstub" def install require "fileutils" @@ -52,11 +53,14 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base puts "Created configuration file in config/deploy.yml" end - if (binstub = Pathname.new(File.expand_path("bin/mrsk"))).exist? - puts "Binstub already exists in bin/mrsk (remove first to create a new one)" - else - `bundle binstubs mrsk` - puts "Created binstub file in bin/mrsk" + unless options[:skip_binstub] + if (binstub = Pathname.new(File.expand_path("bin/mrsk"))).exist? + puts "Binstub already exists in bin/mrsk (remove first to create a new one)" + else + `bundle add mrsk` + `bundle binstubs mrsk` + puts "Created binstub file in bin/mrsk" + end end end From e1e768d7cf565f34ca7c579928a77908ae2d6b88 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 14 Jan 2023 11:51:38 +0100 Subject: [PATCH 03/13] Log traefik details commands --- lib/mrsk/cli/traefik.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mrsk/cli/traefik.rb b/lib/mrsk/cli/traefik.rb index 658ef7dc..482aaf49 100644 --- a/lib/mrsk/cli/traefik.rb +++ b/lib/mrsk/cli/traefik.rb @@ -24,7 +24,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base desc "details", "Display details about Traefik containers from servers" def details - on(MRSK.config.role(:web).hosts) { |host| puts "Traefik Host: #{host}\n" + capture(*MRSK.traefik.info) + "\n\n" } + on(MRSK.config.role(:web).hosts) { |host| puts "Traefik Host: #{host}\n" + capture(*MRSK.traefik.info, verbosity: Logger::INFO) + "\n\n" } end desc "logs", "Show last 100 log lines from Traefik on servers" From ec31e931bf4aca19e64bc531ef917e5eb503642b Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 14 Jan 2023 11:51:46 +0100 Subject: [PATCH 04/13] Add version task --- lib/mrsk/cli/main.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/mrsk/cli/main.rb b/lib/mrsk/cli/main.rb index 4a04ed6e..d9dab4d0 100644 --- a/lib/mrsk/cli/main.rb +++ b/lib/mrsk/cli/main.rb @@ -71,6 +71,11 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base invoke "mrsk:cli:registry:logout" end + desc "version", "Display the MRSK version" + def version + puts Mrsk::VERSION + end + desc "app", "Manage the application" subcommand "app", Mrsk::Cli::App From e3c1992ae9b67de9caa2bb3f7540d8e2e978bd40 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 14 Jan 2023 12:04:41 +0100 Subject: [PATCH 05/13] Move HOST option to real option --- lib/mrsk/cli/app.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/mrsk/cli/app.rb b/lib/mrsk/cli/app.rb index d374fcfd..509f31c1 100644 --- a/lib/mrsk/cli/app.rb +++ b/lib/mrsk/cli/app.rb @@ -55,10 +55,15 @@ class Mrsk::Cli::App < Mrsk::Cli::Base end end - desc "console [HOST]", "Start Rails Console on primary host (or designated HOST)" - def console(host = MRSK.config.primary_host) - puts "Launching Rails console on #{host}..." - exec MRSK.app.console(host: host) + desc "console", "Start Rails Console on primary host" + option :host, desc: "Start console on a different host" + def console + host = options[:host] || MRSK.config.primary_host + + run_locally do + puts "Launching Rails console on #{host}..." + exec MRSK.app.console(host: host) + end end desc "runner [EXPRESSION]", "Execute Rails runner with given expression" From 94b3cfd0f43afce1abc233402ac8885fc14b924f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 14 Jan 2023 12:07:52 +0100 Subject: [PATCH 06/13] Ship is cuter, but deploy is clearer Kill your darlings --- lib/mrsk/cli/main.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/mrsk/cli/main.rb b/lib/mrsk/cli/main.rb index d9dab4d0..51515f05 100644 --- a/lib/mrsk/cli/main.rb +++ b/lib/mrsk/cli/main.rb @@ -8,8 +8,8 @@ require "mrsk/cli/server" require "mrsk/cli/traefik" class Mrsk::Cli::Main < Mrsk::Cli::Base - desc "ship", "Ship the app to servers" - def ship + desc "deploy", "Deploy the app to servers" + def deploy print_runtime do invoke "mrsk:cli:server:bootstrap" invoke "mrsk:cli:registry:login" @@ -21,8 +21,8 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base end end - desc "reship", "Ship new version of the app to servers (without bootstrapping servers, starting Traefik, pruning, and registry login)" - def reship + desc "redeploy", "Deploy new version of the app to servers (without bootstrapping servers, starting Traefik, pruning, and registry login)" + def redeploy print_runtime do invoke "mrsk:cli:build:deliver" invoke "mrsk:cli:app:stop" From e78da2a9253dec3334fd22692340846e09167044 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 14 Jan 2023 12:09:09 +0100 Subject: [PATCH 07/13] Update README to match new exec approach --- README.md | 75 ++++++++++++++++++++++++------------------------------- 1 file changed, 32 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index a201e372..a71c360d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # MRSK -MRSK ships zero-downtime deploys of Rails apps packed as containers to any host. It uses the dynamic reverse-proxy Traefik to hold requests while the new application container is started and the old one is wound down. It works seamlessly across multiple hosts, using SSHKit to execute commands. +MRSK deploys Rails apps packed as containers to any host with zero downtime. It uses the dynamic reverse-proxy Traefik to hold requests while the new application container is started and the old one is wound down. It works seamlessly across multiple hosts, using SSHKit to execute commands. ## Installation -Add the gem with `bundle add mrsk`, then run `rake mrsk:init`, and then edit the new file in `config/deploy.yml`. It could look as simple as this: +Install MRSK globally with `gem install mrsk`. Then inside your app directory, you run `mrsk install`. Now edit the new file in `config/deploy.yml`. It could look as simple as this: ```yaml service: hey @@ -13,34 +13,27 @@ servers: - 192.168.0.1 - 192.168.0.2 registry: - username: <%= Rails.application.credentials.registry["username"] %> - password: <%= Rails.application.credentials.registry["password"] %> -``` - -Then ensure your encrypted credentials have the registry username + password by editing them with `rails credentials:edit`: - -``` -registry: - username: real-user-name - password: real-registry-password-or-token + username: registry-user-name + password: registry-user-password-needs-more-secure-option ``` Now you're ready to deploy a multi-arch image to the servers: ``` -./bin/mrsk deploy +mrsk deploy ``` This will: -1. Log into the registry both locally and remotely -2. Build the image using the standard Dockerfile in the root of the application. -3. Push the image to the registry. -4. Pull the image from the registry on the servers. -5. Ensure Traefik is running and accepting traffic on port 80. -6. Stop any containers running a previous versions of the app. -7. Start a new container with the version of the app that matches the current git version hash. -8. Prune unused images and stopped containers to ensure servers don't fill up. +1. Install Docker on any remote server that might be missing it (using apt-get) +2. Log into the registry both locally and remotely +3. Build the image using the standard Dockerfile in the root of the application. +4. Push the image to the registry. +5. Pull the image from the registry on the servers. +6. Ensure Traefik is running and accepting traffic on port 80. +7. Stop any containers running a previous versions of the app. +8. Start a new container with the version of the app that matches the current git version hash. +9. Prune unused images and stopped containers to ensure servers don't fill up. Voila! All the servers are now serving the app on port 80. If you're just running a single server, you're ready to go. If you're running multiple servers, you need to put a load balancer in front of them. @@ -53,8 +46,8 @@ The default registry for Docker is Docker Hub. If you'd like to use a different ```yaml registry: server: registry.digitalocean.com - username: <%= Rails.application.credentials.registry["username"] %> - password: <%= Rails.application.credentials.registry["password"] %> + username: registry-user-name + password: registry-user-password-needs-more-secure-option ``` ### Using a different SSH user than root @@ -141,7 +134,7 @@ builder: Note: You must have Docker running on the remote host being used as a builder. -With that configuration in place, you can setup the local/remote configuration using `./bin/mrsk build:remote:create`. If you wish to remove the contexts and buildx instances again, you can run `./bin/mrsk build:remote:remove`. If you had already built using the standard emulation setup, run `./bin/mrsk build:remove` before doing `./bin/mrsk build:remote:create`. +With that configuration in place, you can setup the local/remote configuration using `mrsk build create`. If you wish to remove the contexts and buildx instances again, you can run `mrsk build remove`. If you had already built using the standard emulation setup, run `mrsk build remove` before doing `mrsk build remote`. ### Configuring native builder when multi-arch isn't needed @@ -156,11 +149,11 @@ builder: ### Remote execution -If you need to execute commands inside the Rails containers, you can use `./bin/mrsk app:exec`, `./bin/mrsk app:exec:once`, `./bin/mrsk app:exec:rails`, and `./bin/mrsk app:exec:once:rails`. Examples: +If you need to execute commands inside the Rails containers, you can use `mrsk app exec`, `mrsk app exec --once`, `mrsk app runner`, and `mrsk app runner --once`. Examples: ```bash # Runs command on all servers -./bin/mrsk app:exec CMD='ruby -v' +mrsk app exec 'ruby -v' App Host: xxx.xxx.xxx.xxx ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [x86_64-linux] @@ -168,11 +161,11 @@ App Host: xxx.xxx.xxx.xxx ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [x86_64-linux] # Runs command on first server -./bin/mrsk app:exec:once CMD='cat .ruby-version' +mrsk app exec --once 'cat .ruby-version' 3.1.3 # Runs Rails command on all servers -./bin/mrsk app:exec:rails CMD=about +mrsk app exec 'bin/rails about' App Host: xxx.xxx.xxx.xxx About your application's environment Rails version 7.1.0.alpha @@ -197,19 +190,18 @@ Environment production Database adapter sqlite3 Database schema version 20221231233303 -# Runs Rails command on first server -./bin/mrsk app:exec:once:rails CMD='db:version' -database: storage/production.sqlite3 -Current version: 20221231233303 +# Runs Rails runner on first server +mrsk app runner 'puts Rails.application.config.time_zone' +UTC ``` ### Running a Rails console on the primary host -If you need to interact with the production console for the app, you can use `./bin/mrsk app:console`, which will start a Rails console session on the primary host. Be mindful that this is a live wire! Any changes made to the production database will take effect immeditately. +If you need to interact with the production console for the app, you can use `mrsk app console`, which will start a Rails console session on the primary host. You can start the console on a different host using `mrsk app console --host 192.168.0.2`. Be mindful that this is a live wire! Any changes made to the production database will take effect immeditately. ### Inspecting -You can see the state of your servers by running `./bin/mrsk info`. It'll show something like this: +You can see the state of your servers by running `mrsk details`. It'll show something like this: ``` Traefik Host: xxx.xxx.xxx.xxx @@ -229,11 +221,11 @@ CONTAINER ID IMAGE 1d3c91ed1f55 registry.digitalocean.com/user/app:6ef8a6a84c525b123c5245345a8483f86d05a123 "/rails/bin/docker-e…" 13 minutes ago Up 13 minutes 3000/tcp chat-6ef8a6a84c525b123c5245345a8483f86d05a123 ``` -You can also see just info for app containers with `./bin/mrsk app:info` or just for Traefik with `./bin/mrsk traefik:info`. +You can also see just info for app containers with `mrsk app details` or just for Traefik with `mrsk traefik details`. ### Rollback -If you've discovered a bad deploy, you can quickly rollback by reactivating the old, paused container image. You can see what old containers are available for rollback by running `./bin/mrsk app:containers`. It'll give you a presentation similar to `./bin/mrsk app:info`, but include all the old containers as well. Showing something like this: +If you've discovered a bad deploy, you can quickly rollback by reactivating the old, paused container image. You can see what old containers are available for rollback by running `mrsk app containers`. It'll give you a presentation similar to `mrsk app details`, but include all the old containers as well. Showing something like this: ``` App Host: 164.92.105.119 @@ -247,20 +239,17 @@ badb1aa51db4 registry.digitalocean.com/user/app:6ef8a6a84c525b123c5245345a8483 6f170d1172ae registry.digitalocean.com/user/app:e5d9d7c2b898289dfbc5f7f1334140d984eedae4 "/rails/bin/docker-e…" 31 minutes ago Exited (1) 27 minutes ago chat-e5d9d7c2b898289dfbc5f7f1334140d984eedae4 ``` -From the example above, we can see that `e5d9d7c2b898289dfbc5f7f1334140d984eedae4` was the last version, so it's available as a rollback target. We can perform this rollback by running `./bin/mrsk rollback VERSION=e5d9d7c2b898289dfbc5f7f1334140d984eedae4`. That'll stop `6ef8a6a84c525b123c5245345a8483f86d05a123` and then start `e5d9d7c2b898289dfbc5f7f1334140d984eedae4`. Because the old container is still available, this is very quick. Nothing to download from the registry. +From the example above, we can see that `e5d9d7c2b898289dfbc5f7f1334140d984eedae4` was the last version, so it's available as a rollback target. We can perform this rollback by running `mrsk rollback e5d9d7c2b898289dfbc5f7f1334140d984eedae4`. That'll stop `6ef8a6a84c525b123c5245345a8483f86d05a123` and then start `e5d9d7c2b898289dfbc5f7f1334140d984eedae4`. Because the old container is still available, this is very quick. Nothing to download from the registry. -Note that by default old containers are pruned after 3 days when you run `./bin/mrsk deploy`. +Note that by default old containers are pruned after 3 days when you run `mrsk deploy`. ### Removing -If you wish to remove the entire application, including Traefik, containers, images, and registry session, you can run `./bin/mrsk remove`. This will leave the servers clean. +If you wish to remove the entire application, including Traefik, containers, images, and registry session, you can run `mrsk remove`. This will leave the servers clean. ## Stage of development -This is alpha software. Lots of stuff is missing. Here are some of the areas we seek to improve: - -- Adapterize commands to work with Podman and other container runners -- Integrate with cloud CI pipelines +This is alpha software. Lots of stuff is missing. Lots of stuff will keep moving around for a while. ## License From 4551a2b9d7efa0dc1f62ec68ae23221c95d5bd9e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 14 Jan 2023 12:13:31 +0100 Subject: [PATCH 08/13] Always try to log the command we're running remotely --- lib/mrsk/cli/app.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mrsk/cli/app.rb b/lib/mrsk/cli/app.rb index 509f31c1..dd37107c 100644 --- a/lib/mrsk/cli/app.rb +++ b/lib/mrsk/cli/app.rb @@ -78,7 +78,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base desc "containers", "List all the app containers currently on servers" def containers - on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.list_containers) + "\n\n" } + on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.list_containers, verbosity: Logger::INFO) + "\n\n" } end desc "logs", "Show last 100 log lines from app on servers" From 0b7af9ac149af9908ba468e880854f598ea1c336 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 14 Jan 2023 12:17:04 +0100 Subject: [PATCH 09/13] Simplify --- lib/mrsk/cli/templates/deploy.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/mrsk/cli/templates/deploy.yml b/lib/mrsk/cli/templates/deploy.yml index 1c813442..ee631122 100644 --- a/lib/mrsk/cli/templates/deploy.yml +++ b/lib/mrsk/cli/templates/deploy.yml @@ -1,20 +1,15 @@ -# Name of your application will be used for uniquely configuring Traefik and app containers. +# Name of your application. Used to uniquely configuring Traefik and app containers. # Your Dockerfile should set LABEL service=the-same-value to ensure image pruning works. service: my-app -# Name of the container image +# Name of the container image. image: user/my-app -# All the servers targeted for deploy. You can reference a single server for a command by using SERVERS=192.168.0.1 +# Deploy to these servers. servers: - 192.168.0.1 -# The following envs are made available to the container when started -env: - # Remember never to put passwords or tokens directly into this file, use encrypted credentials - # REDIS_URL: redis://x/y - -# Where your images will be hosted +# Credentials for your image host. registry: # Specify the registry server, if you're not using Docker Hub # server: registry.digitalocean.com / ghcr.io / ... From e19e7f9bdec12e9b492794e4ecf76ea699e7a83c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 14 Jan 2023 12:23:22 +0100 Subject: [PATCH 10/13] Explicitly trying to start a specific version should fail if it can't --- lib/mrsk/cli/app.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mrsk/cli/app.rb b/lib/mrsk/cli/app.rb index dd37107c..7473534e 100644 --- a/lib/mrsk/cli/app.rb +++ b/lib/mrsk/cli/app.rb @@ -23,7 +23,7 @@ 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), raise_on_non_zero_exit: false } + on(MRSK.config.hosts) { execute *MRSK.app.start(version: version) } else on(MRSK.config.hosts) { execute *MRSK.app.start, raise_on_non_zero_exit: false } end From f0a3466d9d89de8308db2f861f107cfb32fedecb Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 14 Jan 2023 12:23:30 +0100 Subject: [PATCH 11/13] Rollback is clearer --- lib/mrsk/cli/app.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/mrsk/cli/app.rb b/lib/mrsk/cli/app.rb index 7473534e..fc4f03ab 100644 --- a/lib/mrsk/cli/app.rb +++ b/lib/mrsk/cli/app.rb @@ -34,12 +34,6 @@ class Mrsk::Cli::App < Mrsk::Cli::Base on(MRSK.config.hosts) { execute *MRSK.app.stop, raise_on_non_zero_exit: false } end - desc "restart", "Start app on servers (use VERSION= to designate which version)" - def restart - invoke :stop - invoke :start - end - desc "details", "Display details about app containers" def details on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.info, verbosity: Logger::INFO) + "\n\n" } From cce3d9ccfb3d3716e6a05c0186f68a75ebaf9179 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 14 Jan 2023 12:23:34 +0100 Subject: [PATCH 12/13] Fix rollback --- lib/mrsk/cli/main.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/mrsk/cli/main.rb b/lib/mrsk/cli/main.rb index 51515f05..044d6ab2 100644 --- a/lib/mrsk/cli/main.rb +++ b/lib/mrsk/cli/main.rb @@ -32,7 +32,10 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base desc "rollback [VERSION]", "Rollback the app to VERSION (that must already be on servers)" def rollback(version) - invoke "mrsk:cli:app:restart" + on(MRSK.config.hosts) do + execute *MRSK.app.stop, raise_on_non_zero_exit: false + execute *MRSK.app.start(version: version) + end end desc "details", "Display details about Traefik and app containers" From 78494bdb0f59deeb2cf93a92fefb6efe92e2dd45 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 14 Jan 2023 12:27:38 +0100 Subject: [PATCH 13/13] Just rely on ENV for now --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a71c360d..2055ed70 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ servers: - 192.168.0.2 registry: username: registry-user-name - password: registry-user-password-needs-more-secure-option + password: <%= ENV["MRSK_REGISTRY_PASSWORD"] %> ``` Now you're ready to deploy a multi-arch image to the servers: @@ -47,7 +47,7 @@ The default registry for Docker is Docker Hub. If you'd like to use a different registry: server: registry.digitalocean.com username: registry-user-name - password: registry-user-password-needs-more-secure-option + password: <%= ENV["MRSK_REGISTRY_PASSWORD"] %> ``` ### Using a different SSH user than root