mrsk 0.9.1 → 0.10.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/main.rb CHANGED
@@ -1,77 +1,104 @@
1
1
  class Mrsk::Cli::Main < Mrsk::Cli::Base
2
2
  desc "setup", "Setup all accessories and deploy app to servers"
3
3
  def setup
4
- print_runtime do
5
- invoke "mrsk:cli:server:bootstrap"
6
- invoke "mrsk:cli:accessory:boot", [ "all" ]
7
- deploy
4
+ with_lock do
5
+ print_runtime do
6
+ invoke "mrsk:cli:server:bootstrap"
7
+ invoke "mrsk:cli:accessory:boot", [ "all" ]
8
+ deploy
9
+ end
8
10
  end
9
11
  end
10
12
 
11
13
  desc "deploy", "Deploy app to servers"
14
+ option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
12
15
  def deploy
13
- runtime = print_runtime do
14
- say "Ensure curl and Docker are installed...", :magenta
15
- invoke "mrsk:cli:server:bootstrap"
16
+ with_lock do
17
+ invoke_options = deploy_options
16
18
 
17
- say "Log into image registry...", :magenta
18
- invoke "mrsk:cli:registry:login"
19
+ runtime = print_runtime do
20
+ say "Ensure curl and Docker are installed...", :magenta
21
+ invoke "mrsk:cli:server:bootstrap", [], invoke_options
19
22
 
20
- say "Build and push app image...", :magenta
21
- invoke "mrsk:cli:build:deliver"
23
+ say "Log into image registry...", :magenta
24
+ invoke "mrsk:cli:registry:login", [], invoke_options
22
25
 
23
- say "Ensure Traefik is running...", :magenta
24
- invoke "mrsk:cli:traefik:boot"
26
+ if options[:skip_push]
27
+ say "Pull app image...", :magenta
28
+ invoke "mrsk:cli:build:pull", [], invoke_options
29
+ else
30
+ say "Build and push app image...", :magenta
31
+ invoke "mrsk:cli:build:deliver", [], invoke_options
32
+ end
25
33
 
26
- say "Ensure app can pass healthcheck...", :magenta
27
- invoke "mrsk:cli:healthcheck:perform"
34
+ say "Ensure Traefik is running...", :magenta
35
+ invoke "mrsk:cli:traefik:boot", [], invoke_options
28
36
 
29
- invoke "mrsk:cli:app:boot"
37
+ say "Ensure app can pass healthcheck...", :magenta
38
+ invoke "mrsk:cli:healthcheck:perform", [], invoke_options
30
39
 
31
- say "Prune old containers and images...", :magenta
32
- invoke "mrsk:cli:prune:all"
33
- end
40
+ invoke "mrsk:cli:app:boot", [], invoke_options
34
41
 
35
- audit_broadcast "Deployed app in #{runtime.to_i} seconds" unless options[:skip_broadcast]
42
+ say "Prune old containers and images...", :magenta
43
+ invoke "mrsk:cli:prune:all", [], invoke_options
44
+ end
45
+
46
+ audit_broadcast "Deployed #{service_version} in #{runtime.round} seconds" unless options[:skip_broadcast]
47
+ end
36
48
  end
37
49
 
38
50
  desc "redeploy", "Deploy app to servers without bootstrapping servers, starting Traefik, pruning, and registry login"
51
+ option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
39
52
  def redeploy
40
- runtime = print_runtime do
41
- say "Build and push app image...", :magenta
42
- invoke "mrsk:cli:build:deliver"
43
-
44
- say "Ensure app can pass healthcheck...", :magenta
45
- invoke "mrsk:cli:healthcheck:perform"
53
+ with_lock do
54
+ invoke_options = deploy_options
55
+
56
+ runtime = print_runtime do
57
+ if options[:skip_push]
58
+ say "Pull app image...", :magenta
59
+ invoke "mrsk:cli:build:pull", [], invoke_options
60
+ else
61
+ say "Build and push app image...", :magenta
62
+ invoke "mrsk:cli:build:deliver", [], invoke_options
63
+ end
64
+
65
+ say "Ensure app can pass healthcheck...", :magenta
66
+ invoke "mrsk:cli:healthcheck:perform", [], invoke_options
67
+
68
+ invoke "mrsk:cli:app:boot", [], invoke_options
69
+ end
46
70
 
47
- invoke "mrsk:cli:app:boot"
71
+ audit_broadcast "Redeployed #{service_version} in #{runtime.round} seconds" unless options[:skip_broadcast]
48
72
  end
49
-
50
- audit_broadcast "Redeployed app in #{runtime.to_i} seconds" unless options[:skip_broadcast]
51
73
  end
52
74
 
53
75
  desc "rollback [VERSION]", "Rollback app to VERSION"
54
76
  def rollback(version)
55
- MRSK.version = version
77
+ with_lock do
78
+ MRSK.config.version = version
56
79
 
57
- if container_name_available?(MRSK.config.service_with_version)
58
- say "Start version #{version}, then wait #{MRSK.config.readiness_delay}s for app to boot before stopping the old version...", :magenta
80
+ if container_name_available?(MRSK.config.service_with_version)
81
+ say "Start version #{version}, then wait #{MRSK.config.readiness_delay}s for app to boot before stopping the old version...", :magenta
59
82
 
60
- cli = self
83
+ cli = self
84
+ old_version = nil
61
85
 
62
- on(MRSK.hosts) do |host|
63
- old_version = capture_with_info(*MRSK.app.current_running_version).strip.presence
86
+ on(MRSK.hosts) do |host|
87
+ old_version = capture_with_info(*MRSK.app.current_running_version).strip.presence
64
88
 
65
- execute *MRSK.app.start
89
+ execute *MRSK.app.start
66
90
 
67
- sleep MRSK.config.readiness_delay
91
+ if old_version
92
+ sleep MRSK.config.readiness_delay
68
93
 
69
- execute *MRSK.app.stop(version: old_version), raise_on_non_zero_exit: false
70
- end
94
+ execute *MRSK.app.stop(version: old_version), raise_on_non_zero_exit: false
95
+ end
96
+ end
71
97
 
72
- audit_broadcast "Rolled back app to version #{version}" unless options[:skip_broadcast]
73
- else
74
- say "The app version '#{version}' is not available as a container (use 'mrsk app containers' for available versions)", :red
98
+ audit_broadcast "Rolled back #{service_version(Mrsk::Utils.abbreviate_version(old_version))} to #{service_version}" unless options[:skip_broadcast]
99
+ else
100
+ say "The app version '#{version}' is not available as a container (use 'mrsk app containers' for available versions)", :red
101
+ end
75
102
  end
76
103
  end
77
104
 
@@ -119,8 +146,10 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
119
146
  puts "Binstub already exists in bin/mrsk (remove first to create a new one)"
120
147
  else
121
148
  puts "Adding MRSK to Gemfile and bundle..."
122
- `bundle add mrsk`
123
- `bundle binstubs mrsk`
149
+ run_locally do
150
+ execute :bundle, :add, :mrsk
151
+ execute :bundle, :binstubs, :mrsk
152
+ end
124
153
  puts "Created binstub file in bin/mrsk"
125
154
  end
126
155
  end
@@ -142,11 +171,13 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
142
171
  desc "remove", "Remove Traefik, app, accessories, and registry session from servers"
143
172
  option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
144
173
  def remove
145
- if options[:confirmed] || ask("This will remove all containers and images. Are you sure?", limited_to: %w( y N ), default: "N") == "y"
146
- invoke "mrsk:cli:traefik:remove", [], options.without(:confirmed)
147
- invoke "mrsk:cli:app:remove", [], options.without(:confirmed)
148
- invoke "mrsk:cli:accessory:remove", [ "all" ], options
149
- invoke "mrsk:cli:registry:logout", [], options.without(:confirmed)
174
+ with_lock do
175
+ if options[:confirmed] || ask("This will remove all containers and images. Are you sure?", limited_to: %w( y N ), default: "N") == "y"
176
+ invoke "mrsk:cli:traefik:remove", [], options.without(:confirmed)
177
+ invoke "mrsk:cli:app:remove", [], options.without(:confirmed)
178
+ invoke "mrsk:cli:accessory:remove", [ "all" ], options
179
+ invoke "mrsk:cli:registry:logout", [], options.without(:confirmed)
180
+ end
150
181
  end
151
182
  end
152
183
 
@@ -179,10 +210,21 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
179
210
  desc "traefik", "Manage Traefik load balancer"
180
211
  subcommand "traefik", Mrsk::Cli::Traefik
181
212
 
213
+ desc "lock", "Manage the deploy lock"
214
+ subcommand "lock", Mrsk::Cli::Lock
215
+
182
216
  private
183
217
  def container_name_available?(container_name, host: MRSK.primary_host)
184
218
  container_names = nil
185
219
  on(host) { container_names = capture_with_info(*MRSK.app.list_container_names).split("\n") }
186
220
  Array(container_names).include?(container_name)
187
221
  end
222
+
223
+ def deploy_options
224
+ { "version" => MRSK.config.version }.merge(options.without("skip_push"))
225
+ end
226
+
227
+ def service_version(version = MRSK.config.abbreviated_version)
228
+ [ MRSK.config.service, version ].compact.join("@")
229
+ end
188
230
  end
@@ -1,23 +1,29 @@
1
1
  class Mrsk::Cli::Prune < Mrsk::Cli::Base
2
2
  desc "all", "Prune unused images and stopped containers"
3
3
  def all
4
- containers
5
- images
4
+ with_lock do
5
+ containers
6
+ images
7
+ end
6
8
  end
7
9
 
8
10
  desc "images", "Prune unused images older than 7 days"
9
11
  def images
10
- on(MRSK.hosts) do
11
- execute *MRSK.auditor.record("Pruned images"), verbosity: :debug
12
- execute *MRSK.prune.images
12
+ with_lock do
13
+ on(MRSK.hosts) do
14
+ execute *MRSK.auditor.record("Pruned images"), verbosity: :debug
15
+ execute *MRSK.prune.images
16
+ end
13
17
  end
14
18
  end
15
19
 
16
20
  desc "containers", "Prune stopped containers older than 3 days"
17
21
  def containers
18
- on(MRSK.hosts) do
19
- execute *MRSK.auditor.record("Pruned containers"), verbosity: :debug
20
- execute *MRSK.prune.containers
22
+ with_lock do
23
+ on(MRSK.hosts) do
24
+ execute *MRSK.auditor.record("Pruned containers"), verbosity: :debug
25
+ execute *MRSK.prune.containers
26
+ end
21
27
  end
22
28
  end
23
29
  end
@@ -1,14 +1,16 @@
1
1
  class Mrsk::Cli::Server < Mrsk::Cli::Base
2
2
  desc "bootstrap", "Ensure curl and Docker are installed on servers"
3
3
  def bootstrap
4
- on(MRSK.hosts + MRSK.accessory_hosts) do
5
- dependencies_to_install = Array.new.tap do |dependencies|
6
- dependencies << "curl" unless execute "which curl", raise_on_non_zero_exit: false
7
- dependencies << "docker.io" unless execute "which docker", raise_on_non_zero_exit: false
8
- end
4
+ with_lock do
5
+ on(MRSK.hosts + MRSK.accessory_hosts) do
6
+ dependencies_to_install = Array.new.tap do |dependencies|
7
+ dependencies << "curl" unless execute "which curl", raise_on_non_zero_exit: false
8
+ dependencies << "docker.io" unless execute "which docker", raise_on_non_zero_exit: false
9
+ end
9
10
 
10
- if dependencies_to_install.any?
11
- execute "apt-get update -y && apt-get install #{dependencies_to_install.join(" ")} -y"
11
+ if dependencies_to_install.any?
12
+ execute "apt-get update -y && apt-get install #{dependencies_to_install.join(" ")} -y"
13
+ end
12
14
  end
13
15
  end
14
16
  end
@@ -13,6 +13,8 @@ registry:
13
13
  # Specify the registry server, if you're not using Docker Hub
14
14
  # server: registry.digitalocean.com / ghcr.io / ...
15
15
  username: my-user
16
+
17
+ # Always use an access token rather than real password when possible.
16
18
  password:
17
19
  - MRSK_REGISTRY_PASSWORD
18
20
 
@@ -1,36 +1,46 @@
1
1
  class Mrsk::Cli::Traefik < Mrsk::Cli::Base
2
2
  desc "boot", "Boot Traefik on servers"
3
3
  def boot
4
- on(MRSK.traefik_hosts) { execute *MRSK.traefik.run, raise_on_non_zero_exit: false }
4
+ with_lock do
5
+ on(MRSK.traefik_hosts) { execute *MRSK.traefik.run, raise_on_non_zero_exit: false }
6
+ end
5
7
  end
6
8
 
7
9
  desc "reboot", "Reboot Traefik on servers (stop container, remove container, start new container)"
8
10
  def reboot
9
- stop
10
- remove_container
11
- boot
11
+ with_lock do
12
+ stop
13
+ remove_container
14
+ boot
15
+ end
12
16
  end
13
17
 
14
18
  desc "start", "Start existing Traefik container on servers"
15
19
  def start
16
- on(MRSK.traefik_hosts) do
17
- execute *MRSK.auditor.record("Started traefik"), verbosity: :debug
18
- execute *MRSK.traefik.start, raise_on_non_zero_exit: false
20
+ with_lock do
21
+ on(MRSK.traefik_hosts) do
22
+ execute *MRSK.auditor.record("Started traefik"), verbosity: :debug
23
+ execute *MRSK.traefik.start, raise_on_non_zero_exit: false
24
+ end
19
25
  end
20
26
  end
21
27
 
22
28
  desc "stop", "Stop existing Traefik container on servers"
23
29
  def stop
24
- on(MRSK.traefik_hosts) do
25
- execute *MRSK.auditor.record("Stopped traefik"), verbosity: :debug
26
- execute *MRSK.traefik.stop, raise_on_non_zero_exit: false
30
+ with_lock do
31
+ on(MRSK.traefik_hosts) do
32
+ execute *MRSK.auditor.record("Stopped traefik"), verbosity: :debug
33
+ execute *MRSK.traefik.stop, raise_on_non_zero_exit: false
34
+ end
27
35
  end
28
36
  end
29
37
 
30
38
  desc "restart", "Restart existing Traefik container on servers"
31
39
  def restart
32
- stop
33
- start
40
+ with_lock do
41
+ stop
42
+ start
43
+ end
34
44
  end
35
45
 
36
46
  desc "details", "Show details about Traefik container from servers"
@@ -64,24 +74,30 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
64
74
 
65
75
  desc "remove", "Remove Traefik container and image from servers"
66
76
  def remove
67
- stop
68
- remove_container
69
- remove_image
77
+ with_lock do
78
+ stop
79
+ remove_container
80
+ remove_image
81
+ end
70
82
  end
71
83
 
72
84
  desc "remove_container", "Remove Traefik container from servers", hide: true
73
85
  def remove_container
74
- on(MRSK.traefik_hosts) do
75
- execute *MRSK.auditor.record("Removed traefik container"), verbosity: :debug
76
- execute *MRSK.traefik.remove_container
86
+ with_lock do
87
+ on(MRSK.traefik_hosts) do
88
+ execute *MRSK.auditor.record("Removed traefik container"), verbosity: :debug
89
+ execute *MRSK.traefik.remove_container
90
+ end
77
91
  end
78
92
  end
79
93
 
80
94
  desc "remove_container", "Remove Traefik image from servers", hide: true
81
95
  def remove_image
82
- on(MRSK.traefik_hosts) do
83
- execute *MRSK.auditor.record("Removed traefik image"), verbosity: :debug
84
- execute *MRSK.traefik.remove_image
96
+ with_lock do
97
+ on(MRSK.traefik_hosts) do
98
+ execute *MRSK.auditor.record("Removed traefik image"), verbosity: :debug
99
+ execute *MRSK.traefik.remove_image
100
+ end
85
101
  end
86
102
  end
87
103
  end
@@ -1,35 +1,57 @@
1
1
  require "active_support/core_ext/enumerable"
2
+ require "active_support/core_ext/module/delegation"
2
3
 
3
4
  class Mrsk::Commander
4
- attr_accessor :config_file, :destination, :verbosity, :version
5
+ attr_accessor :verbosity, :lock_count
5
6
 
6
- def initialize(config_file: nil, destination: nil, verbosity: :info)
7
- @config_file, @destination, @verbosity = config_file, destination, verbosity
7
+ def initialize
8
+ self.verbosity = :info
9
+ self.lock_count = 0
8
10
  end
9
11
 
10
12
  def config
11
- @config ||= \
12
- Mrsk::Configuration
13
- .create_from(config_file, destination: destination, version: cascading_version)
14
- .tap { |config| configure_sshkit_with(config) }
13
+ @config ||= Mrsk::Configuration.create_from(**@config_kwargs).tap do |config|
14
+ @config_kwargs = nil
15
+ configure_sshkit_with(config)
16
+ end
17
+ end
18
+
19
+ def configure(**kwargs)
20
+ @config, @config_kwargs = nil, kwargs
15
21
  end
16
22
 
17
- attr_accessor :specific_hosts
23
+ attr_reader :specific_roles, :specific_hosts
18
24
 
19
25
  def specific_primary!
20
26
  self.specific_hosts = [ config.primary_web_host ]
21
27
  end
22
28
 
23
29
  def specific_roles=(role_names)
24
- self.specific_hosts = config.roles.select { |r| role_names.include?(r.name) }.flat_map(&:hosts) if role_names.present?
30
+ @specific_roles = config.roles.select { |r| role_names.include?(r.name) } if role_names.present?
31
+ end
32
+
33
+ def specific_hosts=(hosts)
34
+ @specific_hosts = config.all_hosts & hosts if hosts.present?
25
35
  end
26
36
 
27
37
  def primary_host
28
38
  specific_hosts&.first || config.primary_web_host
29
39
  end
30
40
 
41
+ def roles
42
+ (specific_roles || config.roles).select do |role|
43
+ ((specific_hosts || config.all_hosts) & role.hosts).any?
44
+ end
45
+ end
46
+
31
47
  def hosts
32
- specific_hosts || config.all_hosts
48
+ (specific_hosts || config.all_hosts).select do |host|
49
+ (specific_roles || config.roles).flat_map(&:hosts).include?(host)
50
+ end
51
+ end
52
+
53
+ def roles_on(host)
54
+ roles.select { |role| role.hosts.include?(host.to_s) }.map(&:name)
33
55
  end
34
56
 
35
57
  def traefik_hosts
@@ -37,7 +59,7 @@ class Mrsk::Commander
37
59
  end
38
60
 
39
61
  def accessory_hosts
40
- specific_hosts || config.accessories.collect(&:host)
62
+ specific_hosts || config.accessories.flat_map(&:hosts)
41
63
  end
42
64
 
43
65
  def accessory_names
@@ -45,16 +67,16 @@ class Mrsk::Commander
45
67
  end
46
68
 
47
69
 
48
- def app
49
- @app ||= Mrsk::Commands::App.new(config)
70
+ def app(role: nil)
71
+ Mrsk::Commands::App.new(config, role: role)
50
72
  end
51
73
 
52
74
  def accessory(name)
53
75
  Mrsk::Commands::Accessory.new(config, name: name)
54
76
  end
55
77
 
56
- def auditor
57
- @auditor ||= Mrsk::Commands::Auditor.new(config)
78
+ def auditor(role: nil)
79
+ Mrsk::Commands::Auditor.new(config, role: role)
58
80
  end
59
81
 
60
82
  def builder
@@ -77,6 +99,9 @@ class Mrsk::Commander
77
99
  @traefik ||= Mrsk::Commands::Traefik.new(config)
78
100
  end
79
101
 
102
+ def lock
103
+ @lock ||= Mrsk::Commands::Lock.new(config)
104
+ end
80
105
 
81
106
  def with_verbosity(level)
82
107
  old_level = self.verbosity
@@ -90,26 +115,7 @@ class Mrsk::Commander
90
115
  SSHKit.config.output_verbosity = old_level
91
116
  end
92
117
 
93
- # Test-induced damage!
94
- def reset
95
- @config = @config_file = @destination = @version = nil
96
- @app = @builder = @traefik = @registry = @prune = @auditor = nil
97
- @verbosity = :info
98
- end
99
-
100
118
  private
101
- def cascading_version
102
- version.presence || ENV["VERSION"] || current_commit_hash
103
- end
104
-
105
- def current_commit_hash
106
- if system("git rev-parse")
107
- `git rev-parse HEAD`.strip
108
- else
109
- raise "Can't use commit hash as version, no git repository found in #{Dir.pwd}"
110
- end
111
- end
112
-
113
119
  # Lazy setup of SSHKit
114
120
  def configure_sshkit_with(config)
115
121
  SSHKit::Backend::Netssh.configure { |ssh| ssh.ssh_options = config.ssh_options }
@@ -1,6 +1,7 @@
1
1
  class Mrsk::Commands::Accessory < Mrsk::Commands::Base
2
2
  attr_reader :accessory_config
3
- delegate :service_name, :image, :host, :port, :files, :directories, :env_args, :volume_args, :label_args, to: :accessory_config
3
+ delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd,
4
+ :publish_args, :env_args, :volume_args, :label_args, :option_args, to: :accessory_config
4
5
 
5
6
  def initialize(config, name:)
6
7
  super(config)
@@ -12,12 +13,14 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
12
13
  "--name", service_name,
13
14
  "--detach",
14
15
  "--restart", "unless-stopped",
15
- "--log-opt", "max-size=#{MAX_LOG_SIZE}",
16
- "--publish", port,
16
+ *config.logging_args,
17
+ *publish_args,
17
18
  *env_args,
18
19
  *volume_args,
19
20
  *label_args,
20
- image
21
+ *option_args,
22
+ image,
23
+ cmd
21
24
  end
22
25
 
23
26
  def start
@@ -73,7 +76,7 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
73
76
  end
74
77
 
75
78
  def run_over_ssh(command)
76
- super command, host: host
79
+ super command, host: hosts.first
77
80
  end
78
81
 
79
82
 
@@ -100,7 +103,7 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
100
103
  end
101
104
 
102
105
  def remove_image
103
- docker :image, :prune, "--all", "--force", *service_filter
106
+ docker :image, :rm, "--force", image
104
107
  end
105
108
 
106
109
  private