mrsk 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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