Skip to content

Commit

Permalink
Switch to proper standalone executable with Thor
Browse files Browse the repository at this point in the history
  • Loading branch information
dhh committed Jan 14, 2023
1 parent bf98a03 commit fed64ef
Show file tree
Hide file tree
Showing 28 changed files with 387 additions and 307 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gemspec

gem "debug"
gem "railties"
10 changes: 6 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -90,6 +91,7 @@ PLATFORMS
DEPENDENCIES
debug
mrsk!
railties

BUNDLED WITH
2.4.3
5 changes: 5 additions & 0 deletions bin/mrsk
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env ruby

require "mrsk/cli"

Mrsk::Cli::Main.start(ARGV)
1 change: 0 additions & 1 deletion lib/mrsk.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ module Mrsk
end

require "mrsk/version"
require "mrsk/engine"
require "mrsk/commander"
9 changes: 9 additions & 0 deletions lib/mrsk/cli.rb
Original file line number Diff line number Diff line change
@@ -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"
98 changes: 98 additions & 0 deletions lib/mrsk/cli/app.rb
Original file line number Diff line number Diff line change
@@ -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=<git-hash> 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=<git-hash> 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
27 changes: 27 additions & 0 deletions lib/mrsk/cli/base.rb
Original file line number Diff line number Diff line change
@@ -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
53 changes: 53 additions & 0 deletions lib/mrsk/cli/build.rb
Original file line number Diff line number Diff line change
@@ -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
87 changes: 87 additions & 0 deletions lib/mrsk/cli/main.rb
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions lib/mrsk/cli/prune.rb
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions lib/mrsk/cli/registry.rb
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions lib/mrsk/cli/server.rb
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading

0 comments on commit fed64ef

Please sign in to comment.