Switch to proper standalone executable with Thor
This commit is contained in:
9
lib/mrsk/cli.rb
Normal file
9
lib/mrsk/cli.rb
Normal file
@@ -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
lib/mrsk/cli/app.rb
Normal file
98
lib/mrsk/cli/app.rb
Normal file
@@ -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
lib/mrsk/cli/base.rb
Normal file
27
lib/mrsk/cli/base.rb
Normal file
@@ -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
lib/mrsk/cli/build.rb
Normal file
53
lib/mrsk/cli/build.rb
Normal file
@@ -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
lib/mrsk/cli/main.rb
Normal file
87
lib/mrsk/cli/main.rb
Normal file
@@ -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
lib/mrsk/cli/prune.rb
Normal file
19
lib/mrsk/cli/prune.rb
Normal file
@@ -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
lib/mrsk/cli/registry.rb
Normal file
14
lib/mrsk/cli/registry.rb
Normal file
@@ -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
lib/mrsk/cli/server.rb
Normal file
8
lib/mrsk/cli/server.rb
Normal file
@@ -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
|
||||
22
lib/mrsk/cli/templates/deploy.yml
Normal file
22
lib/mrsk/cli/templates/deploy.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
# Name of your application will be used for 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
|
||||
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
|
||||
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
|
||||
registry:
|
||||
# Specify the registry server, if you're not using Docker Hub
|
||||
# server: registry.digitalocean.com / ghcr.io / ...
|
||||
username: my-user
|
||||
password: my-password-should-go-somewhere-safe
|
||||
44
lib/mrsk/cli/traefik.rb
Normal file
44
lib/mrsk/cli/traefik.rb
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
module Mrsk
|
||||
class Engine < ::Rails::Engine
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user