mrsk 0.0.3 → 0.2.0

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