mrsk 0.1.0 → 0.3.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.
data/lib/mrsk/cli/app.rb CHANGED
@@ -23,75 +23,115 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
23
23
  option :version, desc: "Defaults to the most recent git-hash in local repository"
24
24
  def start
25
25
  if (version = options[:version]).present?
26
- on(MRSK.config.hosts) { execute *MRSK.app.start(version: version) }
26
+ on(MRSK.hosts) { execute *MRSK.app.start(version: version) }
27
27
  else
28
- on(MRSK.config.hosts) { execute *MRSK.app.start, raise_on_non_zero_exit: false }
28
+ on(MRSK.hosts) { execute *MRSK.app.start, raise_on_non_zero_exit: false }
29
29
  end
30
30
  end
31
31
 
32
32
  desc "stop", "Stop app on servers"
33
33
  def stop
34
- on(MRSK.config.hosts) { execute *MRSK.app.stop, raise_on_non_zero_exit: false }
34
+ on(MRSK.hosts) { execute *MRSK.app.stop, raise_on_non_zero_exit: false }
35
35
  end
36
36
 
37
37
  desc "details", "Display details about app containers"
38
38
  def details
39
- on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.info, verbosity: Logger::INFO) + "\n\n" }
39
+ on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.info) }
40
40
  end
41
41
 
42
- desc "exec [CMD]", "Execute a custom task on servers passed in as CMD='bin/rake some:task'"
43
- option :once, type: :boolean, default: false
42
+ desc "exec [CMD]", "Execute a custom command on servers"
43
+ option :method, aliases: "-m", default: "exec", desc: "Execution method: [exec] perform inside app container / [run] perform in new container / [ssh] perform over ssh"
44
44
  def exec(cmd)
45
- if options[:once]
46
- on(MRSK.config.primary_host) { puts capture(*MRSK.app.exec(cmd), verbosity: Logger::INFO) }
45
+ runner = \
46
+ case options[:method]
47
+ when "exec" then "exec"
48
+ when "run" then "run_exec"
49
+ when "ssh" then "exec_over_ssh"
50
+ else raise "Unknown method: #{options[:method]}"
51
+ end.inquiry
52
+
53
+ if runner.exec_over_ssh?
54
+ run_locally do
55
+ info "Launching command on #{MRSK.primary_host}"
56
+ exec MRSK.app.exec_over_ssh(cmd, host: MRSK.primary_host)
57
+ end
47
58
  else
48
- on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.exec(cmd), verbosity: Logger::INFO) + "\n\n" }
59
+ on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.send(runner, cmd)) }
49
60
  end
50
61
  end
51
-
52
- desc "console", "Start Rails Console on primary host"
53
- option :host, desc: "Start console on a different host"
62
+
63
+ desc "console", "Start Rails Console on primary host (or specific host set by --hosts)"
54
64
  def console
55
- host = options[:host] || MRSK.config.primary_host
65
+ run_locally do
66
+ info "Launching Rails console on #{MRSK.primary_host}"
67
+ exec MRSK.app.console(host: MRSK.primary_host)
68
+ end
69
+ end
56
70
 
71
+ desc "bash", "Start a bash session on primary host (or specific host set by --hosts)"
72
+ def bash
57
73
  run_locally do
58
- puts "Launching Rails console on #{host}..."
59
- exec MRSK.app.console(host: host)
74
+ info "Launching bash session on #{MRSK.primary_host}"
75
+ exec MRSK.app.bash(host: MRSK.primary_host)
60
76
  end
61
77
  end
62
78
 
63
79
  desc "runner [EXPRESSION]", "Execute Rails runner with given expression"
64
- option :once, type: :boolean, default: false, desc:
65
80
  def runner(expression)
66
- if options[:once]
67
- on(MRSK.config.primary_host) { puts capture(*MRSK.app.exec("bin/rails", "runner", "'#{expression}'"), verbosity: Logger::INFO) }
68
- else
69
- on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.exec("bin/rails", "runner", "'#{expression}'"), verbosity: Logger::INFO) + "\n\n" }
70
- end
81
+ on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.exec("bin/rails", "runner", "'#{expression}'")) }
71
82
  end
72
83
 
73
84
  desc "containers", "List all the app containers currently on servers"
74
85
  def containers
75
- on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.list_containers, verbosity: Logger::INFO) + "\n\n" }
86
+ on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_containers) }
87
+ end
88
+
89
+ desc "current", "Return the current running container ID"
90
+ def current
91
+ on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.current_container_id) }
76
92
  end
77
93
 
78
- desc "logs", "Show last 100 log lines from app on servers"
94
+ desc "logs", "Show lines from app on servers"
95
+ option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
96
+ option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
97
+ option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
98
+ option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
79
99
  def logs
80
100
  # FIXME: Catch when app containers aren't running
81
- on(MRSK.config.hosts) { |host| puts "App Host: #{host}\n" + capture(*MRSK.app.logs) + "\n\n" }
101
+
102
+ grep = options[:grep]
103
+
104
+ if options[:follow]
105
+ run_locally do
106
+ info "Following logs on #{MRSK.primary_host}..."
107
+ info MRSK.app.follow_logs(host: MRSK.primary_host, grep: grep)
108
+ exec MRSK.app.follow_logs(host: MRSK.primary_host, grep: grep)
109
+ end
110
+ else
111
+ since = options[:since]
112
+ lines = options[:lines]
113
+
114
+ on(MRSK.hosts) do |host|
115
+ begin
116
+ puts_by_host host, capture_with_info(*MRSK.app.logs(since: since, lines: lines, grep: grep))
117
+ rescue SSHKit::Command::Failed
118
+ puts_by_host host, "Nothing found"
119
+ end
120
+ end
121
+ end
82
122
  end
83
-
123
+
84
124
  desc "remove", "Remove app containers and images from servers"
85
125
  option :only, default: "", desc: "Use 'containers' or 'images'"
86
126
  def remove
87
127
  case options[:only]
88
128
  when "containers"
89
- on(MRSK.config.hosts) { execute *MRSK.app.remove_containers }
129
+ on(MRSK.hosts) { execute *MRSK.app.remove_containers }
90
130
  when "images"
91
- on(MRSK.config.hosts) { execute *MRSK.app.remove_images }
131
+ on(MRSK.hosts) { execute *MRSK.app.remove_images }
92
132
  else
93
- on(MRSK.config.hosts) { execute *MRSK.app.remove_containers }
94
- on(MRSK.config.hosts) { execute *MRSK.app.remove_images }
133
+ on(MRSK.hosts) { execute *MRSK.app.remove_containers }
134
+ on(MRSK.hosts) { execute *MRSK.app.remove_images }
95
135
  end
96
136
  end
97
137
  end
data/lib/mrsk/cli/base.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  require "thor"
2
- require "sshkit"
3
- require "sshkit/dsl"
2
+ require "mrsk/sshkit_with_ext"
4
3
 
5
4
  module Mrsk::Cli
6
5
  class Base < Thor
@@ -10,12 +9,34 @@ module Mrsk::Cli
10
9
 
11
10
  class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
12
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
+
13
21
  def initialize(*)
14
22
  super
15
- MRSK.verbose = options[:verbose]
23
+ initialize_commander(options)
16
24
  end
17
25
 
18
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
+
19
40
  def print_runtime
20
41
  started_at = Time.now
21
42
  yield
@@ -9,29 +9,45 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
9
9
 
10
10
  desc "push", "Build locally and push app image to registry"
11
11
  def push
12
+ verbose = options[:verbose]
13
+ cli = self
14
+
12
15
  run_locally do
13
16
  begin
14
- debug "Using builder: #{MRSK.builder.name}"
15
- info "Building image may take a while (run with --verbose for progress logging)"
16
- execute *MRSK.builder.push
17
+ MRSK.verbosity(:debug) { execute *MRSK.builder.push }
17
18
  rescue SSHKit::Command::Failed => e
18
- error "Missing compatible builder, so creating a new one first"
19
- execute *MRSK.builder.create
20
- execute *MRSK.builder.push
19
+ if e.message =~ /(no builder)|(no such file or directory)/
20
+ error "Missing compatible builder, so creating a new one first"
21
+
22
+ if cli.create
23
+ MRSK.verbosity(:debug) { execute *MRSK.builder.push }
24
+ end
25
+ else
26
+ raise
27
+ end
21
28
  end
22
29
  end
23
30
  end
24
31
 
25
32
  desc "pull", "Pull app image from the registry onto servers"
26
33
  def pull
27
- on(MRSK.config.hosts) { execute *MRSK.builder.pull }
34
+ on(MRSK.hosts) { execute *MRSK.builder.pull }
28
35
  end
29
36
 
30
37
  desc "create", "Create a local build setup"
31
38
  def create
32
39
  run_locally do
33
- debug "Using builder: #{MRSK.builder.name}"
34
- execute *MRSK.builder.create
40
+ begin
41
+ debug "Using builder: #{MRSK.builder.name}"
42
+ execute *MRSK.builder.create
43
+ rescue SSHKit::Command::Failed => e
44
+ if e.message =~ /stderr=(.*)/
45
+ error "Couldn't create remote builder: #{$1}"
46
+ false
47
+ else
48
+ raise
49
+ end
50
+ end
35
51
  end
36
52
  end
37
53
 
@@ -46,7 +62,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
46
62
  desc "details", "Show the name of the configured builder"
47
63
  def details
48
64
  run_locally do
49
- puts "Builder: #{MRSK.builder.name} (#{MRSK.builder.target.class.name})"
65
+ puts "Builder: #{MRSK.builder.name}"
50
66
  puts capture(*MRSK.builder.info)
51
67
  end
52
68
  end
data/lib/mrsk/cli/main.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "mrsk/cli/base"
2
2
 
3
+ require "mrsk/cli/accessory"
3
4
  require "mrsk/cli/app"
4
5
  require "mrsk/cli/build"
5
6
  require "mrsk/cli/prune"
@@ -8,6 +9,15 @@ require "mrsk/cli/server"
8
9
  require "mrsk/cli/traefik"
9
10
 
10
11
  class Mrsk::Cli::Main < Mrsk::Cli::Base
12
+ desc "setup", "Setup all accessories and deploy the app to servers"
13
+ def setup
14
+ print_runtime do
15
+ invoke "mrsk:cli:server:bootstrap"
16
+ invoke "mrsk:cli:accessory:boot", [ "all" ]
17
+ deploy
18
+ end
19
+ end
20
+
11
21
  desc "deploy", "Deploy the app to servers"
12
22
  def deploy
13
23
  print_runtime do
@@ -32,7 +42,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
32
42
 
33
43
  desc "rollback [VERSION]", "Rollback the app to VERSION (that must already be on servers)"
34
44
  def rollback(version)
35
- on(MRSK.config.hosts) do
45
+ on(MRSK.hosts) do
36
46
  execute *MRSK.app.stop, raise_on_non_zero_exit: false
37
47
  execute *MRSK.app.start(version: version)
38
48
  end
@@ -42,6 +52,14 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
42
52
  def details
43
53
  invoke "mrsk:cli:traefik:details"
44
54
  invoke "mrsk:cli:app:details"
55
+ invoke "mrsk:cli:accessory:details", [ "all" ]
56
+ end
57
+
58
+ desc "config", "Show combined config"
59
+ def config
60
+ run_locally do
61
+ puts MRSK.config.to_h.to_yaml
62
+ end
45
63
  end
46
64
 
47
65
  desc "install", "Create config stub in config/deploy.yml and binstub in bin/mrsk"
@@ -79,6 +97,9 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
79
97
  puts Mrsk::VERSION
80
98
  end
81
99
 
100
+ desc "accessory", "Manage the accessories"
101
+ subcommand "accessory", Mrsk::Cli::Accessory
102
+
82
103
  desc "app", "Manage the application"
83
104
  subcommand "app", Mrsk::Cli::App
84
105
 
@@ -9,11 +9,11 @@ class Mrsk::Cli::Prune < Mrsk::Cli::Base
9
9
 
10
10
  desc "images", "Prune unused images older than 30 days"
11
11
  def images
12
- on(MRSK.config.hosts) { execute *MRSK.prune.images }
12
+ on(MRSK.hosts) { execute *MRSK.prune.images }
13
13
  end
14
14
 
15
15
  desc "containers", "Prune stopped containers for the service older than 3 days"
16
16
  def containers
17
- on(MRSK.config.hosts) { execute *MRSK.prune.containers }
17
+ on(MRSK.hosts) { execute *MRSK.prune.containers }
18
18
  end
19
19
  end
@@ -4,11 +4,15 @@ class Mrsk::Cli::Registry < Mrsk::Cli::Base
4
4
  desc "login", "Login to the registry locally and remotely"
5
5
  def login
6
6
  run_locally { execute *MRSK.registry.login }
7
- on(MRSK.config.hosts) { execute *MRSK.registry.login }
7
+ on(MRSK.hosts) { execute *MRSK.registry.login }
8
+ rescue ArgumentError => e
9
+ puts e.message
8
10
  end
9
11
 
10
12
  desc "logout", "Logout of the registry remotely"
11
13
  def logout
12
- on(MRSK.config.hosts) { execute *MRSK.registry.logout }
14
+ on(MRSK.hosts) { execute *MRSK.registry.logout }
15
+ rescue ArgumentError => e
16
+ puts e.message
13
17
  end
14
18
  end
@@ -3,6 +3,6 @@ require "mrsk/cli/base"
3
3
  class Mrsk::Cli::Server < Mrsk::Cli::Base
4
4
  desc "bootstrap", "Ensure Docker is installed on the servers"
5
5
  def bootstrap
6
- on(MRSK.config.hosts) { execute "which docker || apt-get install docker.io -y" }
6
+ on(MRSK.hosts + MRSK.accessory_hosts) { execute "which docker || (apt-get update -y && apt-get install docker.io -y)" }
7
7
  end
8
8
  end
@@ -3,17 +3,24 @@ require "mrsk/cli/base"
3
3
  class Mrsk::Cli::Traefik < Mrsk::Cli::Base
4
4
  desc "boot", "Boot Traefik on servers"
5
5
  def boot
6
- on(MRSK.config.role(:web).hosts) { execute *MRSK.traefik.run, raise_on_non_zero_exit: false }
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
7
14
  end
8
15
 
9
16
  desc "start", "Start existing Traefik on servers"
10
17
  def start
11
- on(MRSK.config.role(:web).hosts) { execute *MRSK.traefik.start, raise_on_non_zero_exit: false }
18
+ on(MRSK.traefik_hosts) { execute *MRSK.traefik.start, raise_on_non_zero_exit: false }
12
19
  end
13
20
 
14
21
  desc "stop", "Stop Traefik on servers"
15
22
  def stop
16
- on(MRSK.config.role(:web).hosts) { execute *MRSK.traefik.stop, raise_on_non_zero_exit: false }
23
+ on(MRSK.traefik_hosts) { execute *MRSK.traefik.stop, raise_on_non_zero_exit: false }
17
24
  end
18
25
 
19
26
  desc "restart", "Restart Traefik on servers"
@@ -24,21 +31,47 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
24
31
 
25
32
  desc "details", "Display details about Traefik containers from servers"
26
33
  def details
27
- on(MRSK.config.role(:web).hosts) { |host| puts "Traefik Host: #{host}\n" + capture(*MRSK.traefik.info, verbosity: Logger::INFO) + "\n\n" }
34
+ on(MRSK.traefik_hosts) { |host| puts_by_host host, capture_with_info(*MRSK.traefik.info), type: "Traefik" }
28
35
  end
29
36
 
30
- desc "logs", "Show last 100 log lines from Traefik on servers"
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)"
31
42
  def logs
32
- on(MRSK.config.hosts) { |host| puts "Traefik Host: #{host}\n" + capture(*MRSK.traefik.logs) + "\n\n" }
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
33
59
  end
34
60
 
35
61
  desc "remove", "Remove Traefik container and image from servers"
36
62
  def remove
37
63
  invoke :stop
64
+ invoke :remove_container
65
+ invoke :remove_image
66
+ end
38
67
 
39
- on(MRSK.config.role(:web).hosts) do
40
- execute *MRSK.traefik.remove_container
41
- execute *MRSK.traefik.remove_image
42
- end
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 }
43
76
  end
44
77
  end
data/lib/mrsk/cli.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  require "mrsk"
2
2
 
3
- MRSK = Mrsk::Commander.new \
4
- config_file: Pathname.new(File.expand_path("config/deploy.yml"))
5
-
6
3
  module Mrsk::Cli
7
4
  end
8
5
 
6
+ # SSHKit uses instance eval, so we need a global const for ergonomics
7
+ MRSK = Mrsk::Commander.new
8
+
9
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,15 +9,47 @@ require "mrsk/commands/traefik"
6
9
  require "mrsk/commands/registry"
7
10
 
8
11
  class Mrsk::Commander
9
- attr_reader :config
10
- attr_accessor :verbose
12
+ attr_accessor :config_file, :destination, :verbose, :version
11
13
 
12
- def initialize(config_file:)
13
- @config_file = config_file
14
+ def initialize(config_file: nil, destination: nil, verbose: false)
15
+ @config_file, @destination, @verbose = config_file, destination, verbose
14
16
  end
15
17
 
16
18
  def config
17
- @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)
49
+ end
50
+
51
+ def accessory_names
52
+ config.accessories&.collect(&:name) || []
18
53
  end
19
54
 
20
55
 
@@ -38,6 +73,10 @@ class Mrsk::Commander
38
73
  @prune ||= Mrsk::Commands::Prune.new(config)
39
74
  end
40
75
 
76
+ def accessory(name)
77
+ config.accessories.detect { |a| a.name == name }
78
+ end
79
+
41
80
 
42
81
  def verbosity(level)
43
82
  old_level = SSHKit.config.output_verbosity
@@ -48,8 +87,12 @@ class Mrsk::Commander
48
87
  end
49
88
 
50
89
  private
90
+ def cascading_version
91
+ version.presence || ENV["VERSION"] || `git rev-parse HEAD`.strip
92
+ end
93
+
51
94
  # Lazy setup of SSHKit
52
- def setup_with(config)
95
+ def configure_sshkit_with(config)
53
96
  SSHKit::Backend::Netssh.configure { |ssh| ssh.ssh_options = config.ssh_options }
54
97
  SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs
55
98
  SSHKit.config.output_verbosity = :debug if verbose
@@ -0,0 +1,106 @@
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, :files, :directories, :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 exec(*command, interactive: false)
50
+ docker :exec,
51
+ ("-it" if interactive),
52
+ *env_args,
53
+ *volume_args,
54
+ service_name,
55
+ *command
56
+ end
57
+
58
+ def run_exec(*command, interactive: false)
59
+ docker :run,
60
+ ("-it" if interactive),
61
+ "--rm",
62
+ *env_args,
63
+ *volume_args,
64
+ image,
65
+ *command
66
+ end
67
+
68
+ def bash(host:)
69
+ exec_over_ssh "bash", host: host
70
+ end
71
+
72
+ def ensure_local_file_present(local_file)
73
+ if !local_file.is_a?(StringIO) && !Pathname.new(local_file).exist?
74
+ raise "Missing file: #{local_file}"
75
+ end
76
+ end
77
+
78
+ def make_directory_for(remote_file)
79
+ make_directory Pathname.new(remote_file).dirname.to_s
80
+ end
81
+
82
+ def make_directory(path)
83
+ [ :mkdir, "-p", path ]
84
+ end
85
+
86
+ def remove_service_directory
87
+ [ :rm, "-rf", service_name ]
88
+ end
89
+
90
+ def remove_container
91
+ docker :container, :prune, "-f", *service_filter
92
+ end
93
+
94
+ def remove_image
95
+ docker :image, :prune, "-a", "-f", *service_filter
96
+ end
97
+
98
+ private
99
+ def exec_over_ssh(*command, host:)
100
+ run_over_ssh run_exec(*command, interactive: true).join(" "), host: host
101
+ end
102
+
103
+ def service_filter
104
+ [ "--filter", "label=service=#{service_name}" ]
105
+ end
106
+ end