Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c03216fdf | ||
|
|
1973f55c58 | ||
|
|
0a51cd0899 | ||
|
|
4b0a8728f1 | ||
|
|
3075f8daf1 | ||
|
|
9985834bd6 | ||
|
|
94b4461c76 | ||
|
|
7afa9e0815 | ||
|
|
933ece35ab | ||
|
|
2f80b300f0 | ||
|
|
2e06bf59a4 | ||
|
|
854795c2b6 | ||
|
|
4fe7fb705a | ||
|
|
270e0d0e2c | ||
|
|
6ddc9cf017 | ||
|
|
2dcd76b2de | ||
|
|
a6eabd0b67 | ||
|
|
fb9357b5ba | ||
|
|
d484cfcc31 | ||
|
|
5c93642f2a | ||
|
|
8ff206ba7e | ||
|
|
e36a5e111c | ||
|
|
72522001e5 | ||
|
|
03328a998c |
@@ -1,7 +1,7 @@
|
|||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
mrsk (0.6.4)
|
mrsk (0.7.1)
|
||||||
activesupport (>= 7.0)
|
activesupport (>= 7.0)
|
||||||
dotenv (~> 2.8)
|
dotenv (~> 2.8)
|
||||||
sshkit (~> 1.21)
|
sshkit (~> 1.21)
|
||||||
|
|||||||
46
README.md
46
README.md
@@ -37,9 +37,10 @@ This will:
|
|||||||
5. Push the image to the registry.
|
5. Push the image to the registry.
|
||||||
6. Pull the image from the registry on the servers.
|
6. Pull the image from the registry on the servers.
|
||||||
7. Ensure Traefik is running and accepting traffic on port 80.
|
7. Ensure Traefik is running and accepting traffic on port 80.
|
||||||
8. Stop any containers running a previous versions of the app.
|
8. Ensure your app responds with `200 OK` to `GET /up`.
|
||||||
9. Start a new container with the version of the app that matches the current git version hash.
|
9. Stop any containers running a previous versions of the app.
|
||||||
10. Prune unused images and stopped containers to ensure servers don't fill up.
|
10. Start a new container with the version of the app that matches the current git version hash.
|
||||||
|
11. Prune unused images and stopped containers to ensure servers don't fill up.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@@ -183,10 +184,10 @@ You can specialize the default Traefik rules by setting labels on the containers
|
|||||||
|
|
||||||
```
|
```
|
||||||
labels:
|
labels:
|
||||||
traefik.http.routers.hey.rule: '''Host(`app.hey.com`)'''
|
traefik.http.routers.hey.rule: Host(\`app.hey.com\`)
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: The extra quotes are needed to ensure the rule is passed in correctly!
|
Note: The escaped backticks are needed to ensure the rule is passed in correctly and not treated as command substitution by Bash!
|
||||||
|
|
||||||
This allows you to run multiple applications on the same server sharing the same Traefik instance and port.
|
This allows you to run multiple applications on the same server sharing the same Traefik instance and port.
|
||||||
See https://doc.traefik.io/traefik/routing/routers/#rule for a full list of available routing rules.
|
See https://doc.traefik.io/traefik/routing/routers/#rule for a full list of available routing rules.
|
||||||
@@ -345,6 +346,41 @@ This template can safely be checked into git. Then everyone deploying the app ca
|
|||||||
|
|
||||||
If you need separate env variables for different destinations, you can set them with `.env.destination.erb` for the template, which will generate `.env.staging` when run with `mrsk envify -d staging`.
|
If you need separate env variables for different destinations, you can set them with `.env.destination.erb` for the template, which will generate `.env.staging` when run with `mrsk envify -d staging`.
|
||||||
|
|
||||||
|
### Using audit broadcasts
|
||||||
|
|
||||||
|
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 reads the audit line from STDIN, and then does whatever with it:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
audit_broadcast_cmd:
|
||||||
|
bin/audit_broadcast
|
||||||
|
```
|
||||||
|
|
||||||
|
The broadcast command could look something like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
read
|
||||||
|
curl -q -d content="[My app] ${REPLY}" https://3.basecamp.com/XXXXX/integrations/XXXXX/buckets/XXXXX/chats/XXXXX/lines
|
||||||
|
```
|
||||||
|
|
||||||
|
That'll post a line like follows to a preconfigured chatbot in Basecamp:
|
||||||
|
|
||||||
|
```
|
||||||
|
[My App] [2023-02-18 11:29:52] [dhh] Rolled back to version d264c4e92470ad1bd18590f04466787262f605de
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using custom healthcheck path or port
|
||||||
|
|
||||||
|
MRSK defaults to checking the health of your application again `/up` on port 3000. You can tailor both with the `healthcheck` setting:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
healthcheck:
|
||||||
|
path: /healthz
|
||||||
|
port: 4000
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
### Running commands on servers
|
### Running commands on servers
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|||||||
upload(name)
|
upload(name)
|
||||||
|
|
||||||
on(accessory.host) do
|
on(accessory.host) do
|
||||||
execute *MRSK.auditor.record("accessory #{name} boot"), verbosity: :debug
|
execute *MRSK.auditor.record("Booted #{name} accessory"), verbosity: :debug
|
||||||
execute *accessory.run
|
execute *accessory.run
|
||||||
end
|
end
|
||||||
|
|
||||||
|
audit_broadcast "Booted accessory #{name}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -20,8 +22,6 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|||||||
def upload(name)
|
def upload(name)
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory|
|
||||||
on(accessory.host) do
|
on(accessory.host) do
|
||||||
execute *MRSK.auditor.record("accessory #{name} upload files"), verbosity: :debug
|
|
||||||
|
|
||||||
accessory.files.each do |(local, remote)|
|
accessory.files.each do |(local, remote)|
|
||||||
accessory.ensure_local_file_present(local)
|
accessory.ensure_local_file_present(local)
|
||||||
|
|
||||||
@@ -37,8 +37,6 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|||||||
def directories(name)
|
def directories(name)
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory|
|
||||||
on(accessory.host) do
|
on(accessory.host) do
|
||||||
execute *MRSK.auditor.record("accessory #{name} create directories"), verbosity: :debug
|
|
||||||
|
|
||||||
accessory.directories.keys.each do |host_path|
|
accessory.directories.keys.each do |host_path|
|
||||||
execute *accessory.make_directory(host_path)
|
execute *accessory.make_directory(host_path)
|
||||||
end
|
end
|
||||||
@@ -59,7 +57,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|||||||
def start(name)
|
def start(name)
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory|
|
||||||
on(accessory.host) do
|
on(accessory.host) do
|
||||||
execute *MRSK.auditor.record("accessory #{name} start"), verbosity: :debug
|
execute *MRSK.auditor.record("Started #{name} accessory"), verbosity: :debug
|
||||||
execute *accessory.start
|
execute *accessory.start
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -69,7 +67,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|||||||
def stop(name)
|
def stop(name)
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory|
|
||||||
on(accessory.host) do
|
on(accessory.host) do
|
||||||
execute *MRSK.auditor.record("accessory #{name} stop"), verbosity: :debug
|
execute *MRSK.auditor.record("Stopped #{name} accessory"), verbosity: :debug
|
||||||
execute *accessory.stop, raise_on_non_zero_exit: false
|
execute *accessory.stop, raise_on_non_zero_exit: false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -111,14 +109,14 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|||||||
when options[:reuse]
|
when options[:reuse]
|
||||||
say "Launching command from existing container...", :magenta
|
say "Launching command from existing container...", :magenta
|
||||||
on(accessory.host) do
|
on(accessory.host) do
|
||||||
execute *MRSK.auditor.record("accessory #{name} cmd '#{cmd}'"), verbosity: :debug
|
execute *MRSK.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
||||||
capture_with_info(*accessory.execute_in_existing_container(cmd))
|
capture_with_info(*accessory.execute_in_existing_container(cmd))
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
say "Launching command from new container...", :magenta
|
say "Launching command from new container...", :magenta
|
||||||
on(accessory.host) do
|
on(accessory.host) do
|
||||||
execute *MRSK.auditor.record("accessory #{name} cmd '#{cmd}'"), verbosity: :debug
|
execute *MRSK.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
||||||
capture_with_info(*accessory.execute_in_new_container(cmd))
|
capture_with_info(*accessory.execute_in_new_container(cmd))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -169,7 +167,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|||||||
def remove_container(name)
|
def remove_container(name)
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory|
|
||||||
on(accessory.host) do
|
on(accessory.host) do
|
||||||
execute *MRSK.auditor.record("accessory #{name} remove container"), verbosity: :debug
|
execute *MRSK.auditor.record("Remove #{name} accessory container"), verbosity: :debug
|
||||||
execute *accessory.remove_container
|
execute *accessory.remove_container
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -179,7 +177,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|||||||
def remove_image(name)
|
def remove_image(name)
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory|
|
||||||
on(accessory.host) do
|
on(accessory.host) do
|
||||||
execute *MRSK.auditor.record("accessory #{name} remove image"), verbosity: :debug
|
execute *MRSK.auditor.record("Removed #{name} accessory image"), verbosity: :debug
|
||||||
execute *accessory.remove_image
|
execute *accessory.remove_image
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -189,7 +187,6 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|||||||
def remove_service_directory(name)
|
def remove_service_directory(name)
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory|
|
||||||
on(accessory.host) do
|
on(accessory.host) do
|
||||||
execute *MRSK.auditor.record("accessory #{name} remove service directory"), verbosity: :debug
|
|
||||||
execute *accessory.remove_service_directory
|
execute *accessory.remove_service_directory
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|||||||
|
|
||||||
MRSK.config.roles.each do |role|
|
MRSK.config.roles.each do |role|
|
||||||
on(role.hosts) do |host|
|
on(role.hosts) do |host|
|
||||||
execute *MRSK.auditor.record("app boot version #{version}"), verbosity: :debug
|
execute *MRSK.auditor.record("Booted app version #{version}"), verbosity: :debug
|
||||||
|
|
||||||
begin
|
begin
|
||||||
execute *MRSK.app.stop, raise_on_non_zero_exit: false
|
execute *MRSK.app.stop, raise_on_non_zero_exit: false
|
||||||
@@ -15,7 +15,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|||||||
rescue SSHKit::Command::Failed => e
|
rescue SSHKit::Command::Failed => e
|
||||||
if e.message =~ /already in use/
|
if e.message =~ /already in use/
|
||||||
error "Rebooting container with same version already deployed on #{host}"
|
error "Rebooting container with same version already deployed on #{host}"
|
||||||
execute *MRSK.auditor.record("app rebooted with version #{version}"), verbosity: :debug
|
execute *MRSK.auditor.record("Rebooted app version #{version}"), verbosity: :debug
|
||||||
|
|
||||||
execute *MRSK.app.remove_container(version: version)
|
execute *MRSK.app.remove_container(version: version)
|
||||||
execute *MRSK.app.run(role: role.name)
|
execute *MRSK.app.run(role: role.name)
|
||||||
@@ -31,7 +31,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|||||||
desc "start", "Start existing app on servers (use --version=<git-hash> to designate specific version)"
|
desc "start", "Start existing app on servers (use --version=<git-hash> to designate specific version)"
|
||||||
def start
|
def start
|
||||||
on(MRSK.hosts) do
|
on(MRSK.hosts) do
|
||||||
execute *MRSK.auditor.record("app start version #{MRSK.version}"), verbosity: :debug
|
execute *MRSK.auditor.record("Started app version #{MRSK.version}"), verbosity: :debug
|
||||||
execute *MRSK.app.start, raise_on_non_zero_exit: false
|
execute *MRSK.app.start, raise_on_non_zero_exit: false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -39,7 +39,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|||||||
desc "stop", "Stop app on servers"
|
desc "stop", "Stop app on servers"
|
||||||
def stop
|
def stop
|
||||||
on(MRSK.hosts) do
|
on(MRSK.hosts) do
|
||||||
execute *MRSK.auditor.record("app stop"), verbosity: :debug
|
execute *MRSK.auditor.record("Stopped app"), verbosity: :debug
|
||||||
execute *MRSK.app.stop, raise_on_non_zero_exit: false
|
execute *MRSK.app.stop, raise_on_non_zero_exit: false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -74,7 +74,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|||||||
say "Launching command with version #{version} from existing container...", :magenta
|
say "Launching command with version #{version} from existing container...", :magenta
|
||||||
|
|
||||||
on(MRSK.hosts) do |host|
|
on(MRSK.hosts) do |host|
|
||||||
execute *MRSK.auditor.record("app cmd '#{cmd}' with version #{version}"), verbosity: :debug
|
execute *MRSK.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
|
||||||
puts_by_host host, capture_with_info(*MRSK.app.execute_in_existing_container(cmd))
|
puts_by_host host, capture_with_info(*MRSK.app.execute_in_existing_container(cmd))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -84,7 +84,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|||||||
using_version(options[:version] || most_recent_version_available) do |version|
|
using_version(options[:version] || most_recent_version_available) do |version|
|
||||||
say "Launching command with version #{version} from new container...", :magenta
|
say "Launching command with version #{version} from new container...", :magenta
|
||||||
on(MRSK.hosts) do |host|
|
on(MRSK.hosts) do |host|
|
||||||
execute *MRSK.auditor.record("app cmd '#{cmd}' with version #{version}"), verbosity: :debug
|
execute *MRSK.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
|
||||||
puts_by_host host, capture_with_info(*MRSK.app.execute_in_new_container(cmd))
|
puts_by_host host, capture_with_info(*MRSK.app.execute_in_new_container(cmd))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -140,7 +140,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|||||||
desc "remove_container [VERSION]", "Remove app container with given version from servers"
|
desc "remove_container [VERSION]", "Remove app container with given version from servers"
|
||||||
def remove_container(version)
|
def remove_container(version)
|
||||||
on(MRSK.hosts) do
|
on(MRSK.hosts) do
|
||||||
execute *MRSK.auditor.record("app remove container #{version}"), verbosity: :debug
|
execute *MRSK.auditor.record("Removed app container with version #{version}"), verbosity: :debug
|
||||||
execute *MRSK.app.remove_container(version: version)
|
execute *MRSK.app.remove_container(version: version)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -148,7 +148,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|||||||
desc "remove_containers", "Remove all app containers from servers"
|
desc "remove_containers", "Remove all app containers from servers"
|
||||||
def remove_containers
|
def remove_containers
|
||||||
on(MRSK.hosts) do
|
on(MRSK.hosts) do
|
||||||
execute *MRSK.auditor.record("app remove containers"), verbosity: :debug
|
execute *MRSK.auditor.record("Removed all app containers"), verbosity: :debug
|
||||||
execute *MRSK.app.remove_containers
|
execute *MRSK.app.remove_containers
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -156,7 +156,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|||||||
desc "remove_images", "Remove all app images from servers"
|
desc "remove_images", "Remove all app images from servers"
|
||||||
def remove_images
|
def remove_images
|
||||||
on(MRSK.hosts) do
|
on(MRSK.hosts) do
|
||||||
execute *MRSK.auditor.record("app remove images"), verbosity: :debug
|
execute *MRSK.auditor.record("Removed all app images"), verbosity: :debug
|
||||||
execute *MRSK.app.remove_images
|
execute *MRSK.app.remove_images
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -59,9 +59,14 @@ module Mrsk::Cli
|
|||||||
def print_runtime
|
def print_runtime
|
||||||
started_at = Time.now
|
started_at = Time.now
|
||||||
yield
|
yield
|
||||||
|
return Time.now - started_at
|
||||||
ensure
|
ensure
|
||||||
runtime = Time.now - started_at
|
runtime = Time.now - started_at
|
||||||
puts " Finished all in #{sprintf("%.1f seconds", runtime)}"
|
puts " Finished all in #{sprintf("%.1f seconds", runtime)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def audit_broadcast(line)
|
||||||
|
run_locally { execute *MRSK.auditor.broadcast(line), verbosity: :debug }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
|
|||||||
desc "pull", "Pull app image from the registry onto servers"
|
desc "pull", "Pull app image from the registry onto servers"
|
||||||
def pull
|
def pull
|
||||||
on(MRSK.hosts) do
|
on(MRSK.hosts) do
|
||||||
execute *MRSK.auditor.record("build pull image #{MRSK.version}"), verbosity: :debug
|
execute *MRSK.auditor.record("Pulled image with version #{MRSK.version}"), verbosity: :debug
|
||||||
execute *MRSK.builder.pull
|
execute *MRSK.builder.pull
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
29
lib/mrsk/cli/healthcheck.rb
Normal file
29
lib/mrsk/cli/healthcheck.rb
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
|
||||||
|
desc "perform", "Health check the current version of the app"
|
||||||
|
def perform
|
||||||
|
on(MRSK.primary_host) do
|
||||||
|
begin
|
||||||
|
execute *MRSK.healthcheck.run
|
||||||
|
|
||||||
|
target = "Health check against #{MRSK.config.healthcheck["path"]}"
|
||||||
|
|
||||||
|
if capture_with_info(*MRSK.healthcheck.curl) == "200"
|
||||||
|
info "#{target} succeeded with 200 OK!"
|
||||||
|
else
|
||||||
|
# Catches 1xx, 2xx, 3xx
|
||||||
|
raise SSHKit::Command::Failed, "#{target} failed to return 200 OK!"
|
||||||
|
end
|
||||||
|
rescue SSHKit::Command::Failed => e
|
||||||
|
if e.message =~ /curl/
|
||||||
|
# Catches 4xx, 5xx
|
||||||
|
raise SSHKit::Command::Failed, "#{target} failed to return 200 OK!"
|
||||||
|
else
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
execute *MRSK.healthcheck.stop, raise_on_non_zero_exit: false
|
||||||
|
execute *MRSK.healthcheck.remove, raise_on_non_zero_exit: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -10,7 +10,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|||||||
|
|
||||||
desc "deploy", "Deploy the app to servers"
|
desc "deploy", "Deploy the app to servers"
|
||||||
def deploy
|
def deploy
|
||||||
print_runtime do
|
runtime = print_runtime do
|
||||||
say "Ensure Docker is installed...", :magenta
|
say "Ensure Docker is installed...", :magenta
|
||||||
invoke "mrsk:cli:server:bootstrap"
|
invoke "mrsk:cli:server:bootstrap"
|
||||||
|
|
||||||
@@ -23,33 +23,48 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|||||||
say "Ensure Traefik is running...", :magenta
|
say "Ensure Traefik is running...", :magenta
|
||||||
invoke "mrsk:cli:traefik:boot"
|
invoke "mrsk:cli:traefik:boot"
|
||||||
|
|
||||||
|
say "Ensure app can pass healthcheck...", :magenta
|
||||||
|
invoke "mrsk:cli:healthcheck:perform"
|
||||||
|
|
||||||
invoke "mrsk:cli:app:boot"
|
invoke "mrsk:cli:app:boot"
|
||||||
|
|
||||||
say "Prune old containers and images...", :magenta
|
say "Prune old containers and images...", :magenta
|
||||||
invoke "mrsk:cli:prune:all"
|
invoke "mrsk:cli:prune:all"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
audit_broadcast "Deployed app in #{runtime.to_i} seconds"
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "redeploy", "Deploy new version of the app to servers (without bootstrapping servers, starting Traefik, pruning, and registry login)"
|
desc "redeploy", "Deploy new version of the app to servers (without bootstrapping servers, starting Traefik, pruning, and registry login)"
|
||||||
def redeploy
|
def redeploy
|
||||||
print_runtime do
|
runtime = print_runtime do
|
||||||
say "Build and push app image...", :magenta
|
say "Build and push app image...", :magenta
|
||||||
invoke "mrsk:cli:build:deliver"
|
invoke "mrsk:cli:build:deliver"
|
||||||
|
|
||||||
|
say "Ensure app can pass healthcheck...", :magenta
|
||||||
|
invoke "mrsk:cli:healthcheck:perform"
|
||||||
|
|
||||||
invoke "mrsk:cli:app:boot"
|
invoke "mrsk:cli:app:boot"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
audit_broadcast "Redeployed app in #{runtime.to_i} seconds"
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "rollback [VERSION]", "Rollback the app to VERSION"
|
desc "rollback [VERSION]", "Rollback the app to VERSION"
|
||||||
def rollback(version)
|
def rollback(version)
|
||||||
MRSK.version = version
|
MRSK.version = version
|
||||||
|
|
||||||
cli = self
|
if container_name_available?(MRSK.config.service_with_version)
|
||||||
|
say "Stop current version, then start version #{version}...", :magenta
|
||||||
|
|
||||||
cli.say "Stop current version, then start version #{version}...", :magenta
|
on(MRSK.hosts) do |host|
|
||||||
on(MRSK.hosts) do
|
execute *MRSK.app.stop, raise_on_non_zero_exit: false
|
||||||
execute *MRSK.app.stop, raise_on_non_zero_exit: false
|
execute *MRSK.app.start
|
||||||
execute *MRSK.app.start
|
end
|
||||||
|
|
||||||
|
audit_broadcast "Rolled back app to version #{version}"
|
||||||
|
else
|
||||||
|
say "The app version '#{version}' is not available as a container (use 'mrsk app containers' for available versions)", :red
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -138,6 +153,9 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|||||||
desc "build", "Build the application image"
|
desc "build", "Build the application image"
|
||||||
subcommand "build", Mrsk::Cli::Build
|
subcommand "build", Mrsk::Cli::Build
|
||||||
|
|
||||||
|
desc "healthcheck", "Healthcheck the application"
|
||||||
|
subcommand "healthcheck", Mrsk::Cli::Healthcheck
|
||||||
|
|
||||||
desc "prune", "Prune old application images and containers"
|
desc "prune", "Prune old application images and containers"
|
||||||
subcommand "prune", Mrsk::Cli::Prune
|
subcommand "prune", Mrsk::Cli::Prune
|
||||||
|
|
||||||
@@ -149,4 +167,11 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|||||||
|
|
||||||
desc "traefik", "Manage the Traefik load balancer"
|
desc "traefik", "Manage the Traefik load balancer"
|
||||||
subcommand "traefik", Mrsk::Cli::Traefik
|
subcommand "traefik", Mrsk::Cli::Traefik
|
||||||
|
|
||||||
|
private
|
||||||
|
def container_name_available?(container_name, host: MRSK.primary_host)
|
||||||
|
container_names = nil
|
||||||
|
on(host) { container_names = capture_with_info(*MRSK.app.list_container_names).split("\n") }
|
||||||
|
Array(container_names).include?(container_name)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ class Mrsk::Cli::Prune < Mrsk::Cli::Base
|
|||||||
invoke :images
|
invoke :images
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "images", "Prune unused images older than 30 days"
|
desc "images", "Prune unused images older than 7 days"
|
||||||
def images
|
def images
|
||||||
on(MRSK.hosts) do
|
on(MRSK.hosts) do
|
||||||
execute *MRSK.auditor.record("prune images"), verbosity: :debug
|
execute *MRSK.auditor.record("Pruned images"), verbosity: :debug
|
||||||
execute *MRSK.prune.images
|
execute *MRSK.prune.images
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -16,7 +16,7 @@ class Mrsk::Cli::Prune < Mrsk::Cli::Base
|
|||||||
desc "containers", "Prune stopped containers for the service older than 3 days"
|
desc "containers", "Prune stopped containers for the service older than 3 days"
|
||||||
def containers
|
def containers
|
||||||
on(MRSK.hosts) do
|
on(MRSK.hosts) do
|
||||||
execute *MRSK.auditor.record("prune containers"), verbosity: :debug
|
execute *MRSK.auditor.record("Pruned containers"), verbosity: :debug
|
||||||
execute *MRSK.prune.containers
|
execute *MRSK.prune.containers
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
|
|||||||
desc "start", "Start existing Traefik on servers"
|
desc "start", "Start existing Traefik on servers"
|
||||||
def start
|
def start
|
||||||
on(MRSK.traefik_hosts) do
|
on(MRSK.traefik_hosts) do
|
||||||
execute *MRSK.auditor.record("traefik start"), verbosity: :debug
|
execute *MRSK.auditor.record("Started traefik"), verbosity: :debug
|
||||||
execute *MRSK.traefik.start, raise_on_non_zero_exit: false
|
execute *MRSK.traefik.start, raise_on_non_zero_exit: false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -22,7 +22,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
|
|||||||
desc "stop", "Stop Traefik on servers"
|
desc "stop", "Stop Traefik on servers"
|
||||||
def stop
|
def stop
|
||||||
on(MRSK.traefik_hosts) do
|
on(MRSK.traefik_hosts) do
|
||||||
execute *MRSK.auditor.record("traefik stop"), verbosity: :debug
|
execute *MRSK.auditor.record("Stopped traefik"), verbosity: :debug
|
||||||
execute *MRSK.traefik.stop, raise_on_non_zero_exit: false
|
execute *MRSK.traefik.stop, raise_on_non_zero_exit: false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -72,7 +72,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
|
|||||||
desc "remove_container", "Remove Traefik container from servers"
|
desc "remove_container", "Remove Traefik container from servers"
|
||||||
def remove_container
|
def remove_container
|
||||||
on(MRSK.traefik_hosts) do
|
on(MRSK.traefik_hosts) do
|
||||||
execute *MRSK.auditor.record("traefik remove container"), verbosity: :debug
|
execute *MRSK.auditor.record("Removed traefik container"), verbosity: :debug
|
||||||
execute *MRSK.traefik.remove_container
|
execute *MRSK.traefik.remove_container
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -80,7 +80,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
|
|||||||
desc "remove_container", "Remove Traefik image from servers"
|
desc "remove_container", "Remove Traefik image from servers"
|
||||||
def remove_image
|
def remove_image
|
||||||
on(MRSK.traefik_hosts) do
|
on(MRSK.traefik_hosts) do
|
||||||
execute *MRSK.auditor.record("traefik remove image"), verbosity: :debug
|
execute *MRSK.auditor.record("Removed traefik image"), verbosity: :debug
|
||||||
execute *MRSK.traefik.remove_image
|
execute *MRSK.traefik.remove_image
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ class Mrsk::Commander
|
|||||||
@auditor ||= Mrsk::Commands::Auditor.new(config)
|
@auditor ||= Mrsk::Commands::Auditor.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def healthcheck
|
||||||
|
@healthcheck ||= Mrsk::Commands::Healthcheck.new(config)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def with_verbosity(level)
|
def with_verbosity(level)
|
||||||
old_level = self.verbosity
|
old_level = self.verbosity
|
||||||
|
|||||||
@@ -75,10 +75,6 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|||||||
docker :ps, "-q", *service_filter
|
docker :ps, "-q", *service_filter
|
||||||
end
|
end
|
||||||
|
|
||||||
def container_id_for(container_name:)
|
|
||||||
docker :container, :ls, "-a", "-f", "name=#{container_name}", "-q"
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_running_version
|
def current_running_version
|
||||||
# FIXME: Find more graceful way to extract the version from "app-version" than using sed and tail!
|
# FIXME: Find more graceful way to extract the version from "app-version" than using sed and tail!
|
||||||
pipe \
|
pipe \
|
||||||
@@ -93,11 +89,21 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|||||||
"head -n 1"
|
"head -n 1"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def all_versions_from_available_containers
|
||||||
|
pipe \
|
||||||
|
docker(:image, :ls, "--format", '"{{.Tag}}"', config.repository),
|
||||||
|
"head -n 1"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def list_containers
|
def list_containers
|
||||||
docker :container, :ls, "-a", *service_filter
|
docker :container, :ls, "-a", *service_filter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def list_container_names
|
||||||
|
[ *list_containers, "--format", "'{{ .Names }}'" ]
|
||||||
|
end
|
||||||
|
|
||||||
def remove_container(version:)
|
def remove_container(version:)
|
||||||
pipe \
|
pipe \
|
||||||
container_id_for(container_name: service_with_version(version)),
|
container_id_for(container_name: service_with_version(version)),
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
require "active_support/core_ext/time/conversions"
|
require "active_support/core_ext/time/conversions"
|
||||||
|
|
||||||
class Mrsk::Commands::Auditor < Mrsk::Commands::Base
|
class Mrsk::Commands::Auditor < Mrsk::Commands::Base
|
||||||
|
# Runs remotely
|
||||||
def record(line)
|
def record(line)
|
||||||
append \
|
append \
|
||||||
[ :echo, tagged_line(line) ],
|
[ :echo, tagged_record_line(line) ],
|
||||||
audit_log_file
|
audit_log_file
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Runs locally
|
||||||
|
def broadcast(line)
|
||||||
|
if broadcast_cmd = config.audit_broadcast_cmd
|
||||||
|
pipe \
|
||||||
|
[ :echo, tagged_broadcast_line(line) ],
|
||||||
|
broadcast_cmd
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def reveal
|
def reveal
|
||||||
[ :tail, "-n", 50, audit_log_file ]
|
[ :tail, "-n", 50, audit_log_file ]
|
||||||
end
|
end
|
||||||
@@ -16,19 +26,19 @@ class Mrsk::Commands::Auditor < Mrsk::Commands::Base
|
|||||||
"mrsk-#{config.service}-audit.log"
|
"mrsk-#{config.service}-audit.log"
|
||||||
end
|
end
|
||||||
|
|
||||||
def tagged_line(line)
|
def tagged_record_line(line)
|
||||||
"'#{tags} #{line}'"
|
"'#{recorded_at_tag} #{performer_tag} #{line}'"
|
||||||
end
|
end
|
||||||
|
|
||||||
def tags
|
def tagged_broadcast_line(line)
|
||||||
"[#{timestamp}] [#{performer}]"
|
"'#{performer_tag} #{line}'"
|
||||||
end
|
end
|
||||||
|
|
||||||
def performer
|
def performer_tag
|
||||||
`whoami`.strip
|
"[#{`whoami`.strip}]"
|
||||||
end
|
end
|
||||||
|
|
||||||
def timestamp
|
def recorded_at_tag
|
||||||
Time.now.to_fs(:db)
|
"[#{Time.now.to_fs(:db)}]"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ module Mrsk::Commands
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def container_id_for(container_name:)
|
||||||
|
docker :container, :ls, "-a", "-f", "name=#{container_name}", "-q"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def combine(*commands, by: "&&")
|
def combine(*commands, by: "&&")
|
||||||
commands
|
commands
|
||||||
|
|||||||
46
lib/mrsk/commands/healthcheck.rb
Normal file
46
lib/mrsk/commands/healthcheck.rb
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
class Mrsk::Commands::Healthcheck < Mrsk::Commands::Base
|
||||||
|
EXPOSED_PORT = 3999
|
||||||
|
|
||||||
|
def run
|
||||||
|
web = config.role(:web)
|
||||||
|
|
||||||
|
docker :run,
|
||||||
|
"-d",
|
||||||
|
"--name", container_name_with_version,
|
||||||
|
"-p", "#{EXPOSED_PORT}:#{config.healthcheck["port"]}",
|
||||||
|
"--label", "service=#{container_name}",
|
||||||
|
*web.env_args,
|
||||||
|
*config.volume_args,
|
||||||
|
config.absolute_image,
|
||||||
|
web.cmd
|
||||||
|
end
|
||||||
|
|
||||||
|
def curl
|
||||||
|
[ :curl, "--silent", "--output", "/dev/null", "--write-out", "'%{http_code}'", health_url ]
|
||||||
|
end
|
||||||
|
|
||||||
|
def stop
|
||||||
|
pipe \
|
||||||
|
container_id_for(container_name: container_name),
|
||||||
|
xargs(docker(:stop))
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove
|
||||||
|
pipe \
|
||||||
|
container_id_for(container_name: container_name),
|
||||||
|
xargs(docker(:container, :rm))
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def container_name
|
||||||
|
"healthcheck-#{config.service}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def container_name_with_version
|
||||||
|
"healthcheck-#{config.service_with_version}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def health_url
|
||||||
|
"http://localhost:#{EXPOSED_PORT}#{config.healthcheck["path"]}"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,14 +2,11 @@ require "active_support/duration"
|
|||||||
require "active_support/core_ext/numeric/time"
|
require "active_support/core_ext/numeric/time"
|
||||||
|
|
||||||
class Mrsk::Commands::Prune < Mrsk::Commands::Base
|
class Mrsk::Commands::Prune < Mrsk::Commands::Base
|
||||||
PRUNE_IMAGES_AFTER = 7.days.in_hours.to_i
|
def images(until_hours: 7.days.in_hours.to_i)
|
||||||
PRUNE_CONTAINERS_AFTER = 3.days.in_hours.to_i
|
docker :image, :prune, "--all", "--force", "--filter", "label=service=#{config.service}", "--filter", "until=#{until_hours}h"
|
||||||
|
|
||||||
def images
|
|
||||||
docker :image, :prune, "--all", "--force", "--filter", "label=service=#{config.service}", "--filter", "until=#{PRUNE_IMAGES_AFTER}h"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def containers
|
def containers(until_hours: 3.days.in_hours.to_i)
|
||||||
docker :container, :prune, "--force", "--filter", "label=service=#{config.service}", "--filter", "until=#{PRUNE_CONTAINERS_AFTER}h"
|
docker :container, :prune, "--force", "--filter", "label=service=#{config.service}", "--filter", "until=#{until_hours}h"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ class Mrsk::Configuration
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def ssh_user
|
def ssh_user
|
||||||
if raw_config.ssh.present?
|
if raw_config.ssh.present?
|
||||||
raw_config.ssh["user"] || "root"
|
raw_config.ssh["user"] || "root"
|
||||||
@@ -127,6 +128,15 @@ class Mrsk::Configuration
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def audit_broadcast_cmd
|
||||||
|
raw_config.audit_broadcast_cmd
|
||||||
|
end
|
||||||
|
|
||||||
|
def healthcheck
|
||||||
|
{ "path" => "/up", "port" => 3000 }.merge(raw_config.healthcheck || {})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def valid?
|
def valid?
|
||||||
ensure_required_keys_present && ensure_env_available
|
ensure_required_keys_present && ensure_env_available
|
||||||
end
|
end
|
||||||
@@ -145,7 +155,8 @@ class Mrsk::Configuration
|
|||||||
volume_args: volume_args,
|
volume_args: volume_args,
|
||||||
ssh_options: ssh_options,
|
ssh_options: ssh_options,
|
||||||
builder: raw_config.builder,
|
builder: raw_config.builder,
|
||||||
accessories: raw_config.accessories
|
accessories: raw_config.accessories,
|
||||||
|
healthcheck: healthcheck
|
||||||
}.compact
|
}.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class Mrsk::Configuration::Role
|
|||||||
if running_traefik?
|
if running_traefik?
|
||||||
{
|
{
|
||||||
"traefik.http.routers.#{config.service}.rule" => "'PathPrefix(`/`)'",
|
"traefik.http.routers.#{config.service}.rule" => "'PathPrefix(`/`)'",
|
||||||
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => "/up",
|
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => config.healthcheck["path"],
|
||||||
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.interval" => "1s",
|
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.interval" => "1s",
|
||||||
"traefik.http.middlewares.#{config.service}.retry.attempts" => "3",
|
"traefik.http.middlewares.#{config.service}.retry.attempts" => "3",
|
||||||
"traefik.http.middlewares.#{config.service}.retry.initialinterval" => "500ms"
|
"traefik.http.middlewares.#{config.service}.retry.initialinterval" => "500ms"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module Mrsk
|
module Mrsk
|
||||||
VERSION = "0.6.4"
|
VERSION = "0.7.1"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,4 +5,29 @@ class CliMainTest < CliTestCase
|
|||||||
version = stdouted { Mrsk::Cli::Main.new.version }
|
version = stdouted { Mrsk::Cli::Main.new.version }
|
||||||
assert_equal Mrsk::VERSION, version
|
assert_equal Mrsk::VERSION, version
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "rollback bad version" do
|
||||||
|
run_command("details") # Preheat MRSK const
|
||||||
|
|
||||||
|
run_command("rollback", "nonsense").tap do |output|
|
||||||
|
assert_match /docker container ls -a --filter label=service=app --format '{{ .Names }}'/, output
|
||||||
|
assert_match /The app version 'nonsense' is not available as a container/, output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "rollback good version" do
|
||||||
|
Mrsk::Cli::Main.any_instance.stubs(:container_name_available?).returns(true)
|
||||||
|
|
||||||
|
run_command("rollback", "123").tap do |output|
|
||||||
|
assert_match /Stop current version, then start version 123/, output
|
||||||
|
assert_match /docker ps -q --filter label=service=app | xargs docker stop/, output
|
||||||
|
assert_match /docker start app-123/, output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
private
|
||||||
|
def run_command(*command)
|
||||||
|
stdouted { Mrsk::Cli::Main.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -26,6 +26,14 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
@app.run.join(" ")
|
@app.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "run with custom healthcheck path" do
|
||||||
|
@config[:healthcheck] = { "path" => "/healthz" }
|
||||||
|
|
||||||
|
assert_equal \
|
||||||
|
"docker run -d --restart unless-stopped --log-opt max-size=10m --name app-999 -e RAILS_MASTER_KEY=456 --label service=app --label role=web --label traefik.http.routers.app.rule='PathPrefix(`/`)' --label traefik.http.services.app.loadbalancer.healthcheck.path=/healthz --label traefik.http.services.app.loadbalancer.healthcheck.interval=1s --label traefik.http.middlewares.app.retry.attempts=3 --label traefik.http.middlewares.app.retry.initialinterval=500ms dhh/app:999",
|
||||||
|
@app.run.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
test "start" do
|
test "start" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker start app-999",
|
"docker start app-999",
|
||||||
|
|||||||
27
test/commands/auditor_test.rb
Normal file
27
test/commands/auditor_test.rb
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class CommandsAuditorTest < ActiveSupport::TestCase
|
||||||
|
setup do
|
||||||
|
@config = {
|
||||||
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
||||||
|
audit_broadcast_cmd: "bin/audit_broadcast"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "record" do
|
||||||
|
assert_match \
|
||||||
|
/echo '.* app removed container' >> mrsk-app-audit.log/,
|
||||||
|
new_command.record("app removed container").join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "broadcast" do
|
||||||
|
assert_match \
|
||||||
|
/echo '.* app removed container' \| bin\/audit_broadcast/,
|
||||||
|
new_command.broadcast("app removed container").join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def new_command
|
||||||
|
Mrsk::Commands::Auditor.new(Mrsk::Configuration.new(@config, version: "123"))
|
||||||
|
end
|
||||||
|
end
|
||||||
55
test/commands/healthcheck_test.rb
Normal file
55
test/commands/healthcheck_test.rb
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class CommandsHealthcheckTest < ActiveSupport::TestCase
|
||||||
|
setup do
|
||||||
|
@config = {
|
||||||
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
||||||
|
traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "run" do
|
||||||
|
assert_equal \
|
||||||
|
"docker run -d --name healthcheck-app-123 -p 3999:3000 --label service=healthcheck-app dhh/app:123",
|
||||||
|
new_command.run.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "run with custom port" do
|
||||||
|
@config[:healthcheck] = { "port" => 3001 }
|
||||||
|
|
||||||
|
assert_equal \
|
||||||
|
"docker run -d --name healthcheck-app-123 -p 3999:3001 --label service=healthcheck-app dhh/app:123",
|
||||||
|
new_command.run.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "curl" do
|
||||||
|
assert_equal \
|
||||||
|
"curl --silent --output /dev/null --write-out '%{http_code}' http://localhost:3999/up",
|
||||||
|
new_command.curl.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "curl with custom path" do
|
||||||
|
@config[:healthcheck] = { "path" => "/healthz" }
|
||||||
|
|
||||||
|
assert_equal \
|
||||||
|
"curl --silent --output /dev/null --write-out '%{http_code}' http://localhost:3999/healthz",
|
||||||
|
new_command.curl.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "stop" do
|
||||||
|
assert_equal \
|
||||||
|
"docker container ls -a -f name=healthcheck-app -q | xargs docker stop",
|
||||||
|
new_command.stop.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "remove" do
|
||||||
|
assert_equal \
|
||||||
|
"docker container ls -a -f name=healthcheck-app -q | xargs docker container rm",
|
||||||
|
new_command.remove.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def new_command
|
||||||
|
Mrsk::Commands::Healthcheck.new(Mrsk::Configuration.new(@config, version: "123"))
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -181,6 +181,6 @@ class ConfigurationTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "to_h" do
|
test "to_h" do
|
||||||
assert_equal({ :roles=>["web"], :hosts=>["1.1.1.1", "1.1.1.2"], :primary_host=>"1.1.1.1", :version=>"missing", :repository=>"dhh/app", :absolute_image=>"dhh/app:missing", :service_with_version=>"app-missing", :env_args=>["-e", "REDIS_URL=redis://x/y"], :ssh_options=>{:user=>"root", :auth_methods=>["publickey"]}, :volume_args=>["--volume", "/local/path:/container/path"] }, @config.to_h)
|
assert_equal({ :roles=>["web"], :hosts=>["1.1.1.1", "1.1.1.2"], :primary_host=>"1.1.1.1", :version=>"missing", :repository=>"dhh/app", :absolute_image=>"dhh/app:missing", :service_with_version=>"app-missing", :env_args=>["-e", "REDIS_URL=redis://x/y"], :ssh_options=>{:user=>"root", :auth_methods=>["publickey"]}, :volume_args=>["--volume", "/local/path:/container/path"], :healthcheck=>{"path"=>"/up", "port"=>3000 }}, @config.to_h)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user