mrsk 0.9.1 → 0.10.1

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