mrsk 0.12.1 → 0.13.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.
- checksums.yaml +4 -4
- data/README.md +69 -42
- data/bin/mrsk +1 -1
- data/lib/mrsk/cli/accessory.rb +0 -2
- data/lib/mrsk/cli/app.rb +26 -24
- data/lib/mrsk/cli/base.rb +30 -15
- data/lib/mrsk/cli/build.rb +15 -17
- data/lib/mrsk/cli/lock.rb +4 -4
- data/lib/mrsk/cli/main.rb +33 -60
- data/lib/mrsk/cli/templates/deploy.yml +0 -4
- data/lib/mrsk/cli/templates/sample_hooks/post-deploy.sample +14 -0
- data/lib/mrsk/cli/templates/sample_hooks/pre-build.sample +51 -0
- data/lib/mrsk/cli/templates/sample_hooks/pre-connect.sample +47 -0
- data/lib/mrsk/cli.rb +1 -0
- data/lib/mrsk/commander.rb +8 -4
- data/lib/mrsk/commands/app.rb +6 -2
- data/lib/mrsk/commands/auditor.rb +3 -34
- data/lib/mrsk/commands/base.rb +6 -2
- data/lib/mrsk/commands/hook.rb +14 -0
- data/lib/mrsk/commands/traefik.rb +12 -1
- data/lib/mrsk/configuration/role.rb +8 -1
- data/lib/mrsk/configuration.rb +11 -9
- data/lib/mrsk/sshkit_with_ext.rb +4 -0
- data/lib/mrsk/tags.rb +39 -0
- data/lib/mrsk/utils.rb +8 -1
- data/lib/mrsk/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6b258a4c44d5b010d6ab4004d0e051f9378dd4511203020a8fed2dc639527d9f
|
4
|
+
data.tar.gz: 702b1c42fc0b095d18c37baa1811186ece2267b3d49067648129b76bef284be1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60163759e4097ea91df33411e7dc32261c0bc82f609bc1f54308f4111e3f0efe4d9731b234175e293f5c8a6aef9e1a88156be88260fc0a545b4d276ddb4e753b
|
7
|
+
data.tar.gz: 882d09991704e45f3c377dfafa0b15f2c6f1421085e5ca8096855bb03b109c6a91e4e47b7ec9270cdb31f7e43b3a18b23576fb0b2faa6367c82d9b3748550201
|
data/README.md
CHANGED
@@ -44,24 +44,24 @@ Then edit your `.env` file to add your registry password as `MRSK_REGISTRY_PASSW
|
|
44
44
|
Now you're ready to deploy to the servers:
|
45
45
|
|
46
46
|
```
|
47
|
-
mrsk
|
47
|
+
mrsk setup
|
48
48
|
```
|
49
49
|
|
50
50
|
This will:
|
51
51
|
|
52
52
|
1. Connect to the servers over SSH (using root by default, authenticated by your ssh key)
|
53
|
-
2. Install Docker on any server that might be missing it (using apt-get): root access is needed via ssh for this.
|
53
|
+
2. Install Docker and curl on any server that might be missing it (using apt-get): root access is needed via ssh for this.
|
54
54
|
3. Log into the registry both locally and remotely
|
55
55
|
4. Build the image using the standard Dockerfile in the root of the application.
|
56
56
|
5. Push the image to the registry.
|
57
57
|
6. Pull the image from the registry onto the servers.
|
58
58
|
7. Ensure Traefik is running and accepting traffic on port 80.
|
59
|
-
8. Ensure your app responds with `200 OK` to `GET /up
|
59
|
+
8. Ensure your app responds with `200 OK` to `GET /up` (you must have curl installed inside your app image!).
|
60
60
|
9. Start a new container with the version of the app that matches the current git version hash.
|
61
61
|
10. Stop the old container running the previous version of the app.
|
62
62
|
11. Prune unused images and stopped containers to ensure servers don't fill up.
|
63
63
|
|
64
|
-
Voila! All the servers are now serving the app on port 80. If you're just running a single server, you're ready to go. If you're running multiple servers, you need to put a load balancer in front of them.
|
64
|
+
Voila! All the servers are now serving the app on port 80. If you're just running a single server, you're ready to go. If you're running multiple servers, you need to put a load balancer in front of them. For subsequent deploys, or if your servers already have Docker and curl installed, you can just run `mrsk deploy`.
|
65
65
|
|
66
66
|
## Vision
|
67
67
|
|
@@ -184,6 +184,19 @@ registry:
|
|
184
184
|
|
185
185
|
A reference to secret `DOCKER_REGISTRY_TOKEN` will look for `ENV["DOCKER_REGISTRY_TOKEN"]` on the machine running MRSK.
|
186
186
|
|
187
|
+
#### Using AWS ECR as the container registry
|
188
|
+
|
189
|
+
AWS ECR's access token is only valid for 12hrs. In order to not have to manually regenerate the token every time, you can use ERB in the `deploy.yml` file to shell out to the `aws` cli command, and obtain the token:
|
190
|
+
|
191
|
+
```yaml
|
192
|
+
registry:
|
193
|
+
server: <your aws account id>.dkr.ecr.<your aws region id>.amazonaws.com
|
194
|
+
username: AWS
|
195
|
+
password: <%= %x(aws ecr get-login-password) %>
|
196
|
+
```
|
197
|
+
|
198
|
+
You will need to have the `aws` CLI installed locally for this to work.
|
199
|
+
|
187
200
|
### Using a different SSH user than root
|
188
201
|
|
189
202
|
The default SSH user is root, but you can change it using `ssh/user`:
|
@@ -522,7 +535,7 @@ traefik:
|
|
522
535
|
options:
|
523
536
|
publish:
|
524
537
|
- 8080:8080
|
525
|
-
|
538
|
+
volume:
|
526
539
|
- /tmp/example.json:/tmp/example.json
|
527
540
|
memory: 512m
|
528
541
|
```
|
@@ -655,43 +668,6 @@ servers:
|
|
655
668
|
|
656
669
|
This assumes the Cron settings are stored in `config/crontab`.
|
657
670
|
|
658
|
-
### Using audit broadcasts
|
659
|
-
|
660
|
-
If you'd like to broadcast audits of deploys, rollbacks, etc to a chatroom or elsewhere, you can configure the `audit_broadcast_cmd` setting with the path to a bin file that will be passed the audit line as the first argument:
|
661
|
-
|
662
|
-
```yaml
|
663
|
-
audit_broadcast_cmd:
|
664
|
-
bin/audit_broadcast
|
665
|
-
```
|
666
|
-
|
667
|
-
The broadcast command could look something like:
|
668
|
-
|
669
|
-
```bash
|
670
|
-
#!/usr/bin/env bash
|
671
|
-
curl -q -d content="[My App] ${1}" https://3.basecamp.com/XXXXX/integrations/XXXXX/buckets/XXXXX/chats/XXXXX/lines
|
672
|
-
```
|
673
|
-
|
674
|
-
That'll post a line like follows to a preconfigured chatbot in Basecamp:
|
675
|
-
|
676
|
-
```
|
677
|
-
[My App] [dhh] Rolled back to version d264c4e92470ad1bd18590f04466787262f605de
|
678
|
-
```
|
679
|
-
|
680
|
-
`MRSK_*` environment variables are available to the broadcast command for
|
681
|
-
fine-grained audit reporting, e.g. for triggering deployment reports or
|
682
|
-
firing a JSON webhook. These variables include:
|
683
|
-
- `MRSK_RECORDED_AT` - UTC timestamp in ISO 8601 format, e.g. `2023-04-14T17:07:31Z`
|
684
|
-
- `MRSK_PERFORMER` - the local user performing the command (from `whoami`)
|
685
|
-
- `MRSK_MESSAGE` - the full audit message, e.g. "Deployed app@150b24f"
|
686
|
-
- `MRSK_DESTINATION` - optional: destination, e.g. "staging"
|
687
|
-
- `MRSK_ROLE` - optional: role targeted, e.g. "web"
|
688
|
-
|
689
|
-
Use `mrsk broadcast` to test and troubleshoot your broadcast command:
|
690
|
-
|
691
|
-
```bash
|
692
|
-
mrsk broadcast -m "test audit message"
|
693
|
-
```
|
694
|
-
|
695
671
|
### Healthcheck
|
696
672
|
|
697
673
|
MRSK uses Docker healtchecks to check the health of your application during deployment. Traefik uses this same healthcheck status to determine when a container is ready to receive traffic.
|
@@ -703,6 +679,7 @@ healthcheck:
|
|
703
679
|
path: /healthz
|
704
680
|
port: 4000
|
705
681
|
max_attempts: 7
|
682
|
+
interval: 20s
|
706
683
|
```
|
707
684
|
|
708
685
|
This will ensure your application is configured with a traefik label for the healthcheck against `/healthz` and that the pre-deploy healthcheck that MRSK performs is done against the same path on port 4000.
|
@@ -888,6 +865,56 @@ When `limit` is specified, containers will be booted on, at most, `limit` hosts
|
|
888
865
|
|
889
866
|
These settings only apply when booting containers (using `mrsk deploy`, or `mrsk app boot`). For other commands, MRSK continues to run commands in parallel across all hosts.
|
890
867
|
|
868
|
+
## Hooks
|
869
|
+
|
870
|
+
You can run custom scripts at specific points with hooks.
|
871
|
+
|
872
|
+
Hooks should be stored in the .mrsk/hooks folder. Running mrsk init will build that folder and add some sample scripts.
|
873
|
+
|
874
|
+
You can change their location by setting `hooks_path` in the configuration file.
|
875
|
+
|
876
|
+
If the script returns a non-zero exit code the command will be aborted.
|
877
|
+
|
878
|
+
`MRSK_*` environment variables are available to the hooks command for
|
879
|
+
fine-grained audit reporting, e.g. for triggering deployment reports or
|
880
|
+
firing a JSON webhook. These variables include:
|
881
|
+
- `MRSK_RECORDED_AT` - UTC timestamp in ISO 8601 format, e.g. `2023-04-14T17:07:31Z`
|
882
|
+
- `MRSK_PERFORMER` - the local user performing the command (from `whoami`)
|
883
|
+
- `MRSK_SERVICE_VERSION` - an abbreviated service and version for use in messages, e.g. app@150b24f
|
884
|
+
- `MRSK_VERSION` - an full version being deployed
|
885
|
+
- `MRSK_DESTINATION` - optional: destination, e.g. "staging"
|
886
|
+
- `MRSK_HOSTS` - a comma separated list of the hosts targeted by the command
|
887
|
+
- `MRSK_ROLE` - optional: role targeted, e.g. "web"
|
888
|
+
|
889
|
+
There are three hooks:
|
890
|
+
|
891
|
+
1. pre-connect
|
892
|
+
Called before taking the deploy lock. For checks that need to run before connecting to remote hosts - e.g. DNS warming.
|
893
|
+
|
894
|
+
2. pre-build
|
895
|
+
Used for pre-build checks - e.g. there are no uncommitted changes or that CI has passed.
|
896
|
+
|
897
|
+
3. post-deploy - run after a deploy, redeploy or rollback
|
898
|
+
|
899
|
+
This hook is also passed a `MRSK_RUNTIME` env variable.
|
900
|
+
|
901
|
+
This could be used to broadcast a deployment message, or register the new version with an APM.
|
902
|
+
|
903
|
+
The command could look something like:
|
904
|
+
|
905
|
+
```bash
|
906
|
+
#!/usr/bin/env bash
|
907
|
+
curl -q -d content="[My App] ${MRSK_PERFORMER} Rolled back to version ${MRSK_VERSION}" https://3.basecamp.com/XXXXX/integrations/XXXXX/buckets/XXXXX/chats/XXXXX/lines
|
908
|
+
```
|
909
|
+
|
910
|
+
That'll post a line like follows to a preconfigured chatbot in Basecamp:
|
911
|
+
|
912
|
+
```
|
913
|
+
[My App] [dhh] Rolled back to version d264c4e92470ad1bd18590f04466787262f605de
|
914
|
+
```
|
915
|
+
|
916
|
+
Set `--skip_hooks` to avoid running the hooks.
|
917
|
+
|
891
918
|
## Stage of development
|
892
919
|
|
893
920
|
This is beta software. Commands may still move around. But we're live in production at [37signals](https://37signals.com).
|
data/bin/mrsk
CHANGED
@@ -8,7 +8,7 @@ require "mrsk"
|
|
8
8
|
begin
|
9
9
|
Mrsk::Cli::Main.start(ARGV)
|
10
10
|
rescue SSHKit::Runner::ExecuteError => e
|
11
|
-
puts " \e[31mERROR (#{e.cause.class}): #{e.
|
11
|
+
puts " \e[31mERROR (#{e.cause.class}): #{e.message}\e[0m"
|
12
12
|
puts e.cause.backtrace if ENV["VERBOSE"]
|
13
13
|
exit 1
|
14
14
|
rescue => e
|
data/lib/mrsk/cli/accessory.rb
CHANGED
data/lib/mrsk/cli/app.rb
CHANGED
@@ -2,37 +2,39 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
2
2
|
desc "boot", "Boot app on servers (or reboot app if already running)"
|
3
3
|
def boot
|
4
4
|
with_lock do
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
hold_lock_on_error do
|
6
|
+
say "Get most recent version available as an image...", :magenta unless options[:version]
|
7
|
+
using_version(version_or_latest) do |version|
|
8
|
+
say "Start container with version #{version} using a #{MRSK.config.readiness_delay}s readiness delay (or reboot if already running)...", :magenta
|
9
|
+
|
10
|
+
on(MRSK.hosts) do
|
11
|
+
execute *MRSK.auditor.record("Tagging #{MRSK.config.absolute_image} as the latest image"), verbosity: :debug
|
12
|
+
execute *MRSK.app.tag_current_as_latest
|
13
|
+
end
|
8
14
|
|
9
|
-
|
10
|
-
|
11
|
-
execute *MRSK.app.tag_current_as_latest
|
12
|
-
end
|
15
|
+
on(MRSK.hosts, **MRSK.boot_strategy) do |host|
|
16
|
+
roles = MRSK.roles_on(host)
|
13
17
|
|
14
|
-
|
15
|
-
|
18
|
+
roles.each do |role|
|
19
|
+
app = MRSK.app(role: role)
|
20
|
+
auditor = MRSK.auditor(role: role)
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
if capture_with_info(*app.container_id_for_version(version, only_running: true), raise_on_non_zero_exit: false).present?
|
23
|
+
tmp_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
|
24
|
+
info "Renaming container #{version} to #{tmp_version} as already deployed on #{host}"
|
25
|
+
execute *auditor.record("Renaming container #{version} to #{tmp_version}"), verbosity: :debug
|
26
|
+
execute *app.rename_container(version: version, new_version: tmp_version)
|
27
|
+
end
|
22
28
|
|
23
|
-
|
24
|
-
tmp_version = "#{version}_#{SecureRandom.hex(8)}"
|
25
|
-
info "Renaming container #{version} to #{tmp_version} as already deployed on #{host}"
|
26
|
-
execute *auditor.record("Renaming container #{version} to #{tmp_version}"), verbosity: :debug
|
27
|
-
execute *app.rename_container(version: version, new_version: tmp_version)
|
28
|
-
end
|
29
|
+
execute *auditor.record("Booted app version #{version}"), verbosity: :debug
|
29
30
|
|
30
|
-
|
31
|
-
|
31
|
+
old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
|
32
|
+
execute *app.start_or_run
|
32
33
|
|
33
|
-
|
34
|
+
Mrsk::Utils::HealthcheckPoller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
|
34
35
|
|
35
|
-
|
36
|
+
execute *app.stop(version: old_version), raise_on_non_zero_exit: false if old_version.present?
|
37
|
+
end
|
36
38
|
end
|
37
39
|
end
|
38
40
|
end
|
data/lib/mrsk/cli/base.rb
CHANGED
@@ -20,7 +20,7 @@ module Mrsk::Cli
|
|
20
20
|
class_option :config_file, aliases: "-c", default: "config/deploy.yml", desc: "Path to config file"
|
21
21
|
class_option :destination, aliases: "-d", desc: "Specify destination to be used for config file (staging -> deploy.staging.yml)"
|
22
22
|
|
23
|
-
class_option :
|
23
|
+
class_option :skip_hooks, aliases: "-H", type: :boolean, default: false, desc: "Don't run hooks"
|
24
24
|
|
25
25
|
def initialize(*)
|
26
26
|
super
|
@@ -72,14 +72,12 @@ module Mrsk::Cli
|
|
72
72
|
puts " Finished all in #{sprintf("%.1f seconds", runtime)}"
|
73
73
|
end
|
74
74
|
|
75
|
-
def audit_broadcast(line)
|
76
|
-
run_locally { execute *MRSK.auditor.broadcast(line), verbosity: :debug }
|
77
|
-
end
|
78
|
-
|
79
75
|
def with_lock
|
80
76
|
if MRSK.holding_lock?
|
81
77
|
yield
|
82
78
|
else
|
79
|
+
run_hook "pre-connect"
|
80
|
+
|
83
81
|
acquire_lock
|
84
82
|
|
85
83
|
begin
|
@@ -99,26 +97,32 @@ module Mrsk::Cli
|
|
99
97
|
end
|
100
98
|
|
101
99
|
def acquire_lock
|
102
|
-
|
103
|
-
|
100
|
+
raise_if_locked do
|
101
|
+
say "Acquiring the deploy lock...", :magenta
|
102
|
+
on(MRSK.primary_host) { execute *MRSK.lock.acquire("Automatic deploy lock", MRSK.config.version), verbosity: :debug }
|
103
|
+
end
|
104
104
|
|
105
105
|
MRSK.holding_lock = true
|
106
|
+
end
|
107
|
+
|
108
|
+
def release_lock
|
109
|
+
say "Releasing the deploy lock...", :magenta
|
110
|
+
on(MRSK.primary_host) { execute *MRSK.lock.release, verbosity: :debug }
|
111
|
+
|
112
|
+
MRSK.holding_lock = false
|
113
|
+
end
|
114
|
+
|
115
|
+
def raise_if_locked
|
116
|
+
yield
|
106
117
|
rescue SSHKit::Runner::ExecuteError => e
|
107
118
|
if e.message =~ /cannot create directory/
|
108
|
-
on(MRSK.primary_host) {
|
119
|
+
on(MRSK.primary_host) { puts capture_with_debug(*MRSK.lock.status) }
|
109
120
|
raise LockError, "Deploy lock found"
|
110
121
|
else
|
111
122
|
raise e
|
112
123
|
end
|
113
124
|
end
|
114
125
|
|
115
|
-
def release_lock
|
116
|
-
say "Releasing the deploy lock"
|
117
|
-
on(MRSK.primary_host) { execute *MRSK.lock.release }
|
118
|
-
|
119
|
-
MRSK.holding_lock = false
|
120
|
-
end
|
121
|
-
|
122
126
|
def hold_lock_on_error
|
123
127
|
if MRSK.hold_lock_on_error?
|
124
128
|
yield
|
@@ -128,5 +132,16 @@ module Mrsk::Cli
|
|
128
132
|
MRSK.hold_lock_on_error = false
|
129
133
|
end
|
130
134
|
end
|
135
|
+
|
136
|
+
def run_hook(hook, **details)
|
137
|
+
if !options[:skip_hooks] && MRSK.hook.hook_exists?(hook)
|
138
|
+
say "Running the #{hook} hook...", :magenta
|
139
|
+
run_locally do
|
140
|
+
MRSK.with_verbosity(:debug) { execute *MRSK.hook.run(hook, **details, hosts: MRSK.hosts.join(",")) }
|
141
|
+
rescue SSHKit::Command::Failed
|
142
|
+
raise HookError.new("Hook `#{hook}` failed")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
131
146
|
end
|
132
147
|
end
|
data/lib/mrsk/cli/build.rb
CHANGED
@@ -14,11 +14,12 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
|
|
14
14
|
with_lock do
|
15
15
|
cli = self
|
16
16
|
|
17
|
+
verify_local_dependencies
|
18
|
+
run_hook "pre-build"
|
19
|
+
|
17
20
|
run_locally do
|
18
21
|
begin
|
19
|
-
|
20
|
-
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
|
21
|
-
end
|
22
|
+
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
|
22
23
|
rescue SSHKit::Command::Failed => e
|
23
24
|
if e.message =~ /(no builder)|(no such file or directory)/
|
24
25
|
error "Missing compatible builder, so creating a new one first"
|
@@ -82,21 +83,18 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
|
|
82
83
|
end
|
83
84
|
end
|
84
85
|
|
86
|
+
private
|
87
|
+
def verify_local_dependencies
|
88
|
+
run_locally do
|
89
|
+
begin
|
90
|
+
execute *MRSK.builder.ensure_local_dependencies_installed
|
91
|
+
rescue SSHKit::Command::Failed => e
|
92
|
+
build_error = e.message =~ /command not found/ ?
|
93
|
+
"Docker is not installed locally" :
|
94
|
+
"Docker buildx plugin is not installed locally"
|
85
95
|
|
86
|
-
|
87
|
-
|
88
|
-
run_locally do
|
89
|
-
begin
|
90
|
-
execute *MRSK.builder.ensure_local_dependencies_installed
|
91
|
-
rescue SSHKit::Command::Failed => e
|
92
|
-
build_error = e.message =~ /command not found/ ?
|
93
|
-
"Docker is not installed locally" :
|
94
|
-
"Docker buildx plugin is not installed locally"
|
95
|
-
|
96
|
-
raise BuildError, build_error
|
96
|
+
raise BuildError, build_error
|
97
|
+
end
|
97
98
|
end
|
98
99
|
end
|
99
|
-
|
100
|
-
true
|
101
|
-
end
|
102
100
|
end
|
data/lib/mrsk/cli/lock.rb
CHANGED
@@ -2,7 +2,7 @@ class Mrsk::Cli::Lock < Mrsk::Cli::Base
|
|
2
2
|
desc "status", "Report lock status"
|
3
3
|
def status
|
4
4
|
handle_missing_lock do
|
5
|
-
on(MRSK.primary_host) { puts
|
5
|
+
on(MRSK.primary_host) { puts capture_with_debug(*MRSK.lock.status) }
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
@@ -10,8 +10,8 @@ class Mrsk::Cli::Lock < Mrsk::Cli::Base
|
|
10
10
|
option :message, aliases: "-m", type: :string, desc: "A lock mesasge", required: true
|
11
11
|
def acquire
|
12
12
|
message = options[:message]
|
13
|
-
|
14
|
-
on(MRSK.primary_host) { execute *MRSK.lock.acquire(message, MRSK.config.version) }
|
13
|
+
raise_if_locked do
|
14
|
+
on(MRSK.primary_host) { execute *MRSK.lock.acquire(message, MRSK.config.version), verbosity: :debug }
|
15
15
|
say "Acquired the deploy lock"
|
16
16
|
end
|
17
17
|
end
|
@@ -19,7 +19,7 @@ class Mrsk::Cli::Lock < Mrsk::Cli::Base
|
|
19
19
|
desc "release", "Release the deploy lock"
|
20
20
|
def release
|
21
21
|
handle_missing_lock do
|
22
|
-
on(MRSK.primary_host) { execute *MRSK.lock.release }
|
22
|
+
on(MRSK.primary_host) { execute *MRSK.lock.release, verbosity: :debug }
|
23
23
|
say "Released the deploy lock"
|
24
24
|
end
|
25
25
|
end
|
data/lib/mrsk/cli/main.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
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
|
-
|
5
|
-
|
4
|
+
print_runtime do
|
5
|
+
with_lock do
|
6
6
|
invoke "mrsk:cli:server:bootstrap"
|
7
7
|
invoke "mrsk:cli:accessory:boot", [ "all" ]
|
8
8
|
deploy
|
@@ -13,10 +13,10 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|
13
13
|
desc "deploy", "Deploy app to servers"
|
14
14
|
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
15
15
|
def deploy
|
16
|
-
|
17
|
-
|
16
|
+
runtime = print_runtime do
|
17
|
+
with_lock do
|
18
|
+
invoke_options = deploy_options
|
18
19
|
|
19
|
-
runtime = print_runtime do
|
20
20
|
say "Log into image registry...", :magenta
|
21
21
|
invoke "mrsk:cli:registry:login", [], invoke_options
|
22
22
|
|
@@ -37,25 +37,23 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|
37
37
|
say "Detect stale containers...", :magenta
|
38
38
|
invoke "mrsk:cli:app:stale_containers", [], invoke_options
|
39
39
|
|
40
|
-
|
41
|
-
invoke "mrsk:cli:app:boot", [], invoke_options
|
42
|
-
end
|
40
|
+
invoke "mrsk:cli:app:boot", [], invoke_options
|
43
41
|
|
44
42
|
say "Prune old containers and images...", :magenta
|
45
43
|
invoke "mrsk:cli:prune:all", [], invoke_options
|
46
44
|
end
|
47
|
-
|
48
|
-
audit_broadcast "Deployed #{service_version} in #{runtime.round} seconds" unless options[:skip_broadcast]
|
49
45
|
end
|
46
|
+
|
47
|
+
run_hook "post-deploy", runtime: runtime.round
|
50
48
|
end
|
51
49
|
|
52
50
|
desc "redeploy", "Deploy app to servers without bootstrapping servers, starting Traefik, pruning, and registry login"
|
53
51
|
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
54
52
|
def redeploy
|
55
|
-
|
56
|
-
|
53
|
+
runtime = print_runtime do
|
54
|
+
with_lock do
|
55
|
+
invoke_options = deploy_options
|
57
56
|
|
58
|
-
runtime = print_runtime do
|
59
57
|
if options[:skip_push]
|
60
58
|
say "Pull app image...", :magenta
|
61
59
|
invoke "mrsk:cli:build:pull", [], invoke_options
|
@@ -70,55 +68,33 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|
70
68
|
say "Detect stale containers...", :magenta
|
71
69
|
invoke "mrsk:cli:app:stale_containers", [], invoke_options
|
72
70
|
|
73
|
-
|
74
|
-
invoke "mrsk:cli:app:boot", [], invoke_options
|
75
|
-
end
|
71
|
+
invoke "mrsk:cli:app:boot", [], invoke_options
|
76
72
|
end
|
77
|
-
|
78
|
-
audit_broadcast "Redeployed #{service_version} in #{runtime.round} seconds" unless options[:skip_broadcast]
|
79
73
|
end
|
74
|
+
|
75
|
+
run_hook "post-deploy", runtime: runtime.round
|
80
76
|
end
|
81
77
|
|
82
78
|
desc "rollback [VERSION]", "Rollback app to VERSION"
|
83
79
|
def rollback(version)
|
84
|
-
|
85
|
-
|
80
|
+
rolled_back = false
|
81
|
+
runtime = print_runtime do
|
82
|
+
with_lock do
|
83
|
+
invoke_options = deploy_options
|
86
84
|
|
87
|
-
hold_lock_on_error do
|
88
85
|
MRSK.config.version = version
|
89
86
|
old_version = nil
|
90
87
|
|
91
88
|
if container_available?(version)
|
92
|
-
|
93
|
-
|
94
|
-
on(MRSK.hosts) do
|
95
|
-
execute *MRSK.auditor.record("Tagging #{MRSK.config.absolute_image} as the latest image"), verbosity: :debug
|
96
|
-
execute *MRSK.app.tag_current_as_latest
|
97
|
-
end
|
98
|
-
|
99
|
-
on(MRSK.hosts) do |host|
|
100
|
-
roles = MRSK.roles_on(host)
|
101
|
-
|
102
|
-
roles.each do |role|
|
103
|
-
app = MRSK.app(role: role)
|
104
|
-
old_version = capture_with_info(*app.current_running_version).strip.presence
|
105
|
-
|
106
|
-
execute *app.start
|
107
|
-
|
108
|
-
if old_version
|
109
|
-
sleep MRSK.config.readiness_delay
|
110
|
-
|
111
|
-
execute *app.stop(version: old_version), raise_on_non_zero_exit: false
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
audit_broadcast "Rolled back #{service_version(Mrsk::Utils.abbreviate_version(old_version))} to #{service_version}" unless options[:skip_broadcast]
|
89
|
+
invoke "mrsk:cli:app:boot", [], invoke_options.merge(version: version)
|
90
|
+
rolled_back = true
|
117
91
|
else
|
118
92
|
say "The app version '#{version}' is not available as a container (use 'mrsk app containers' for available versions)", :red
|
119
93
|
end
|
120
94
|
end
|
121
95
|
end
|
96
|
+
|
97
|
+
run_hook "post-deploy", runtime: runtime.round if rolled_back
|
122
98
|
end
|
123
99
|
|
124
100
|
desc "details", "Show details about all containers"
|
@@ -160,6 +136,14 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|
160
136
|
puts "Created .env file"
|
161
137
|
end
|
162
138
|
|
139
|
+
unless (hooks_dir = Pathname.new(File.expand_path(".mrsk/hooks"))).exist?
|
140
|
+
hooks_dir.mkpath
|
141
|
+
Pathname.new(File.expand_path("templates/sample_hooks", __dir__)).each_child do |sample_hook|
|
142
|
+
FileUtils.cp sample_hook, hooks_dir, preserve: true
|
143
|
+
end
|
144
|
+
puts "Created sample hooks in .mrsk/hooks"
|
145
|
+
end
|
146
|
+
|
163
147
|
if options[:bundle]
|
164
148
|
if (binstub = Pathname.new(File.expand_path("bin/mrsk"))).exist?
|
165
149
|
puts "Binstub already exists in bin/mrsk (remove first to create a new one)"
|
@@ -200,13 +184,6 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|
200
184
|
end
|
201
185
|
end
|
202
186
|
|
203
|
-
desc "broadcast", "Broadcast an audit message"
|
204
|
-
option :message, aliases: "-m", type: :string, desc: "Audit mesasge", required: true
|
205
|
-
def broadcast
|
206
|
-
say "Broadcast: #{options[:message]}", :magenta
|
207
|
-
audit_broadcast options[:message]
|
208
|
-
end
|
209
|
-
|
210
187
|
desc "version", "Show MRSK version"
|
211
188
|
def version
|
212
189
|
puts Mrsk::VERSION
|
@@ -224,6 +201,9 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|
224
201
|
desc "healthcheck", "Healthcheck application"
|
225
202
|
subcommand "healthcheck", Mrsk::Cli::Healthcheck
|
226
203
|
|
204
|
+
desc "lock", "Manage the deploy lock"
|
205
|
+
subcommand "lock", Mrsk::Cli::Lock
|
206
|
+
|
227
207
|
desc "prune", "Prune old application images and containers"
|
228
208
|
subcommand "prune", Mrsk::Cli::Prune
|
229
209
|
|
@@ -236,9 +216,6 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|
236
216
|
desc "traefik", "Manage Traefik load balancer"
|
237
217
|
subcommand "traefik", Mrsk::Cli::Traefik
|
238
218
|
|
239
|
-
desc "lock", "Manage the deploy lock"
|
240
|
-
subcommand "lock", Mrsk::Cli::Lock
|
241
|
-
|
242
219
|
private
|
243
220
|
def container_available?(version)
|
244
221
|
begin
|
@@ -263,8 +240,4 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|
263
240
|
def deploy_options
|
264
241
|
{ "version" => MRSK.config.version }.merge(options.without("skip_push"))
|
265
242
|
end
|
266
|
-
|
267
|
-
def service_version(version = MRSK.config.abbreviated_version)
|
268
|
-
[ MRSK.config.service, version ].compact.join("@")
|
269
|
-
end
|
270
243
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# A sample post-deploy hook
|
4
|
+
#
|
5
|
+
# These environment variables are available:
|
6
|
+
# MRSK_RECORDED_AT
|
7
|
+
# MRSK_PERFORMER
|
8
|
+
# MRSK_VERSION
|
9
|
+
# MRSK_HOSTS
|
10
|
+
# MRSK_ROLE (if set)
|
11
|
+
# MRSK_DESTINATION (if set)
|
12
|
+
# MRSK_RUNTIME
|
13
|
+
|
14
|
+
echo "$MRSK_PERFORMER deployed $MRSK_VERSION to $MRSK_DESTINATION in $MRSK_RUNTIME seconds"
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# A sample pre-build hook
|
4
|
+
#
|
5
|
+
# Checks:
|
6
|
+
# 1. We have a clean checkout
|
7
|
+
# 2. A remote is configured
|
8
|
+
# 3. The branch has been pushed to the remote
|
9
|
+
# 4. The version we are deploying matches the remote
|
10
|
+
#
|
11
|
+
# These environment variables are available:
|
12
|
+
# MRSK_RECORDED_AT
|
13
|
+
# MRSK_PERFORMER
|
14
|
+
# MRSK_VERSION
|
15
|
+
# MRSK_HOSTS
|
16
|
+
# MRSK_ROLE (if set)
|
17
|
+
# MRSK_DESTINATION (if set)
|
18
|
+
|
19
|
+
if [ -n "$(git status --porcelain)" ]; then
|
20
|
+
echo "Git checkout is not clean, aborting..." >&2
|
21
|
+
git status --porcelain >&2
|
22
|
+
exit 1
|
23
|
+
fi
|
24
|
+
|
25
|
+
first_remote=$(git remote)
|
26
|
+
|
27
|
+
if [ -z "$first_remote" ]; then
|
28
|
+
echo "No git remote set, aborting..." >&2
|
29
|
+
exit 1
|
30
|
+
fi
|
31
|
+
|
32
|
+
current_branch=$(git branch --show-current)
|
33
|
+
|
34
|
+
if [ -z "$current_branch" ]; then
|
35
|
+
echo "No git remote set, aborting..." >&2
|
36
|
+
exit 1
|
37
|
+
fi
|
38
|
+
|
39
|
+
remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1)
|
40
|
+
|
41
|
+
if [ -z "$remote_head" ]; then
|
42
|
+
echo "Branch not pushed to remote, aborting..." >&2
|
43
|
+
exit 1
|
44
|
+
fi
|
45
|
+
|
46
|
+
if [ "$MRSK_VERSION" != "$remote_head" ]; then
|
47
|
+
echo "Version ($MRSK_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2
|
48
|
+
exit 1
|
49
|
+
fi
|
50
|
+
|
51
|
+
exit 0
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# A sample pre-connect check
|
4
|
+
#
|
5
|
+
# Warms DNS before connecting to hosts in parallel
|
6
|
+
#
|
7
|
+
# These environment variables are available:
|
8
|
+
# MRSK_RECORDED_AT
|
9
|
+
# MRSK_PERFORMER
|
10
|
+
# MRSK_VERSION
|
11
|
+
# MRSK_HOSTS
|
12
|
+
# MRSK_ROLE (if set)
|
13
|
+
# MRSK_DESTINATION (if set)
|
14
|
+
# MRSK_RUNTIME
|
15
|
+
|
16
|
+
hosts = ENV["MRSK_HOSTS"].split(",")
|
17
|
+
results = nil
|
18
|
+
max = 3
|
19
|
+
|
20
|
+
elapsed = Benchmark.realtime do
|
21
|
+
results = hosts.map do |host|
|
22
|
+
Thread.new do
|
23
|
+
tries = 1
|
24
|
+
|
25
|
+
begin
|
26
|
+
Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME)
|
27
|
+
rescue SocketError
|
28
|
+
if tries < max
|
29
|
+
puts "Retrying DNS warmup: #{host}"
|
30
|
+
tries += 1
|
31
|
+
sleep rand
|
32
|
+
retry
|
33
|
+
else
|
34
|
+
puts "DNS warmup failed: #{host}"
|
35
|
+
host
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
tries
|
40
|
+
end
|
41
|
+
end.map(&:value)
|
42
|
+
end
|
43
|
+
|
44
|
+
retries = results.sum - hosts.size
|
45
|
+
nopes = results.count { |r| r == max }
|
46
|
+
|
47
|
+
puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ]
|
data/lib/mrsk/cli.rb
CHANGED
data/lib/mrsk/commander.rb
CHANGED
@@ -100,6 +100,14 @@ class Mrsk::Commander
|
|
100
100
|
@healthcheck ||= Mrsk::Commands::Healthcheck.new(config)
|
101
101
|
end
|
102
102
|
|
103
|
+
def hook
|
104
|
+
@hook ||= Mrsk::Commands::Hook.new(config)
|
105
|
+
end
|
106
|
+
|
107
|
+
def lock
|
108
|
+
@lock ||= Mrsk::Commands::Lock.new(config)
|
109
|
+
end
|
110
|
+
|
103
111
|
def prune
|
104
112
|
@prune ||= Mrsk::Commands::Prune.new(config)
|
105
113
|
end
|
@@ -112,10 +120,6 @@ class Mrsk::Commander
|
|
112
120
|
@traefik ||= Mrsk::Commands::Traefik.new(config)
|
113
121
|
end
|
114
122
|
|
115
|
-
def lock
|
116
|
-
@lock ||= Mrsk::Commands::Lock.new(config)
|
117
|
-
end
|
118
|
-
|
119
123
|
def with_verbosity(level)
|
120
124
|
old_level = self.verbosity
|
121
125
|
|
data/lib/mrsk/commands/app.rb
CHANGED
@@ -6,6 +6,10 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
6
6
|
@role = role
|
7
7
|
end
|
8
8
|
|
9
|
+
def start_or_run
|
10
|
+
combine start, run, by: "||"
|
11
|
+
end
|
12
|
+
|
9
13
|
def run
|
10
14
|
role = config.role(self.role)
|
11
15
|
|
@@ -91,8 +95,8 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
91
95
|
docker :ps, "--quiet", *filter_args(status: :running), "--latest"
|
92
96
|
end
|
93
97
|
|
94
|
-
def container_id_for_version(version)
|
95
|
-
container_id_for(container_name: container_name(version))
|
98
|
+
def container_id_for_version(version, only_running: false)
|
99
|
+
container_id_for(container_name: container_name(version), only_running: only_running)
|
96
100
|
end
|
97
101
|
|
98
102
|
def current_running_version
|
@@ -1,27 +1,18 @@
|
|
1
|
-
require "time"
|
2
|
-
|
3
1
|
class Mrsk::Commands::Auditor < Mrsk::Commands::Base
|
4
2
|
attr_reader :details
|
5
3
|
|
6
4
|
def initialize(config, **details)
|
7
5
|
super(config)
|
8
|
-
@details =
|
6
|
+
@details = details
|
9
7
|
end
|
10
8
|
|
11
9
|
# Runs remotely
|
12
10
|
def record(line, **details)
|
13
11
|
append \
|
14
|
-
[ :echo,
|
12
|
+
[ :echo, audit_tags(**details).except(:version, :service_version).to_s, line ],
|
15
13
|
audit_log_file
|
16
14
|
end
|
17
15
|
|
18
|
-
# Runs locally
|
19
|
-
def broadcast(line, **details)
|
20
|
-
if broadcast_cmd = config.audit_broadcast_cmd
|
21
|
-
[ broadcast_cmd, *broadcast_args(line, **details), env: env_for(event: line, **details) ]
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
16
|
def reveal
|
26
17
|
[ :tail, "-n", 50, audit_log_file ]
|
27
18
|
end
|
@@ -31,29 +22,7 @@ class Mrsk::Commands::Auditor < Mrsk::Commands::Base
|
|
31
22
|
[ "mrsk", config.service, config.destination, "audit.log" ].compact.join("-")
|
32
23
|
end
|
33
24
|
|
34
|
-
def default_details
|
35
|
-
{ recorded_at: Time.now.utc.iso8601,
|
36
|
-
performer: `whoami`.chomp,
|
37
|
-
destination: config.destination }
|
38
|
-
end
|
39
|
-
|
40
25
|
def audit_tags(**details)
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
def broadcast_args(line, **details)
|
45
|
-
"'#{broadcast_tags(**details).join(" ")} #{line}'"
|
46
|
-
end
|
47
|
-
|
48
|
-
def broadcast_tags(**details)
|
49
|
-
tags_for **self.details.merge(details).except(:recorded_at)
|
50
|
-
end
|
51
|
-
|
52
|
-
def tags_for(**details)
|
53
|
-
details.compact.values.map { |value| "[#{value}]" }
|
54
|
-
end
|
55
|
-
|
56
|
-
def env_for(**details)
|
57
|
-
self.details.merge(details).compact.transform_keys { |detail| "MRSK_#{detail.upcase}" }
|
26
|
+
tags(**self.details, **details)
|
58
27
|
end
|
59
28
|
end
|
data/lib/mrsk/commands/base.rb
CHANGED
@@ -18,8 +18,8 @@ module Mrsk::Commands
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
def container_id_for(container_name:)
|
22
|
-
docker :container, :ls, "--all", "--filter", "name=^#{container_name}$", "--quiet"
|
21
|
+
def container_id_for(container_name:, only_running: false)
|
22
|
+
docker :container, :ls, *("--all" unless only_running), "--filter", "name=^#{container_name}$", "--quiet"
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
@@ -53,5 +53,9 @@ module Mrsk::Commands
|
|
53
53
|
def docker(*args)
|
54
54
|
args.compact.unshift :docker
|
55
55
|
end
|
56
|
+
|
57
|
+
def tags(**details)
|
58
|
+
Mrsk::Tags.from_config(config, **details)
|
59
|
+
end
|
56
60
|
end
|
57
61
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Mrsk::Commands::Hook < Mrsk::Commands::Base
|
2
|
+
def run(hook, **details)
|
3
|
+
[ hook_file(hook), env: tags(**details).env ]
|
4
|
+
end
|
5
|
+
|
6
|
+
def hook_exists?(hook)
|
7
|
+
Pathname.new(hook_file(hook)).exist?
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def hook_file(hook)
|
12
|
+
"#{config.hooks_path}/#{hook}"
|
13
|
+
end
|
14
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
2
|
-
delegate :argumentize, :optionize, to: Mrsk::Utils
|
2
|
+
delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Mrsk::Utils
|
3
3
|
|
4
4
|
DEFAULT_IMAGE = "traefik:v2.9"
|
5
5
|
CONTAINER_PORT = 80
|
@@ -10,6 +10,7 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|
10
10
|
"--restart", "unless-stopped",
|
11
11
|
"--publish", port,
|
12
12
|
"--volume", "/var/run/docker.sock:/var/run/docker.sock",
|
13
|
+
*env_args,
|
13
14
|
*config.logging_args,
|
14
15
|
*label_args,
|
15
16
|
*docker_options_args,
|
@@ -61,6 +62,16 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|
61
62
|
argumentize "--label", labels
|
62
63
|
end
|
63
64
|
|
65
|
+
def env_args
|
66
|
+
env_config = config.traefik["env"] || {}
|
67
|
+
|
68
|
+
if env_config.present?
|
69
|
+
argumentize_env_with_secrets(env_config)
|
70
|
+
else
|
71
|
+
[]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
64
75
|
def labels
|
65
76
|
config.traefik["labels"] || []
|
66
77
|
end
|
@@ -37,7 +37,7 @@ class Mrsk::Configuration::Role
|
|
37
37
|
|
38
38
|
def health_check_args
|
39
39
|
if health_check_cmd.present?
|
40
|
-
optionize({ "health-cmd" => health_check_cmd, "health-interval" =>
|
40
|
+
optionize({ "health-cmd" => health_check_cmd, "health-interval" => health_check_interval })
|
41
41
|
else
|
42
42
|
[]
|
43
43
|
end
|
@@ -50,6 +50,13 @@ class Mrsk::Configuration::Role
|
|
50
50
|
options["cmd"] || http_health_check(port: options["port"], path: options["path"])
|
51
51
|
end
|
52
52
|
|
53
|
+
def health_check_interval
|
54
|
+
options = specializations["healthcheck"] || {}
|
55
|
+
options = config.healthcheck.merge(options) if running_traefik?
|
56
|
+
|
57
|
+
options["interval"] || "1s"
|
58
|
+
end
|
59
|
+
|
53
60
|
def cmd
|
54
61
|
specializations["cmd"]
|
55
62
|
end
|
data/lib/mrsk/configuration.rb
CHANGED
@@ -6,7 +6,7 @@ require "erb"
|
|
6
6
|
require "net/ssh/proxy/jump"
|
7
7
|
|
8
8
|
class Mrsk::Configuration
|
9
|
-
delegate :service, :image, :servers, :env, :labels, :registry, :builder, :stop_wait_time, to: :raw_config, allow_nil: true
|
9
|
+
delegate :service, :image, :servers, :env, :labels, :registry, :builder, :stop_wait_time, :hooks_path, to: :raw_config, allow_nil: true
|
10
10
|
delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Mrsk::Utils
|
11
11
|
|
12
12
|
attr_accessor :destination
|
@@ -50,7 +50,7 @@ class Mrsk::Configuration
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def version
|
53
|
-
@declared_version.presence || ENV["VERSION"] ||
|
53
|
+
@declared_version.presence || ENV["VERSION"] || git_version
|
54
54
|
end
|
55
55
|
|
56
56
|
def abbreviated_version
|
@@ -157,10 +157,6 @@ class Mrsk::Configuration
|
|
157
157
|
end
|
158
158
|
|
159
159
|
|
160
|
-
def audit_broadcast_cmd
|
161
|
-
raw_config.audit_broadcast_cmd
|
162
|
-
end
|
163
|
-
|
164
160
|
def healthcheck
|
165
161
|
{ "path" => "/up", "port" => 3000, "max_attempts" => 7 }.merge(raw_config.healthcheck || {})
|
166
162
|
end
|
@@ -197,6 +193,10 @@ class Mrsk::Configuration
|
|
197
193
|
raw_config.traefik || {}
|
198
194
|
end
|
199
195
|
|
196
|
+
def hooks_path
|
197
|
+
raw_config.hooks_path || ".mrsk/hooks"
|
198
|
+
end
|
199
|
+
|
200
200
|
private
|
201
201
|
# Will raise ArgumentError if any required config keys are missing
|
202
202
|
def ensure_required_keys_present
|
@@ -233,10 +233,12 @@ class Mrsk::Configuration
|
|
233
233
|
raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
|
234
234
|
end
|
235
235
|
|
236
|
-
def
|
237
|
-
@
|
236
|
+
def git_version
|
237
|
+
@git_version ||=
|
238
238
|
if system("git rev-parse")
|
239
|
-
`git
|
239
|
+
uncommitted_suffix = `git status --porcelain`.strip.present? ? "_uncommitted_#{SecureRandom.hex(8)}" : ""
|
240
|
+
|
241
|
+
"#{`git rev-parse HEAD`.strip}#{uncommitted_suffix}"
|
240
242
|
else
|
241
243
|
raise "Can't use commit hash as version, no git repository found in #{Dir.pwd}"
|
242
244
|
end
|
data/lib/mrsk/sshkit_with_ext.rb
CHANGED
@@ -8,6 +8,10 @@ class SSHKit::Backend::Abstract
|
|
8
8
|
capture(*args, **kwargs, verbosity: Logger::INFO)
|
9
9
|
end
|
10
10
|
|
11
|
+
def capture_with_debug(*args, **kwargs)
|
12
|
+
capture(*args, **kwargs, verbosity: Logger::DEBUG)
|
13
|
+
end
|
14
|
+
|
11
15
|
def capture_with_pretty_json(*args, **kwargs)
|
12
16
|
JSON.pretty_generate(JSON.parse(capture(*args, **kwargs)))
|
13
17
|
end
|
data/lib/mrsk/tags.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require "time"
|
2
|
+
|
3
|
+
class Mrsk::Tags
|
4
|
+
attr_reader :config, :tags
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def from_config(config, **extra)
|
8
|
+
new(**default_tags(config), **extra)
|
9
|
+
end
|
10
|
+
|
11
|
+
def default_tags(config)
|
12
|
+
{ recorded_at: Time.now.utc.iso8601,
|
13
|
+
performer: `whoami`.chomp,
|
14
|
+
destination: config.destination,
|
15
|
+
version: config.version,
|
16
|
+
service_version: service_version(config) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def service_version(config)
|
20
|
+
[ config.service, config.abbreviated_version ].compact.join("@")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(**tags)
|
25
|
+
@tags = tags.compact
|
26
|
+
end
|
27
|
+
|
28
|
+
def env
|
29
|
+
tags.transform_keys { |detail| "MRSK_#{detail.upcase}" }
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
tags.values.map { |value| "[#{value}]" }.join(" ")
|
34
|
+
end
|
35
|
+
|
36
|
+
def except(*tags)
|
37
|
+
self.class.new(**self.tags.except(*tags))
|
38
|
+
end
|
39
|
+
end
|
data/lib/mrsk/utils.rb
CHANGED
@@ -84,6 +84,13 @@ module Mrsk::Utils
|
|
84
84
|
|
85
85
|
# Abbreviate a git revhash for concise display
|
86
86
|
def abbreviate_version(version)
|
87
|
-
|
87
|
+
if version
|
88
|
+
# Don't abbreviate <sha>_uncommitted_<etc>
|
89
|
+
if version.include?("_")
|
90
|
+
version
|
91
|
+
else
|
92
|
+
version[0...7]
|
93
|
+
end
|
94
|
+
end
|
88
95
|
end
|
89
96
|
end
|
data/lib/mrsk/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mrsk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
11
|
+
date: 2023-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -187,6 +187,9 @@ files:
|
|
187
187
|
- lib/mrsk/cli/registry.rb
|
188
188
|
- lib/mrsk/cli/server.rb
|
189
189
|
- lib/mrsk/cli/templates/deploy.yml
|
190
|
+
- lib/mrsk/cli/templates/sample_hooks/post-deploy.sample
|
191
|
+
- lib/mrsk/cli/templates/sample_hooks/pre-build.sample
|
192
|
+
- lib/mrsk/cli/templates/sample_hooks/pre-connect.sample
|
190
193
|
- lib/mrsk/cli/templates/template.env
|
191
194
|
- lib/mrsk/cli/traefik.rb
|
192
195
|
- lib/mrsk/commander.rb
|
@@ -203,6 +206,7 @@ files:
|
|
203
206
|
- lib/mrsk/commands/builder/native/remote.rb
|
204
207
|
- lib/mrsk/commands/docker.rb
|
205
208
|
- lib/mrsk/commands/healthcheck.rb
|
209
|
+
- lib/mrsk/commands/hook.rb
|
206
210
|
- lib/mrsk/commands/lock.rb
|
207
211
|
- lib/mrsk/commands/prune.rb
|
208
212
|
- lib/mrsk/commands/registry.rb
|
@@ -212,6 +216,7 @@ files:
|
|
212
216
|
- lib/mrsk/configuration/boot.rb
|
213
217
|
- lib/mrsk/configuration/role.rb
|
214
218
|
- lib/mrsk/sshkit_with_ext.rb
|
219
|
+
- lib/mrsk/tags.rb
|
215
220
|
- lib/mrsk/utils.rb
|
216
221
|
- lib/mrsk/utils/healthcheck_poller.rb
|
217
222
|
- lib/mrsk/utils/sensitive.rb
|