mrsk 0.0.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +193 -77
  3. data/bin/mrsk +5 -0
  4. data/lib/mrsk/cli/accessory.rb +85 -0
  5. data/lib/mrsk/cli/app.rb +123 -0
  6. data/lib/mrsk/cli/base.rb +48 -0
  7. data/lib/mrsk/cli/build.rb +53 -0
  8. data/lib/mrsk/cli/main.rb +110 -0
  9. data/lib/mrsk/cli/prune.rb +19 -0
  10. data/lib/mrsk/cli/registry.rb +18 -0
  11. data/lib/mrsk/cli/server.rb +8 -0
  12. data/lib/mrsk/cli/templates/deploy.yml +17 -0
  13. data/lib/mrsk/cli/traefik.rb +77 -0
  14. data/lib/mrsk/cli.rb +9 -0
  15. data/lib/mrsk/commander.rb +45 -5
  16. data/lib/mrsk/commands/accessory.rb +61 -0
  17. data/lib/mrsk/commands/app.rb +50 -10
  18. data/lib/mrsk/commands/base.rb +13 -9
  19. data/lib/mrsk/commands/builder/base.rb +26 -0
  20. data/lib/mrsk/commands/builder/multiarch/remote.rb +12 -4
  21. data/lib/mrsk/commands/builder/multiarch.rb +17 -9
  22. data/lib/mrsk/commands/builder/native/remote.rb +71 -0
  23. data/lib/mrsk/commands/builder/native.rb +3 -7
  24. data/lib/mrsk/commands/builder.rb +11 -7
  25. data/lib/mrsk/commands/traefik.rb +13 -3
  26. data/lib/mrsk/configuration/accessory.rb +60 -0
  27. data/lib/mrsk/configuration/role.rb +46 -14
  28. data/lib/mrsk/configuration.rb +86 -43
  29. data/lib/mrsk/sshkit_with_ext.rb +12 -0
  30. data/lib/mrsk/utils.rb +29 -0
  31. data/lib/mrsk/version.rb +1 -1
  32. data/lib/mrsk.rb +0 -1
  33. metadata +40 -18
  34. data/lib/mrsk/engine.rb +0 -4
  35. data/lib/tasks/mrsk/app.rake +0 -97
  36. data/lib/tasks/mrsk/build.rake +0 -52
  37. data/lib/tasks/mrsk/mrsk.rake +0 -37
  38. data/lib/tasks/mrsk/prune.rake +0 -18
  39. data/lib/tasks/mrsk/registry.rake +0 -16
  40. data/lib/tasks/mrsk/server.rake +0 -11
  41. data/lib/tasks/mrsk/setup.rb +0 -6
  42. data/lib/tasks/mrsk/templates/deploy.yml +0 -24
  43. data/lib/tasks/mrsk/templates/mrsk +0 -8
  44. data/lib/tasks/mrsk/traefik.rake +0 -41
@@ -0,0 +1,48 @@
1
+ require "thor"
2
+ require "mrsk/sshkit_with_ext"
3
+
4
+ module Mrsk::Cli
5
+ class Base < Thor
6
+ include SSHKit::DSL
7
+
8
+ def self.exit_on_failure?() true end
9
+
10
+ class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
11
+
12
+ class_option :version, desc: "Run commands against a specific app version"
13
+
14
+ class_option :primary, type: :boolean, aliases: "-p", desc: "Run commands only on primary host instead of all"
15
+ class_option :hosts, aliases: "-h", desc: "Run commands on these hosts instead of all (separate by comma)"
16
+ class_option :roles, aliases: "-r", desc: "Run commands on these roles instead of all (separate by comma)"
17
+
18
+ class_option :config_file, aliases: "-c", default: "config/deploy.yml", desc: "Path to config file (default: config/deploy.yml)"
19
+ class_option :destination, aliases: "-d", desc: "Specify destination to be used for config file (west -> deploy.west.yml)"
20
+
21
+ def initialize(*)
22
+ super
23
+ initialize_commander(options)
24
+ end
25
+
26
+ private
27
+ def initialize_commander(options)
28
+ MRSK.tap do |commander|
29
+ commander.config_file = Pathname.new(File.expand_path(options[:config_file]))
30
+ commander.destination = options[:destination]
31
+ commander.verbose = options[:verbose]
32
+ commander.version = options[:version]
33
+
34
+ commander.specific_hosts = options[:hosts]&.split(",")
35
+ commander.specific_roles = options[:roles]&.split(",")
36
+ commander.specific_primary! if options[:primary]
37
+ end
38
+ end
39
+
40
+ def print_runtime
41
+ started_at = Time.now
42
+ yield
43
+ ensure
44
+ runtime = Time.now - started_at
45
+ puts " Finished all in #{sprintf("%.1f seconds", runtime)}"
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,53 @@
1
+ require "mrsk/cli/base"
2
+
3
+ class Mrsk::Cli::Build < Mrsk::Cli::Base
4
+ desc "deliver", "Deliver a newly built app image to servers"
5
+ def deliver
6
+ invoke :push
7
+ invoke :pull
8
+ end
9
+
10
+ desc "push", "Build locally and push app image to registry"
11
+ def push
12
+ verbose = options[:verbose]
13
+
14
+ run_locally do
15
+ begin
16
+ MRSK.verbosity(:debug) { execute *MRSK.builder.push }
17
+ rescue SSHKit::Command::Failed => e
18
+ error "Missing compatible builder, so creating a new one first"
19
+ execute *MRSK.builder.create
20
+ MRSK.verbosity(:debug) { execute *MRSK.builder.push }
21
+ end
22
+ end
23
+ end
24
+
25
+ desc "pull", "Pull app image from the registry onto servers"
26
+ def pull
27
+ on(MRSK.hosts) { execute *MRSK.builder.pull }
28
+ end
29
+
30
+ desc "create", "Create a local build setup"
31
+ def create
32
+ run_locally do
33
+ debug "Using builder: #{MRSK.builder.name}"
34
+ execute *MRSK.builder.create
35
+ end
36
+ end
37
+
38
+ desc "remove", "Remove local build setup"
39
+ def remove
40
+ run_locally do
41
+ debug "Using builder: #{MRSK.builder.name}"
42
+ execute *MRSK.builder.remove
43
+ end
44
+ end
45
+
46
+ desc "details", "Show the name of the configured builder"
47
+ def details
48
+ run_locally do
49
+ puts "Builder: #{MRSK.builder.name}"
50
+ puts capture(*MRSK.builder.info)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,110 @@
1
+ require "mrsk/cli/base"
2
+
3
+ require "mrsk/cli/accessory"
4
+ require "mrsk/cli/app"
5
+ require "mrsk/cli/build"
6
+ require "mrsk/cli/prune"
7
+ require "mrsk/cli/registry"
8
+ require "mrsk/cli/server"
9
+ require "mrsk/cli/traefik"
10
+
11
+ class Mrsk::Cli::Main < Mrsk::Cli::Base
12
+ desc "deploy", "Deploy the app to servers"
13
+ def deploy
14
+ print_runtime do
15
+ invoke "mrsk:cli:server:bootstrap"
16
+ invoke "mrsk:cli:registry:login"
17
+ invoke "mrsk:cli:build:deliver"
18
+ invoke "mrsk:cli:traefik:boot"
19
+ invoke "mrsk:cli:app:stop"
20
+ invoke "mrsk:cli:app:boot"
21
+ invoke "mrsk:cli:prune:all"
22
+ end
23
+ end
24
+
25
+ desc "redeploy", "Deploy new version of the app to servers (without bootstrapping servers, starting Traefik, pruning, and registry login)"
26
+ def redeploy
27
+ print_runtime do
28
+ invoke "mrsk:cli:build:deliver"
29
+ invoke "mrsk:cli:app:stop"
30
+ invoke "mrsk:cli:app:boot"
31
+ end
32
+ end
33
+
34
+ desc "rollback [VERSION]", "Rollback the app to VERSION (that must already be on servers)"
35
+ def rollback(version)
36
+ on(MRSK.hosts) do
37
+ execute *MRSK.app.stop, raise_on_non_zero_exit: false
38
+ execute *MRSK.app.start(version: version)
39
+ end
40
+ end
41
+
42
+ desc "details", "Display details about Traefik and app containers"
43
+ def details
44
+ invoke "mrsk:cli:traefik:details"
45
+ invoke "mrsk:cli:app:details"
46
+ end
47
+
48
+ desc "config", "Show combined config"
49
+ def config
50
+ run_locally do
51
+ pp MRSK.config.to_h
52
+ end
53
+ end
54
+
55
+ desc "install", "Create config stub in config/deploy.yml and binstub in bin/mrsk"
56
+ option :skip_binstub, type: :boolean, default: false, desc: "Skip adding MRSK to the Gemfile and creating bin/mrsk binstub"
57
+ def install
58
+ require "fileutils"
59
+
60
+ if (deploy_file = Pathname.new(File.expand_path("config/deploy.yml"))).exist?
61
+ puts "Config file already exists in config/deploy.yml (remove first to create a new one)"
62
+ else
63
+ FileUtils.cp_r Pathname.new(File.expand_path("templates/deploy.yml", __dir__)), deploy_file
64
+ puts "Created configuration file in config/deploy.yml"
65
+ end
66
+
67
+ unless options[:skip_binstub]
68
+ if (binstub = Pathname.new(File.expand_path("bin/mrsk"))).exist?
69
+ puts "Binstub already exists in bin/mrsk (remove first to create a new one)"
70
+ else
71
+ `bundle add mrsk`
72
+ `bundle binstubs mrsk`
73
+ puts "Created binstub file in bin/mrsk"
74
+ end
75
+ end
76
+ end
77
+
78
+ desc "remove", "Remove Traefik, app, and registry session from servers"
79
+ def remove
80
+ invoke "mrsk:cli:traefik:remove"
81
+ invoke "mrsk:cli:app:remove"
82
+ invoke "mrsk:cli:registry:logout"
83
+ end
84
+
85
+ desc "version", "Display the MRSK version"
86
+ def version
87
+ puts Mrsk::VERSION
88
+ end
89
+
90
+ desc "accessory", "Manage the accessories"
91
+ subcommand "accessory", Mrsk::Cli::Accessory
92
+
93
+ desc "app", "Manage the application"
94
+ subcommand "app", Mrsk::Cli::App
95
+
96
+ desc "build", "Build the application image"
97
+ subcommand "build", Mrsk::Cli::Build
98
+
99
+ desc "prune", "Prune old application images and containers"
100
+ subcommand "prune", Mrsk::Cli::Prune
101
+
102
+ desc "registry", "Login and out of the image registry"
103
+ subcommand "registry", Mrsk::Cli::Registry
104
+
105
+ desc "server", "Bootstrap servers with Docker"
106
+ subcommand "server", Mrsk::Cli::Server
107
+
108
+ desc "traefik", "Manage the Traefik load balancer"
109
+ subcommand "traefik", Mrsk::Cli::Traefik
110
+ end
@@ -0,0 +1,19 @@
1
+ require "mrsk/cli/base"
2
+
3
+ class Mrsk::Cli::Prune < Mrsk::Cli::Base
4
+ desc "all", "Prune unused images and stopped containers"
5
+ def all
6
+ invoke :containers
7
+ invoke :images
8
+ end
9
+
10
+ desc "images", "Prune unused images older than 30 days"
11
+ def images
12
+ on(MRSK.hosts) { execute *MRSK.prune.images }
13
+ end
14
+
15
+ desc "containers", "Prune stopped containers for the service older than 3 days"
16
+ def containers
17
+ on(MRSK.hosts) { execute *MRSK.prune.containers }
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ require "mrsk/cli/base"
2
+
3
+ class Mrsk::Cli::Registry < Mrsk::Cli::Base
4
+ desc "login", "Login to the registry locally and remotely"
5
+ def login
6
+ run_locally { execute *MRSK.registry.login }
7
+ on(MRSK.hosts) { execute *MRSK.registry.login }
8
+ rescue ArgumentError => e
9
+ puts e.message
10
+ end
11
+
12
+ desc "logout", "Logout of the registry remotely"
13
+ def logout
14
+ on(MRSK.hosts) { execute *MRSK.registry.logout }
15
+ rescue ArgumentError => e
16
+ puts e.message
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ require "mrsk/cli/base"
2
+
3
+ class Mrsk::Cli::Server < Mrsk::Cli::Base
4
+ desc "bootstrap", "Ensure Docker is installed on the servers"
5
+ def bootstrap
6
+ on(MRSK.hosts) { execute "which docker || (apt-get update -y && apt-get install docker.io -y)" }
7
+ end
8
+ end
@@ -0,0 +1,17 @@
1
+ # Name of your application. Used to uniquely configuring Traefik and app containers.
2
+ # Your Dockerfile should set LABEL service=the-same-value to ensure image pruning works.
3
+ service: my-app
4
+
5
+ # Name of the container image.
6
+ image: user/my-app
7
+
8
+ # Deploy to these servers.
9
+ servers:
10
+ - 192.168.0.1
11
+
12
+ # Credentials for your image host.
13
+ registry:
14
+ # Specify the registry server, if you're not using Docker Hub
15
+ # server: registry.digitalocean.com / ghcr.io / ...
16
+ username: my-user
17
+ password: my-password-should-go-somewhere-safe
@@ -0,0 +1,77 @@
1
+ require "mrsk/cli/base"
2
+
3
+ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
4
+ desc "boot", "Boot Traefik on servers"
5
+ def boot
6
+ on(MRSK.traefik_hosts) { execute *MRSK.traefik.run, raise_on_non_zero_exit: false }
7
+ end
8
+
9
+ desc "reboot", "Reboot Traefik on servers (stop container, remove container, start new container)"
10
+ def reboot
11
+ invoke :stop
12
+ invoke :remove_container
13
+ invoke :boot
14
+ end
15
+
16
+ desc "start", "Start existing Traefik on servers"
17
+ def start
18
+ on(MRSK.traefik_hosts) { execute *MRSK.traefik.start, raise_on_non_zero_exit: false }
19
+ end
20
+
21
+ desc "stop", "Stop Traefik on servers"
22
+ def stop
23
+ on(MRSK.traefik_hosts) { execute *MRSK.traefik.stop, raise_on_non_zero_exit: false }
24
+ end
25
+
26
+ desc "restart", "Restart Traefik on servers"
27
+ def restart
28
+ invoke :stop
29
+ invoke :start
30
+ end
31
+
32
+ desc "details", "Display details about Traefik containers from servers"
33
+ def details
34
+ on(MRSK.traefik_hosts) { |host| puts_by_host host, capture_with_info(*MRSK.traefik.info), type: "Traefik" }
35
+ end
36
+
37
+ desc "logs", "Show log lines from Traefik on servers"
38
+ option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
39
+ option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
40
+ option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
41
+ option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
42
+ def logs
43
+ grep = options[:grep]
44
+
45
+ if options[:follow]
46
+ run_locally do
47
+ info "Following logs on #{MRSK.primary_host}..."
48
+ info MRSK.traefik.follow_logs(host: MRSK.primary_host, grep: grep)
49
+ exec MRSK.traefik.follow_logs(host: MRSK.primary_host, grep: grep)
50
+ end
51
+ else
52
+ since = options[:since]
53
+ lines = options[:lines]
54
+
55
+ on(MRSK.traefik_hosts) do |host|
56
+ puts_by_host host, capture(*MRSK.traefik.logs(since: since, lines: lines, grep: grep)), type: "Traefik"
57
+ end
58
+ end
59
+ end
60
+
61
+ desc "remove", "Remove Traefik container and image from servers"
62
+ def remove
63
+ invoke :stop
64
+ invoke :remove_container
65
+ invoke :remove_image
66
+ end
67
+
68
+ desc "remove_container", "Remove Traefik container from servers"
69
+ def remove_container
70
+ on(MRSK.traefik_hosts) { execute *MRSK.traefik.remove_container }
71
+ end
72
+
73
+ desc "remove_container", "Remove Traefik image from servers"
74
+ def remove_image
75
+ on(MRSK.traefik_hosts) { execute *MRSK.traefik.remove_image }
76
+ end
77
+ end
data/lib/mrsk/cli.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "mrsk"
2
+
3
+ module Mrsk::Cli
4
+ end
5
+
6
+ # SSHKit uses instance eval, so we need a global const for ergonomics
7
+ MRSK = Mrsk::Commander.new
8
+
9
+ require "mrsk/cli/main"
@@ -1,4 +1,7 @@
1
+ require "active_support/core_ext/enumerable"
2
+
1
3
  require "mrsk/configuration"
4
+ require "mrsk/commands/accessory"
2
5
  require "mrsk/commands/app"
3
6
  require "mrsk/commands/builder"
4
7
  require "mrsk/commands/prune"
@@ -6,14 +9,43 @@ require "mrsk/commands/traefik"
6
9
  require "mrsk/commands/registry"
7
10
 
8
11
  class Mrsk::Commander
9
- attr_reader :config_file, :config, :verbose
12
+ attr_accessor :config_file, :destination, :verbose, :version
10
13
 
11
- def initialize(config_file:, verbose: false)
12
- @config_file, @verbose = config_file, verbose
14
+ def initialize(config_file: nil, destination: nil, verbose: false)
15
+ @config_file, @destination, @verbose = config_file, destination, verbose
13
16
  end
14
17
 
15
18
  def config
16
- @config ||= Mrsk::Configuration.load_file(config_file).tap { |config| setup_with(config) }
19
+ @config ||= \
20
+ Mrsk::Configuration
21
+ .create_from(config_file, destination: destination, version: cascading_version)
22
+ .tap { |config| configure_sshkit_with(config) }
23
+ end
24
+
25
+ attr_accessor :specific_hosts
26
+
27
+ def specific_primary!
28
+ self.specific_hosts = [ config.primary_web_host ]
29
+ end
30
+
31
+ def specific_roles=(role_names)
32
+ self.specific_hosts = config.roles.select { |r| role_names.include?(r.name) }.flat_map(&:hosts) if role_names.present?
33
+ end
34
+
35
+ def primary_host
36
+ specific_hosts&.sole || config.primary_web_host
37
+ end
38
+
39
+ def hosts
40
+ specific_hosts || config.all_hosts
41
+ end
42
+
43
+ def traefik_hosts
44
+ specific_hosts || config.traefik_hosts
45
+ end
46
+
47
+ def accessory_hosts
48
+ specific_hosts || config.accessories.collect(&:host)
17
49
  end
18
50
 
19
51
 
@@ -37,6 +69,10 @@ class Mrsk::Commander
37
69
  @prune ||= Mrsk::Commands::Prune.new(config)
38
70
  end
39
71
 
72
+ def accessory(name)
73
+ (@accessories ||= {})[name] ||= Mrsk::Commands::Accessory.new(config, name: name)
74
+ end
75
+
40
76
 
41
77
  def verbosity(level)
42
78
  old_level = SSHKit.config.output_verbosity
@@ -47,8 +83,12 @@ class Mrsk::Commander
47
83
  end
48
84
 
49
85
  private
86
+ def cascading_version
87
+ version.presence || ENV["VERSION"] || `git rev-parse HEAD`.strip
88
+ end
89
+
50
90
  # Lazy setup of SSHKit
51
- def setup_with(config)
91
+ def configure_sshkit_with(config)
52
92
  SSHKit::Backend::Netssh.configure { |ssh| ssh.ssh_options = config.ssh_options }
53
93
  SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs
54
94
  SSHKit.config.output_verbosity = :debug if verbose
@@ -0,0 +1,61 @@
1
+ require "mrsk/commands/base"
2
+
3
+ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
4
+ attr_reader :accessory_config
5
+ delegate :service_name, :image, :host, :port, :env_args, :volume_args, :label_args, to: :accessory_config
6
+
7
+ def initialize(config, name:)
8
+ super(config)
9
+ @accessory_config = config.accessory(name)
10
+ end
11
+
12
+ def run
13
+ docker :run,
14
+ "--name", service_name,
15
+ "-d",
16
+ "--restart", "unless-stopped",
17
+ "-p", port,
18
+ *env_args,
19
+ *volume_args,
20
+ *label_args,
21
+ image
22
+ end
23
+
24
+ def start
25
+ docker :container, :start, service_name
26
+ end
27
+
28
+ def stop
29
+ docker :container, :stop, service_name
30
+ end
31
+
32
+ def info
33
+ docker :ps, *service_filter
34
+ end
35
+
36
+ def logs(since: nil, lines: nil, grep: nil)
37
+ pipe \
38
+ docker(:logs, service_name, (" --since #{since}" if since), (" -n #{lines}" if lines), "-t", "2>&1"),
39
+ ("grep '#{grep}'" if grep)
40
+ end
41
+
42
+ def follow_logs(grep: nil)
43
+ run_over_ssh pipe(
44
+ docker(:logs, service_name, "-t", "-n", "10", "-f", "2>&1"),
45
+ ("grep '#{grep}'" if grep)
46
+ ).join(" "), host: host
47
+ end
48
+
49
+ def remove_container
50
+ docker :container, :prune, "-f", *service_filter
51
+ end
52
+
53
+ def remove_image
54
+ docker :image, :prune, "-a", "-f", *service_filter
55
+ end
56
+
57
+ private
58
+ def service_filter
59
+ [ "--filter", "label=service=#{service_name}" ]
60
+ end
61
+ end
@@ -8,40 +8,72 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
8
8
  "-d",
9
9
  "--restart unless-stopped",
10
10
  "--name", config.service_with_version,
11
- "-e", redact("RAILS_MASTER_KEY=#{config.master_key}"),
12
- *config.env_args,
11
+ *rails_master_key_arg,
12
+ *role.env_args,
13
+ *config.volume_args,
13
14
  *role.label_args,
14
15
  config.absolute_image,
15
16
  role.cmd
16
17
  end
17
18
 
18
- def start
19
- docker :start, config.service_with_version
19
+ def start(version: config.version)
20
+ docker :start, "#{config.service}-#{version}"
21
+ end
22
+
23
+ def current_container_id
24
+ docker :ps, "-q", *service_filter
20
25
  end
21
26
 
22
27
  def stop
23
- [ "docker ps -q #{service_filter.join(" ")} | xargs docker stop" ]
28
+ pipe current_container_id, "xargs docker stop"
24
29
  end
25
30
 
26
31
  def info
27
32
  docker :ps, *service_filter
28
33
  end
29
34
 
30
- def logs
31
- [ "docker ps -q #{service_filter.join(" ")} | xargs docker logs -n 100 -t" ]
35
+ def logs(since: nil, lines: nil, grep: nil)
36
+ pipe \
37
+ current_container_id,
38
+ "xargs docker logs#{" --since #{since}" if since}#{" -n #{lines}" if lines} -t 2>&1",
39
+ ("grep '#{grep}'" if grep)
32
40
  end
33
41
 
34
42
  def exec(*command, interactive: false)
35
43
  docker :exec,
36
44
  ("-it" if interactive),
37
- "-e", redact("RAILS_MASTER_KEY=#{config.master_key}"),
45
+ *rails_master_key_arg,
38
46
  *config.env_args,
47
+ *config.volume_args,
39
48
  config.service_with_version,
40
49
  *command
41
50
  end
42
51
 
43
- def console
44
- "ssh -t #{config.ssh_user}@#{config.primary_host} '#{exec("bin/rails", "c", interactive: true).join(" ")}'"
52
+ def run_exec(*command, interactive: false)
53
+ docker :run,
54
+ ("-it" if interactive),
55
+ "--rm",
56
+ *rails_master_key_arg,
57
+ *config.env_args,
58
+ *config.volume_args,
59
+ config.absolute_image,
60
+ *command
61
+ end
62
+
63
+ def follow_logs(host:, grep: nil)
64
+ run_over_ssh pipe(
65
+ current_container_id,
66
+ "xargs docker logs -t -n 10 -f 2>&1",
67
+ ("grep '#{grep}'" if grep)
68
+ ).join(" "), host: host
69
+ end
70
+
71
+ def console(host:)
72
+ exec_over_ssh "bin/rails", "c", host: host
73
+ end
74
+
75
+ def bash(host:)
76
+ exec_over_ssh "bash", host: host
45
77
  end
46
78
 
47
79
  def list_containers
@@ -57,7 +89,15 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
57
89
  end
58
90
 
59
91
  private
92
+ def exec_over_ssh(*command, host:)
93
+ run_over_ssh run_exec(*command, interactive: true).join(" "), host: host
94
+ end
95
+
60
96
  def service_filter
61
97
  [ "--filter", "label=service=#{config.service}" ]
62
98
  end
99
+
100
+ def rails_master_key_arg
101
+ [ "-e", redact("RAILS_MASTER_KEY=#{config.master_key}") ]
102
+ end
63
103
  end
@@ -1,7 +1,7 @@
1
- require "sshkit"
2
-
3
1
  module Mrsk::Commands
4
2
  class Base
3
+ delegate :redact, to: Mrsk::Utils
4
+
5
5
  attr_accessor :config
6
6
 
7
7
  def initialize(config)
@@ -9,19 +9,23 @@ module Mrsk::Commands
9
9
  end
10
10
 
11
11
  private
12
- def combine(*commands)
12
+ def combine(*commands, by: "&&")
13
13
  commands
14
- .collect { |command| command + [ "&&" ] }.flatten # Join commands with &&
15
- .tap { |commands| commands.pop } # Remove trailing &&
14
+ .compact
15
+ .collect { |command| Array(command) + [ by ] }.flatten # Join commands
16
+ .tap { |commands| commands.pop } # Remove trailing combiner
17
+ end
18
+
19
+ def pipe(*commands)
20
+ combine *commands, by: "|"
16
21
  end
17
22
 
18
23
  def docker(*args)
19
24
  args.compact.unshift :docker
20
25
  end
21
26
 
22
- # Copied from SSHKit::Backend::Abstract#redact to be available inside Commands classes
23
- def redact(arg) # Used in execute_command to hide redact() args a user passes in
24
- arg.to_s.extend(SSHKit::Redaction) # to_s due to our inability to extend Integer, etc
25
- end
27
+ def run_over_ssh(command, host:)
28
+ "ssh -t #{config.ssh_user}@#{host} '#{command}'"
29
+ end
26
30
  end
27
31
  end
@@ -0,0 +1,26 @@
1
+ require "mrsk/commands/base"
2
+
3
+ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
4
+ delegate :argumentize, to: Mrsk::Utils
5
+
6
+ def pull
7
+ docker :pull, config.absolute_image
8
+ end
9
+
10
+ def build_args
11
+ argumentize "--build-arg", args, redacted: true
12
+ end
13
+
14
+ def build_secrets
15
+ argumentize "--secret", secrets.collect { |secret| [ "id", secret ] }
16
+ end
17
+
18
+ private
19
+ def args
20
+ (config.builder && config.builder["args"]) || {}
21
+ end
22
+
23
+ def secrets
24
+ (config.builder && config.builder["secrets"]) || []
25
+ end
26
+ end