Compare commits
1 Commits
v0.8.2
...
audit-broa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3f2b5d184 |
2
Gemfile
2
Gemfile
@@ -6,5 +6,3 @@ gemspec
|
||||
gem "debug"
|
||||
gem "mocha"
|
||||
gem "railties"
|
||||
gem "ed25519"
|
||||
gem "bcrypt_pbkdf"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
mrsk (0.8.2)
|
||||
mrsk (0.6.4)
|
||||
activesupport (>= 7.0)
|
||||
dotenv (~> 2.8)
|
||||
sshkit (~> 1.21)
|
||||
@@ -29,7 +29,6 @@ GEM
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
bcrypt_pbkdf (1.1.0)
|
||||
builder (3.2.4)
|
||||
concurrent-ruby (1.1.10)
|
||||
crass (1.0.6)
|
||||
@@ -37,7 +36,6 @@ GEM
|
||||
irb (>= 1.5.0)
|
||||
reline (>= 0.3.1)
|
||||
dotenv (2.8.1)
|
||||
ed25519 (1.3.0)
|
||||
erubi (1.12.0)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
@@ -98,9 +96,7 @@ PLATFORMS
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
bcrypt_pbkdf
|
||||
debug
|
||||
ed25519
|
||||
mocha
|
||||
mrsk!
|
||||
railties
|
||||
|
||||
39
README.md
39
README.md
@@ -14,8 +14,7 @@ servers:
|
||||
- 192.168.0.2
|
||||
registry:
|
||||
username: registry-user-name
|
||||
password:
|
||||
- MRSK_REGISTRY_PASSWORD
|
||||
password: <%= ENV.fetch("MRSK_REGISTRY_PASSWORD") %>
|
||||
env:
|
||||
secret:
|
||||
- RAILS_MASTER_KEY
|
||||
@@ -23,6 +22,8 @@ env:
|
||||
|
||||
Then edit your `.env` file to add your registry password as `MRSK_REGISTRY_PASSWORD` (and your `RAILS_MASTER_KEY` for production with a Rails app).
|
||||
|
||||
Finally, you have to ensure your application can answer `200 OK` to a `GET /up` request. That's how the zero-downtime deploy process knows that your new version is ready to serve traffic.
|
||||
|
||||
Now you're ready to deploy to the servers:
|
||||
|
||||
```
|
||||
@@ -38,10 +39,9 @@ This will:
|
||||
5. Push the image to the registry.
|
||||
6. Pull the image from the registry on the servers.
|
||||
7. Ensure Traefik is running and accepting traffic on port 80.
|
||||
8. Ensure your app responds with `200 OK` to `GET /up`.
|
||||
9. Stop any containers running a previous versions of the app.
|
||||
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.
|
||||
8. Stop any containers running a previous versions of the app.
|
||||
9. Start a new container with the version of the app that matches the current git version hash.
|
||||
10. 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.
|
||||
|
||||
@@ -185,10 +185,10 @@ You can specialize the default Traefik rules by setting labels on the containers
|
||||
|
||||
```
|
||||
labels:
|
||||
traefik.http.routers.hey.rule: Host(\`app.hey.com\`)
|
||||
traefik.http.routers.hey.rule: '''Host(`app.hey.com`)'''
|
||||
```
|
||||
|
||||
Note: The escaped backticks are needed to ensure the rule is passed in correctly and not treated as command substitution by Bash!
|
||||
Note: The extra quotes are needed to ensure the rule is passed in correctly!
|
||||
|
||||
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.
|
||||
@@ -349,7 +349,7 @@ If you need separate env variables for different destinations, you can set them
|
||||
|
||||
### 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 will be passed the audit line as the first argument:
|
||||
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:
|
||||
@@ -360,27 +360,16 @@ The broadcast command could look something like:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
curl -q -d content="[My App] ${1}" https://3.basecamp.com/XXXXX/integrations/XXXXX/buckets/XXXXX/chats/XXXXX/lines
|
||||
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] [dhh] Rolled back to version d264c4e92470ad1bd18590f04466787262f605de
|
||||
[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
|
||||
|
||||
### Running commands on servers
|
||||
@@ -448,7 +437,7 @@ mrsk app exec -i 'bin/rails console'
|
||||
```
|
||||
|
||||
|
||||
### Running details to show state of containers
|
||||
### Running details to see state of containers
|
||||
|
||||
You can see the state of your servers by running `mrsk details`:
|
||||
|
||||
@@ -498,7 +487,7 @@ If you wish to remove the entire application, including Traefik, containers, ima
|
||||
|
||||
## Stage of development
|
||||
|
||||
This is beta software. Commands may still move around. But we're live in production at [37signals](https://37signals.com).
|
||||
This is alpha software. Lots of stuff is missing. Lots of stuff will keep moving around for a while.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
||||
desc "boot [NAME]", "Boot new accessory service on host (use NAME=all to boot all accessories)"
|
||||
desc "boot [NAME]", "Boot accessory service on host (use NAME=all to boot all accessories)"
|
||||
def boot(name)
|
||||
if name == "all"
|
||||
MRSK.accessory_names.each { |accessory_name| boot(accessory_name) }
|
||||
@@ -13,12 +13,12 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
||||
execute *accessory.run
|
||||
end
|
||||
|
||||
audit_broadcast "Booted accessory #{name}" unless options[:skip_broadcast]
|
||||
audit_broadcast "Booted accessory #{name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc "upload [NAME]", "Upload accessory files to host", hide: true
|
||||
desc "upload [NAME]", "Upload accessory files to host"
|
||||
def upload(name)
|
||||
with_accessory(name) do |accessory|
|
||||
on(accessory.host) do
|
||||
@@ -33,7 +33,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "directories [NAME]", "Create accessory directories on host", hide: true
|
||||
desc "directories [NAME]", "Create accessory directories on host"
|
||||
def directories(name)
|
||||
with_accessory(name) do |accessory|
|
||||
on(accessory.host) do
|
||||
@@ -44,7 +44,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "reboot [NAME]", "Reboot existing accessory on host (stop container, remove container, start new container)"
|
||||
desc "reboot [NAME]", "Reboot accessory on host (stop container, remove container, start new container)"
|
||||
def reboot(name)
|
||||
with_accessory(name) do |accessory|
|
||||
stop(name)
|
||||
@@ -53,7 +53,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "start [NAME]", "Start existing accessory container on host"
|
||||
desc "start [NAME]", "Start existing accessory on host"
|
||||
def start(name)
|
||||
with_accessory(name) do |accessory|
|
||||
on(accessory.host) do
|
||||
@@ -63,7 +63,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "stop [NAME]", "Stop existing accessory container on host"
|
||||
desc "stop [NAME]", "Stop accessory on host"
|
||||
def stop(name)
|
||||
with_accessory(name) do |accessory|
|
||||
on(accessory.host) do
|
||||
@@ -73,7 +73,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "restart [NAME]", "Restart existing accessory container on host"
|
||||
desc "restart [NAME]", "Restart accessory on host"
|
||||
def restart(name)
|
||||
with_accessory(name) do
|
||||
stop(name)
|
||||
@@ -81,7 +81,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "details [NAME]", "Show details about accessory on host (use NAME=all to show all accessories)"
|
||||
desc "details [NAME]", "Display details about accessory on host (use NAME=all to boot all accessories)"
|
||||
def details(name)
|
||||
if name == "all"
|
||||
MRSK.accessory_names.each { |accessory_name| details(accessory_name) }
|
||||
@@ -92,7 +92,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "exec [NAME] [CMD]", "Execute a custom command on servers (use --help to show options)"
|
||||
desc "exec [NAME] [CMD]", "Execute a custom command on servers"
|
||||
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
||||
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
||||
def exec(name, cmd)
|
||||
@@ -123,7 +123,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "logs [NAME]", "Show log lines from accessory on host (use --help to show options)"
|
||||
desc "logs [NAME]", "Show log lines from accessory on host"
|
||||
option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
||||
option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
|
||||
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
||||
@@ -149,15 +149,11 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "remove [NAME]", "Remove accessory container and image from host (use NAME=all to remove all accessories)"
|
||||
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
||||
desc "remove [NAME]", "Remove accessory container and image from host (use NAME=all to boot all accessories)"
|
||||
def remove(name)
|
||||
if name == "all"
|
||||
if options[:confirmed] || ask("This will remove all containers and images for all accessories. Are you sure?", limited_to: %w( y N ), default: "N") == "y"
|
||||
MRSK.accessory_names.each { |accessory_name| remove(accessory_name) }
|
||||
end
|
||||
else
|
||||
if options[:confirmed] || ask("This will remove all containers and images for #{name}. Are you sure?", limited_to: %w( y N ), default: "N") == "y"
|
||||
with_accessory(name) do
|
||||
stop(name)
|
||||
remove_container(name)
|
||||
@@ -166,9 +162,8 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc "remove_container [NAME]", "Remove accessory container from host", hide: true
|
||||
desc "remove_container [NAME]", "Remove accessory container from host"
|
||||
def remove_container(name)
|
||||
with_accessory(name) do |accessory|
|
||||
on(accessory.host) do
|
||||
@@ -178,7 +173,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "remove_image [NAME]", "Remove accessory image from host", hide: true
|
||||
desc "remove_image [NAME]", "Remove accessory image from host"
|
||||
def remove_image(name)
|
||||
with_accessory(name) do |accessory|
|
||||
on(accessory.host) do
|
||||
@@ -188,7 +183,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host", hide: true
|
||||
desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host"
|
||||
def remove_service_directory(name)
|
||||
with_accessory(name) do |accessory|
|
||||
on(accessory.host) do
|
||||
|
||||
@@ -5,27 +5,18 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
||||
using_version(options[:version] || most_recent_version_available) do |version|
|
||||
say "Start container with version #{version} (or reboot if already running)...", :magenta
|
||||
|
||||
cli = self
|
||||
|
||||
MRSK.config.roles.each do |role|
|
||||
on(role.hosts) do |host|
|
||||
execute *MRSK.auditor.record("Booted app version #{version}"), verbosity: :debug
|
||||
|
||||
begin
|
||||
old_version = capture_with_info(*MRSK.app.current_running_version).strip
|
||||
execute *MRSK.app.stop, raise_on_non_zero_exit: false
|
||||
execute *MRSK.app.run(role: role.name)
|
||||
|
||||
cli.say "Waiting #{MRSK.config.readiness_delay}s for app to boot...", :magenta
|
||||
sleep MRSK.config.readiness_delay
|
||||
|
||||
execute *MRSK.app.stop(version: old_version), raise_on_non_zero_exit: false if old_version.present?
|
||||
|
||||
rescue SSHKit::Command::Failed => e
|
||||
if e.message =~ /already in use/
|
||||
error "Rebooting container with same version #{version} already deployed on #{host} (may cause gap in zero-downtime promise!)"
|
||||
error "Rebooting container with same version already deployed on #{host}"
|
||||
execute *MRSK.auditor.record("Rebooted app version #{version}"), verbosity: :debug
|
||||
|
||||
execute *MRSK.app.stop(version: version)
|
||||
execute *MRSK.app.remove_container(version: version)
|
||||
execute *MRSK.app.run(role: role.name)
|
||||
else
|
||||
@@ -37,7 +28,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "start", "Start existing app container on servers"
|
||||
desc "start", "Start existing app on servers (use --version=<git-hash> to designate specific version)"
|
||||
def start
|
||||
on(MRSK.hosts) do
|
||||
execute *MRSK.auditor.record("Started app version #{MRSK.version}"), verbosity: :debug
|
||||
@@ -45,7 +36,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "stop", "Stop app container on servers"
|
||||
desc "stop", "Stop app on servers"
|
||||
def stop
|
||||
on(MRSK.hosts) do
|
||||
execute *MRSK.auditor.record("Stopped app"), verbosity: :debug
|
||||
@@ -53,13 +44,12 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
# FIXME: Drop in favor of just containers?
|
||||
desc "details", "Show details about app containers"
|
||||
desc "details", "Display details about app containers"
|
||||
def details
|
||||
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.info) }
|
||||
end
|
||||
|
||||
desc "exec [CMD]", "Execute a custom command on servers (use --help to show options)"
|
||||
desc "exec [CMD]", "Execute a custom command on servers"
|
||||
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
||||
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
||||
def exec(cmd)
|
||||
@@ -101,21 +91,21 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "containers", "Show app containers on servers"
|
||||
desc "containers", "List all the app containers currently on servers"
|
||||
def containers
|
||||
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_containers) }
|
||||
end
|
||||
|
||||
desc "images", "Show app images on servers"
|
||||
desc "images", "List all the app images currently on servers"
|
||||
def images
|
||||
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_images) }
|
||||
end
|
||||
|
||||
desc "logs", "Show log lines from app on servers (use --help to show options)"
|
||||
option :since, aliases: "-s", desc: "Show lines since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
||||
option :lines, type: :numeric, aliases: "-n", desc: "Number of lines to show from each server"
|
||||
desc "logs", "Show lines from app on servers"
|
||||
option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
||||
option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
|
||||
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
||||
option :follow, aliases: "-f", desc: "Follow log on primary server (or specific host set by --hosts)"
|
||||
option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
|
||||
def logs
|
||||
# FIXME: Catch when app containers aren't running
|
||||
|
||||
@@ -143,12 +133,11 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
||||
|
||||
desc "remove", "Remove app containers and images from servers"
|
||||
def remove
|
||||
stop
|
||||
remove_containers
|
||||
remove_images
|
||||
end
|
||||
|
||||
desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true
|
||||
desc "remove_container [VERSION]", "Remove app container with given version from servers"
|
||||
def remove_container(version)
|
||||
on(MRSK.hosts) do
|
||||
execute *MRSK.auditor.record("Removed app container with version #{version}"), verbosity: :debug
|
||||
@@ -156,7 +145,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "remove_containers", "Remove all app containers from servers", hide: true
|
||||
desc "remove_containers", "Remove all app containers from servers"
|
||||
def remove_containers
|
||||
on(MRSK.hosts) do
|
||||
execute *MRSK.auditor.record("Removed all app containers"), verbosity: :debug
|
||||
@@ -164,7 +153,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "remove_images", "Remove all app images from servers", hide: true
|
||||
desc "remove_images", "Remove all app images from servers"
|
||||
def remove_images
|
||||
on(MRSK.hosts) do
|
||||
execute *MRSK.auditor.record("Removed all app images"), verbosity: :debug
|
||||
@@ -172,8 +161,8 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "version", "Show app version currently running on servers"
|
||||
def version
|
||||
desc "current_version", "Shows the version currently running"
|
||||
def current_version
|
||||
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.current_running_version).strip }
|
||||
end
|
||||
|
||||
@@ -195,13 +184,8 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
||||
def most_recent_version_available(host: MRSK.primary_host)
|
||||
version = nil
|
||||
on(host) { version = capture_with_info(*MRSK.app.most_recent_version_from_available_images).strip }
|
||||
|
||||
if version == "<none>"
|
||||
raise "Most recent image available was not tagged with a version (returned <none>)"
|
||||
else
|
||||
version.presence
|
||||
end
|
||||
end
|
||||
|
||||
def current_running_version(host: MRSK.primary_host)
|
||||
version = nil
|
||||
|
||||
@@ -17,10 +17,8 @@ module Mrsk::Cli
|
||||
class_option :hosts, aliases: "-h", desc: "Run commands on these hosts instead of all (separate by comma)"
|
||||
class_option :roles, aliases: "-r", desc: "Run commands on these roles instead of all (separate by comma)"
|
||||
|
||||
class_option :config_file, aliases: "-c", default: "config/deploy.yml", desc: "Path to config file"
|
||||
class_option :destination, aliases: "-d", desc: "Specify destination to be used for config file (staging -> deploy.staging.yml)"
|
||||
|
||||
class_option :skip_broadcast, aliases: "-B", type: :boolean, default: false, desc: "Skip audit broadcasts"
|
||||
class_option :config_file, aliases: "-c", default: "config/deploy.yml", desc: "Path to config file (default: config/deploy.yml)"
|
||||
class_option :destination, aliases: "-d", desc: "Specify destination to be used for config file (west -> deploy.west.yml)"
|
||||
|
||||
def initialize(*)
|
||||
super
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
class Mrsk::Cli::Build < Mrsk::Cli::Base
|
||||
desc "deliver", "Build app and push app image to registry then pull image on servers"
|
||||
desc "deliver", "Deliver a newly built app image to servers"
|
||||
def deliver
|
||||
push
|
||||
pull
|
||||
invoke :push
|
||||
invoke :pull
|
||||
end
|
||||
|
||||
desc "push", "Build and push app image to registry"
|
||||
desc "push", "Build locally and push app image to registry"
|
||||
def push
|
||||
cli = self
|
||||
|
||||
@@ -26,16 +26,15 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "pull", "Pull app image from registry onto servers"
|
||||
desc "pull", "Pull app image from the registry onto servers"
|
||||
def pull
|
||||
on(MRSK.hosts) do
|
||||
execute *MRSK.auditor.record("Pulled image with version #{MRSK.version}"), verbosity: :debug
|
||||
execute *MRSK.builder.clean, raise_on_non_zero_exit: false
|
||||
execute *MRSK.builder.pull
|
||||
end
|
||||
end
|
||||
|
||||
desc "create", "Create a build setup"
|
||||
desc "create", "Create a local build setup"
|
||||
def create
|
||||
run_locally do
|
||||
begin
|
||||
@@ -52,7 +51,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "remove", "Remove build setup"
|
||||
desc "remove", "Remove local build setup"
|
||||
def remove
|
||||
run_locally do
|
||||
debug "Using builder: #{MRSK.builder.name}"
|
||||
@@ -60,7 +59,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "details", "Show build setup"
|
||||
desc "details", "Show the name of the configured builder"
|
||||
def details
|
||||
run_locally do
|
||||
puts "Builder: #{MRSK.builder.name}"
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
|
||||
MAX_ATTEMPTS = 7
|
||||
|
||||
class HealthcheckError < StandardError; end
|
||||
|
||||
default_command :perform
|
||||
|
||||
desc "perform", "Health check current app version"
|
||||
def perform
|
||||
on(MRSK.primary_host) do
|
||||
begin
|
||||
execute *MRSK.healthcheck.run
|
||||
|
||||
target = "Health check against #{MRSK.config.healthcheck["path"]}"
|
||||
attempt = 1
|
||||
|
||||
begin
|
||||
status = capture_with_info(*MRSK.healthcheck.curl)
|
||||
|
||||
if status == "200"
|
||||
info "#{target} succeeded with 200 OK!"
|
||||
else
|
||||
raise HealthcheckError, "#{target} failed with status #{status}"
|
||||
end
|
||||
rescue SSHKit::Command::Failed
|
||||
if attempt <= MAX_ATTEMPTS
|
||||
info "#{target} failed to respond, retrying in #{attempt}s..."
|
||||
sleep attempt
|
||||
attempt += 1
|
||||
|
||||
retry
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
rescue SSHKit::Command::Failed, HealthcheckError => e
|
||||
error capture_with_info(*MRSK.healthcheck.logs)
|
||||
|
||||
if e.message =~ /curl/
|
||||
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
|
||||
@@ -1,5 +1,5 @@
|
||||
class Mrsk::Cli::Main < Mrsk::Cli::Base
|
||||
desc "setup", "Setup all accessories and deploy app to servers"
|
||||
desc "setup", "Setup all accessories and deploy the app to servers"
|
||||
def setup
|
||||
print_runtime do
|
||||
invoke "mrsk:cli:server:bootstrap"
|
||||
@@ -8,7 +8,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "deploy", "Deploy app to servers"
|
||||
desc "deploy", "Deploy the app to servers"
|
||||
def deploy
|
||||
runtime = print_runtime do
|
||||
say "Ensure Docker is installed...", :magenta
|
||||
@@ -23,60 +23,43 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
||||
say "Ensure Traefik is running...", :magenta
|
||||
invoke "mrsk:cli:traefik:boot"
|
||||
|
||||
say "Ensure app can pass healthcheck...", :magenta
|
||||
invoke "mrsk:cli:healthcheck:perform"
|
||||
|
||||
invoke "mrsk:cli:app:boot"
|
||||
|
||||
say "Prune old containers and images...", :magenta
|
||||
invoke "mrsk:cli:prune:all"
|
||||
end
|
||||
|
||||
audit_broadcast "Deployed app in #{runtime.to_i} seconds" unless options[:skip_broadcast]
|
||||
audit_broadcast "Deployed in #{runtime.to_i} seconds"
|
||||
end
|
||||
|
||||
desc "redeploy", "Deploy 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
|
||||
runtime = print_runtime do
|
||||
say "Build and push app image...", :magenta
|
||||
invoke "mrsk:cli:build:deliver"
|
||||
|
||||
say "Ensure app can pass healthcheck...", :magenta
|
||||
invoke "mrsk:cli:healthcheck:perform"
|
||||
|
||||
invoke "mrsk:cli:app:boot"
|
||||
end
|
||||
|
||||
audit_broadcast "Redeployed app in #{runtime.to_i} seconds" unless options[:skip_broadcast]
|
||||
audit_broadcast "Redeployed in #{runtime.to_i} seconds"
|
||||
end
|
||||
|
||||
desc "rollback [VERSION]", "Rollback app to VERSION"
|
||||
desc "rollback [VERSION]", "Rollback the app to VERSION"
|
||||
def rollback(version)
|
||||
MRSK.version = version
|
||||
|
||||
if container_name_available?(MRSK.config.service_with_version)
|
||||
say "Start version #{version}, then stop the old version...", :magenta
|
||||
|
||||
cli = self
|
||||
|
||||
on(MRSK.hosts) do |host|
|
||||
old_version = capture_with_info(*MRSK.app.current_running_version).strip.presence
|
||||
|
||||
cli.say "Stop current version, then start version #{version}...", :magenta
|
||||
on(MRSK.hosts) do
|
||||
execute *MRSK.app.stop, raise_on_non_zero_exit: false
|
||||
execute *MRSK.app.start
|
||||
|
||||
cli.say "Waiting #{MRSK.config.readiness_delay}s for app to start...", :magenta
|
||||
sleep MRSK.config.readiness_delay
|
||||
|
||||
execute *MRSK.app.stop(version: old_version), raise_on_non_zero_exit: false
|
||||
end
|
||||
|
||||
audit_broadcast "Rolled back app to version #{version}" unless options[:skip_broadcast]
|
||||
else
|
||||
say "The app version '#{version}' is not available as a container (use 'mrsk app containers' for available versions)", :red
|
||||
end
|
||||
audit_broadcast "Rolled back to version #{version}"
|
||||
end
|
||||
|
||||
desc "details", "Show details about all containers"
|
||||
desc "details", "Display details about Traefik and app containers"
|
||||
def details
|
||||
invoke "mrsk:cli:traefik:details"
|
||||
invoke "mrsk:cli:app:details"
|
||||
@@ -90,7 +73,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "config", "Show combined config (including secrets!)"
|
||||
desc "config", "Show combined config"
|
||||
def config
|
||||
run_locally do
|
||||
puts MRSK.config.to_h.to_yaml
|
||||
@@ -140,56 +123,36 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
||||
File.write(env_path, ERB.new(File.read(env_template_path)).result, perm: 0600)
|
||||
end
|
||||
|
||||
desc "remove", "Remove Traefik, app, accessories, and registry session from servers"
|
||||
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
||||
desc "remove", "Remove Traefik, app, and registry session from servers"
|
||||
def remove
|
||||
if options[:confirmed] || ask(remove_confirmation_question, limited_to: %w( y N ), default: "N") == "y"
|
||||
invoke "mrsk:cli:traefik:remove", [], options.without(:confirmed)
|
||||
invoke "mrsk:cli:app:remove", [], options.without(:confirmed)
|
||||
invoke "mrsk:cli:accessory:remove", [ "all" ]
|
||||
invoke "mrsk:cli:registry:logout", [], options.without(:confirmed)
|
||||
end
|
||||
invoke "mrsk:cli:traefik:remove"
|
||||
invoke "mrsk:cli:app:remove"
|
||||
invoke "mrsk:cli:registry:logout"
|
||||
end
|
||||
|
||||
desc "version", "Show MRSK version"
|
||||
desc "version", "Display the MRSK version"
|
||||
def version
|
||||
puts Mrsk::VERSION
|
||||
end
|
||||
|
||||
desc "accessory", "Manage accessories (db/redis/search)"
|
||||
desc "accessory", "Manage the accessories"
|
||||
subcommand "accessory", Mrsk::Cli::Accessory
|
||||
|
||||
desc "app", "Manage application"
|
||||
desc "app", "Manage the application"
|
||||
subcommand "app", Mrsk::Cli::App
|
||||
|
||||
desc "build", "Build application image"
|
||||
desc "build", "Build the application image"
|
||||
subcommand "build", Mrsk::Cli::Build
|
||||
|
||||
desc "healthcheck", "Healthcheck application"
|
||||
subcommand "healthcheck", Mrsk::Cli::Healthcheck
|
||||
|
||||
desc "prune", "Prune old application images and containers"
|
||||
subcommand "prune", Mrsk::Cli::Prune
|
||||
|
||||
desc "registry", "Login and -out of the image registry"
|
||||
desc "registry", "Login and out of the image registry"
|
||||
subcommand "registry", Mrsk::Cli::Registry
|
||||
|
||||
desc "server", "Bootstrap servers with Docker"
|
||||
subcommand "server", Mrsk::Cli::Server
|
||||
|
||||
desc "traefik", "Manage Traefik load balancer"
|
||||
desc "traefik", "Manage the Traefik load balancer"
|
||||
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
|
||||
|
||||
def remove_confirmation_question
|
||||
"This will remove all containers and images. " +
|
||||
(MRSK.config.accessories.any? ? "Including #{MRSK.config.accessories.collect(&:name).to_sentence}. " : "") +
|
||||
"Are you sure?"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
class Mrsk::Cli::Prune < Mrsk::Cli::Base
|
||||
desc "all", "Prune unused images and stopped containers"
|
||||
def all
|
||||
containers
|
||||
images
|
||||
invoke :containers
|
||||
invoke :images
|
||||
end
|
||||
|
||||
desc "images", "Prune unused images older than 7 days"
|
||||
@@ -13,7 +13,7 @@ class Mrsk::Cli::Prune < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "containers", "Prune stopped containers older than 3 days"
|
||||
desc "containers", "Prune stopped containers for the service older than 3 days"
|
||||
def containers
|
||||
on(MRSK.hosts) do
|
||||
execute *MRSK.auditor.record("Pruned containers"), verbosity: :debug
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
class Mrsk::Cli::Registry < Mrsk::Cli::Base
|
||||
desc "login", "Log in to registry locally and remotely"
|
||||
desc "login", "Login to the registry locally and remotely"
|
||||
def login
|
||||
run_locally { execute *MRSK.registry.login }
|
||||
on(MRSK.hosts) { execute *MRSK.registry.login }
|
||||
# FIXME: This rescue needed?
|
||||
rescue ArgumentError => e
|
||||
puts e.message
|
||||
end
|
||||
|
||||
desc "logout", "Log out of registry remotely"
|
||||
desc "logout", "Logout of the registry remotely"
|
||||
def logout
|
||||
on(MRSK.hosts) { execute *MRSK.registry.logout }
|
||||
# FIXME: This rescue needed?
|
||||
rescue ArgumentError => e
|
||||
puts e.message
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Mrsk::Cli::Server < Mrsk::Cli::Base
|
||||
desc "bootstrap", "Ensure Docker is installed on servers"
|
||||
desc "bootstrap", "Ensure Docker is installed on the servers"
|
||||
def bootstrap
|
||||
on(MRSK.hosts + MRSK.accessory_hosts) { execute "which docker || (apt-get update -y && apt-get install docker.io -y)" }
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Name of your application. Used to uniquely configure containers.
|
||||
# Name of your application. Used to uniquely configuring Traefik and app containers.
|
||||
# Your Dockerfile should set LABEL service=the-same-value to ensure image pruning works.
|
||||
service: my-app
|
||||
|
||||
# Name of the container image.
|
||||
@@ -13,64 +14,4 @@ registry:
|
||||
# Specify the registry server, if you're not using Docker Hub
|
||||
# server: registry.digitalocean.com / ghcr.io / ...
|
||||
username: my-user
|
||||
password:
|
||||
- MRSK_REGISTRY_PASSWORD
|
||||
|
||||
# Inject ENV variables into containers (secrets come from .env).
|
||||
# env:
|
||||
# clear:
|
||||
# DB_HOST: 192.168.0.2
|
||||
# secret:
|
||||
# - RAILS_MASTER_KEY
|
||||
|
||||
# Call a broadcast command on deploys.
|
||||
# audit_broadcast_cmd:
|
||||
# bin/broadcast_to_bc
|
||||
|
||||
# Use a different ssh user than root
|
||||
# ssh:
|
||||
# user: app
|
||||
|
||||
# Configure builder setup.
|
||||
# builder:
|
||||
# args:
|
||||
# RUBY_VERSION: 3.2.0
|
||||
# secrets:
|
||||
# - GITHUB_TOKEN
|
||||
# remote:
|
||||
# arch: amd64
|
||||
# host: ssh://app@192.168.0.1
|
||||
|
||||
# Use accessory services (secrets come from .env).
|
||||
# accessories:
|
||||
# db:
|
||||
# image: mysql:8.0
|
||||
# host: 192.168.0.2
|
||||
# port: 3306
|
||||
# env:
|
||||
# clear:
|
||||
# MYSQL_ROOT_HOST: '%'
|
||||
# secret:
|
||||
# - MYSQL_ROOT_PASSWORD
|
||||
# files:
|
||||
# - config/mysql/production.cnf:/etc/mysql/my.cnf
|
||||
# - db/production.sql.erb:/docker-entrypoint-initdb.d/setup.sql
|
||||
# directories:
|
||||
# - data:/var/lib/mysql
|
||||
# redis:
|
||||
# image: redis:7.0
|
||||
# host: 192.168.0.2
|
||||
# port: 6379
|
||||
# directories:
|
||||
# - data:/data
|
||||
|
||||
# Configure custom arguments for Traefik
|
||||
# traefik:
|
||||
# args:
|
||||
# accesslog: true
|
||||
# accesslog.format: json
|
||||
|
||||
# Configure a custom healthcheck (default is /up on port 3000)
|
||||
# healthcheck:
|
||||
# path: /healthz
|
||||
# port: 4000
|
||||
password: my-password-should-go-somewhere-safe
|
||||
|
||||
@@ -6,12 +6,12 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
|
||||
|
||||
desc "reboot", "Reboot Traefik on servers (stop container, remove container, start new container)"
|
||||
def reboot
|
||||
stop
|
||||
remove_container
|
||||
boot
|
||||
invoke :stop
|
||||
invoke :remove_container
|
||||
invoke :boot
|
||||
end
|
||||
|
||||
desc "start", "Start existing Traefik container on servers"
|
||||
desc "start", "Start existing Traefik on servers"
|
||||
def start
|
||||
on(MRSK.traefik_hosts) do
|
||||
execute *MRSK.auditor.record("Started traefik"), verbosity: :debug
|
||||
@@ -19,7 +19,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "stop", "Stop existing Traefik container on servers"
|
||||
desc "stop", "Stop Traefik on servers"
|
||||
def stop
|
||||
on(MRSK.traefik_hosts) do
|
||||
execute *MRSK.auditor.record("Stopped traefik"), verbosity: :debug
|
||||
@@ -27,13 +27,13 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "restart", "Restart existing Traefik container on servers"
|
||||
desc "restart", "Restart Traefik on servers"
|
||||
def restart
|
||||
stop
|
||||
start
|
||||
invoke :stop
|
||||
invoke :start
|
||||
end
|
||||
|
||||
desc "details", "Show details about Traefik container from servers"
|
||||
desc "details", "Display details about Traefik containers from servers"
|
||||
def details
|
||||
on(MRSK.traefik_hosts) { |host| puts_by_host host, capture_with_info(*MRSK.traefik.info), type: "Traefik" }
|
||||
end
|
||||
@@ -64,12 +64,12 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
|
||||
|
||||
desc "remove", "Remove Traefik container and image from servers"
|
||||
def remove
|
||||
stop
|
||||
remove_container
|
||||
remove_image
|
||||
invoke :stop
|
||||
invoke :remove_container
|
||||
invoke :remove_image
|
||||
end
|
||||
|
||||
desc "remove_container", "Remove Traefik container from servers", hide: true
|
||||
desc "remove_container", "Remove Traefik container from servers"
|
||||
def remove_container
|
||||
on(MRSK.traefik_hosts) do
|
||||
execute *MRSK.auditor.record("Removed traefik container"), verbosity: :debug
|
||||
@@ -77,7 +77,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "remove_container", "Remove Traefik image from servers", hide: true
|
||||
desc "remove_container", "Remove Traefik image from servers"
|
||||
def remove_image
|
||||
on(MRSK.traefik_hosts) do
|
||||
execute *MRSK.auditor.record("Removed traefik image"), verbosity: :debug
|
||||
|
||||
@@ -49,32 +49,28 @@ class Mrsk::Commander
|
||||
@app ||= Mrsk::Commands::App.new(config)
|
||||
end
|
||||
|
||||
def accessory(name)
|
||||
Mrsk::Commands::Accessory.new(config, name: name)
|
||||
end
|
||||
|
||||
def auditor
|
||||
@auditor ||= Mrsk::Commands::Auditor.new(config)
|
||||
end
|
||||
|
||||
def builder
|
||||
@builder ||= Mrsk::Commands::Builder.new(config)
|
||||
end
|
||||
|
||||
def healthcheck
|
||||
@healthcheck ||= Mrsk::Commands::Healthcheck.new(config)
|
||||
end
|
||||
|
||||
def prune
|
||||
@prune ||= Mrsk::Commands::Prune.new(config)
|
||||
def traefik
|
||||
@traefik ||= Mrsk::Commands::Traefik.new(config)
|
||||
end
|
||||
|
||||
def registry
|
||||
@registry ||= Mrsk::Commands::Registry.new(config)
|
||||
end
|
||||
|
||||
def traefik
|
||||
@traefik ||= Mrsk::Commands::Traefik.new(config)
|
||||
def prune
|
||||
@prune ||= Mrsk::Commands::Prune.new(config)
|
||||
end
|
||||
|
||||
def accessory(name)
|
||||
Mrsk::Commands::Accessory.new(config, name: name)
|
||||
end
|
||||
|
||||
def auditor
|
||||
@auditor ||= Mrsk::Commands::Auditor.new(config)
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
||||
def run
|
||||
docker :run,
|
||||
"--name", service_name,
|
||||
"--detach",
|
||||
"-d",
|
||||
"--restart", "unless-stopped",
|
||||
"--log-opt", "max-size=#{MAX_LOG_SIZE}",
|
||||
"--publish", port,
|
||||
"-p", port,
|
||||
*env_args,
|
||||
*volume_args,
|
||||
*label_args,
|
||||
@@ -35,14 +35,14 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
||||
|
||||
def logs(since: nil, lines: nil, grep: nil)
|
||||
pipe \
|
||||
docker(:logs, service_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), "--timestamps", "2>&1"),
|
||||
docker(:logs, service_name, (" --since #{since}" if since), (" -n #{lines}" if lines), "-t", "2>&1"),
|
||||
("grep '#{grep}'" if grep)
|
||||
end
|
||||
|
||||
def follow_logs(grep: nil)
|
||||
run_over_ssh \
|
||||
pipe \
|
||||
docker(:logs, service_name, "--timestamps", "--tail", "10", "--follow", "2>&1"),
|
||||
docker(:logs, service_name, "-t", "-n", "10", "-f", "2>&1"),
|
||||
(%(grep "#{grep}") if grep)
|
||||
end
|
||||
|
||||
@@ -96,11 +96,11 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
||||
end
|
||||
|
||||
def remove_container
|
||||
docker :container, :prune, "--force", *service_filter
|
||||
docker :container, :prune, "-f", *service_filter
|
||||
end
|
||||
|
||||
def remove_image
|
||||
docker :image, :prune, "--all", "--force", *service_filter
|
||||
docker :image, :prune, "-a", "-f", *service_filter
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -3,7 +3,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
||||
role = config.role(role)
|
||||
|
||||
docker :run,
|
||||
"--detach",
|
||||
"-d",
|
||||
"--restart unless-stopped",
|
||||
"--log-opt", "max-size=#{MAX_LOG_SIZE}",
|
||||
"--name", service_with_version,
|
||||
@@ -18,10 +18,8 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
||||
docker :start, service_with_version
|
||||
end
|
||||
|
||||
def stop(version: nil)
|
||||
pipe \
|
||||
version ? container_id_for_version(version) : current_container_id,
|
||||
xargs(docker(:stop))
|
||||
def stop
|
||||
pipe current_container_id, xargs(docker(:stop))
|
||||
end
|
||||
|
||||
def info
|
||||
@@ -32,7 +30,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
||||
def logs(since: nil, lines: nil, grep: nil)
|
||||
pipe \
|
||||
current_container_id,
|
||||
"xargs docker logs#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1",
|
||||
"xargs docker logs#{" --since #{since}" if since}#{" -n #{lines}" if lines} 2>&1",
|
||||
("grep '#{grep}'" if grep)
|
||||
end
|
||||
|
||||
@@ -40,7 +38,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
||||
run_over_ssh \
|
||||
pipe(
|
||||
current_container_id,
|
||||
"xargs docker logs --timestamps --tail 10 --follow 2>&1",
|
||||
"xargs docker logs -t -n 10 -f 2>&1",
|
||||
(%(grep "#{grep}") if grep)
|
||||
),
|
||||
host: host
|
||||
@@ -74,7 +72,11 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
||||
|
||||
|
||||
def current_container_id
|
||||
docker :ps, "--quiet", *service_filter
|
||||
docker :ps, "-q", *service_filter
|
||||
end
|
||||
|
||||
def container_id_for(container_name:)
|
||||
docker :container, :ls, "-a", "-f", "name=#{container_name}", "-q"
|
||||
end
|
||||
|
||||
def current_running_version
|
||||
@@ -91,19 +93,9 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
||||
"head -n 1"
|
||||
end
|
||||
|
||||
def all_versions_from_available_containers
|
||||
pipe \
|
||||
docker(:image, :ls, "--format", '"{{.Tag}}"', config.repository),
|
||||
"head -n 1"
|
||||
end
|
||||
|
||||
|
||||
def list_containers
|
||||
docker :container, :ls, "--all", *service_filter
|
||||
end
|
||||
|
||||
def list_container_names
|
||||
[ *list_containers, "--format", "'{{ .Names }}'" ]
|
||||
docker :container, :ls, "-a", *service_filter
|
||||
end
|
||||
|
||||
def remove_container(version:)
|
||||
@@ -113,7 +105,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
||||
end
|
||||
|
||||
def remove_containers
|
||||
docker :container, :prune, "--force", *service_filter
|
||||
docker :container, :prune, "-f", *service_filter
|
||||
end
|
||||
|
||||
def list_images
|
||||
@@ -121,7 +113,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
||||
end
|
||||
|
||||
def remove_images
|
||||
docker :image, :prune, "--all", "--force", *service_filter
|
||||
docker :image, :prune, "-a", "-f", *service_filter
|
||||
end
|
||||
|
||||
|
||||
@@ -134,10 +126,6 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
||||
end
|
||||
end
|
||||
|
||||
def container_id_for_version(version)
|
||||
container_id_for(container_name: service_with_version(version))
|
||||
end
|
||||
|
||||
def service_filter
|
||||
[ "--filter", "label=service=#{config.service}" ]
|
||||
end
|
||||
|
||||
@@ -4,14 +4,16 @@ class Mrsk::Commands::Auditor < Mrsk::Commands::Base
|
||||
# Runs remotely
|
||||
def record(line)
|
||||
append \
|
||||
[ :echo, tagged_record_line(line) ],
|
||||
[ :echo, tagged_line(line) ],
|
||||
audit_log_file
|
||||
end
|
||||
|
||||
# Runs locally
|
||||
def broadcast(line)
|
||||
if broadcast_cmd = config.audit_broadcast_cmd
|
||||
[ broadcast_cmd, tagged_broadcast_line(line) ]
|
||||
pipe \
|
||||
[ :echo, tagged_line(line) ],
|
||||
broadcast_cmd
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,19 +26,19 @@ class Mrsk::Commands::Auditor < Mrsk::Commands::Base
|
||||
"mrsk-#{config.service}-audit.log"
|
||||
end
|
||||
|
||||
def tagged_record_line(line)
|
||||
"'#{recorded_at_tag} #{performer_tag} #{line}'"
|
||||
def tagged_line(line)
|
||||
"'#{tags} #{line}'"
|
||||
end
|
||||
|
||||
def tagged_broadcast_line(line)
|
||||
"'#{performer_tag} #{line}'"
|
||||
def tags
|
||||
"[#{recorded_at}] [#{performer}]"
|
||||
end
|
||||
|
||||
def performer_tag
|
||||
"[#{`whoami`.strip}]"
|
||||
def performer
|
||||
@performer ||= `whoami`.strip
|
||||
end
|
||||
|
||||
def recorded_at_tag
|
||||
"[#{Time.now.to_fs(:db)}]"
|
||||
def recorded_at
|
||||
Time.now.to_fs(:db)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,10 +17,6 @@ module Mrsk::Commands
|
||||
end
|
||||
end
|
||||
|
||||
def container_id_for(container_name:)
|
||||
docker :container, :ls, "--all", "--filter", "name=#{container_name}", "--quiet"
|
||||
end
|
||||
|
||||
private
|
||||
def combine(*commands, by: "&&")
|
||||
commands
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Mrsk::Commands::Builder < Mrsk::Commands::Base
|
||||
delegate :create, :remove, :push, :clean, :pull, :info, to: :target
|
||||
delegate :create, :remove, :push, :pull, :info, to: :target
|
||||
|
||||
def name
|
||||
target.class.to_s.remove("Mrsk::Commands::Builder::").underscore
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
|
||||
delegate :argumentize, to: Mrsk::Utils
|
||||
|
||||
def clean
|
||||
docker :image, :rm, "--force", config.absolute_image
|
||||
end
|
||||
|
||||
def pull
|
||||
docker :pull, config.absolute_image
|
||||
end
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
class Mrsk::Commands::Healthcheck < Mrsk::Commands::Base
|
||||
EXPOSED_PORT = 3999
|
||||
|
||||
def run
|
||||
web = config.role(:web)
|
||||
|
||||
docker :run,
|
||||
"--detach",
|
||||
"--name", container_name_with_version,
|
||||
"--publish", "#{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}'", "--max-time", "2", health_url ]
|
||||
end
|
||||
|
||||
def logs
|
||||
pipe container_id, xargs(docker(:logs, "--tail", 50, "2>&1"))
|
||||
end
|
||||
|
||||
def stop
|
||||
pipe container_id, xargs(docker(:stop))
|
||||
end
|
||||
|
||||
def remove
|
||||
pipe container_id, 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 container_id
|
||||
container_id_for(container_name: container_name)
|
||||
end
|
||||
|
||||
def health_url
|
||||
"http://localhost:#{EXPOSED_PORT}#{config.healthcheck["path"]}"
|
||||
end
|
||||
end
|
||||
@@ -2,19 +2,10 @@ class Mrsk::Commands::Registry < Mrsk::Commands::Base
|
||||
delegate :registry, to: :config
|
||||
|
||||
def login
|
||||
docker :login, registry["server"], "-u", redact(registry["username"]), "-p", redact(lookup_password)
|
||||
docker :login, registry["server"], "-u", redact(registry["username"]), "-p", redact(registry["password"])
|
||||
end
|
||||
|
||||
def logout
|
||||
docker :logout, registry["server"]
|
||||
end
|
||||
|
||||
private
|
||||
def lookup_password
|
||||
if registry["password"].is_a?(Array)
|
||||
ENV.fetch(registry["password"].first).dup
|
||||
else
|
||||
registry["password"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
||||
def run
|
||||
docker :run, "--name traefik",
|
||||
"--detach",
|
||||
"-d",
|
||||
"--restart", "unless-stopped",
|
||||
"--log-opt", "max-size=#{MAX_LOG_SIZE}",
|
||||
"--publish", "80:80",
|
||||
"--volume", "/var/run/docker.sock:/var/run/docker.sock",
|
||||
"-p 80:80",
|
||||
"-v /var/run/docker.sock:/var/run/docker.sock",
|
||||
"traefik",
|
||||
"--providers.docker",
|
||||
"--log.level=DEBUG",
|
||||
@@ -26,23 +26,23 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
||||
|
||||
def logs(since: nil, lines: nil, grep: nil)
|
||||
pipe \
|
||||
docker(:logs, "traefik", (" --since #{since}" if since), (" --tail #{lines}" if lines), "--timestamps", "2>&1"),
|
||||
docker(:logs, "traefik", (" --since #{since}" if since), (" -n #{lines}" if lines), "-t", "2>&1"),
|
||||
("grep '#{grep}'" if grep)
|
||||
end
|
||||
|
||||
def follow_logs(host:, grep: nil)
|
||||
run_over_ssh pipe(
|
||||
docker(:logs, "traefik", "--timestamps", "--tail", "10", "--follow", "2>&1"),
|
||||
docker(:logs, "traefik", "-t", "-n", "10", "-f", "2>&1"),
|
||||
(%(grep "#{grep}") if grep)
|
||||
).join(" "), host: host
|
||||
end
|
||||
|
||||
def remove_container
|
||||
docker :container, :prune, "--force", "--filter", "label=org.opencontainers.image.title=Traefik"
|
||||
docker :container, :prune, "-f", "--filter", "label=org.opencontainers.image.title=Traefik"
|
||||
end
|
||||
|
||||
def remove_image
|
||||
docker :image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=Traefik"
|
||||
docker :image, :prune, "-a", "-f", "--filter", "label=org.opencontainers.image.title=Traefik"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -107,7 +107,6 @@ class Mrsk::Configuration
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def ssh_user
|
||||
if raw_config.ssh.present?
|
||||
raw_config.ssh["user"] || "root"
|
||||
@@ -127,18 +126,10 @@ class Mrsk::Configuration
|
||||
{ user: ssh_user, proxy: ssh_proxy, auth_methods: [ "publickey" ] }.compact
|
||||
end
|
||||
|
||||
|
||||
def audit_broadcast_cmd
|
||||
raw_config.audit_broadcast_cmd
|
||||
end
|
||||
|
||||
def healthcheck
|
||||
{ "path" => "/up", "port" => 3000 }.merge(raw_config.healthcheck || {})
|
||||
end
|
||||
|
||||
def readiness_delay
|
||||
raw_config.readiness_delay || 7
|
||||
end
|
||||
|
||||
def valid?
|
||||
ensure_required_keys_present && ensure_env_available
|
||||
@@ -158,8 +149,7 @@ class Mrsk::Configuration
|
||||
volume_args: volume_args,
|
||||
ssh_options: ssh_options,
|
||||
builder: raw_config.builder,
|
||||
accessories: raw_config.accessories,
|
||||
healthcheck: healthcheck
|
||||
accessories: raw_config.accessories
|
||||
}.compact
|
||||
end
|
||||
|
||||
|
||||
@@ -58,10 +58,10 @@ class Mrsk::Configuration::Role
|
||||
def traefik_labels
|
||||
if running_traefik?
|
||||
{
|
||||
"traefik.http.routers.#{config.service}.rule" => "PathPrefix(`/`)",
|
||||
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => config.healthcheck["path"],
|
||||
"traefik.http.routers.#{config.service}.rule" => "'PathPrefix(`/`)'",
|
||||
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => "/up",
|
||||
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.interval" => "1s",
|
||||
"traefik.http.middlewares.#{config.service}.retry.attempts" => "5",
|
||||
"traefik.http.middlewares.#{config.service}.retry.attempts" => "3",
|
||||
"traefik.http.middlewares.#{config.service}.retry.initialinterval" => "500ms"
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
module Mrsk::Utils
|
||||
extend self
|
||||
|
||||
# Return a list of escaped shell arguments using the same named argument against the passed attributes (hash or array).
|
||||
# Return a list of shell arguments using the same named argument against the passed attributes (hash or array).
|
||||
def argumentize(argument, attributes, redacted: false)
|
||||
Array(attributes).flat_map do |key, value|
|
||||
if value.present?
|
||||
escaped_pair = [ key, value.to_s.dump.gsub(/`/, '\\\\`') ].join("=")
|
||||
[ argument, redacted ? redact(escaped_pair) : escaped_pair ]
|
||||
Array(attributes).flat_map do |k, v|
|
||||
if v.present?
|
||||
[ argument, redacted ? redact("#{k}=#{v}") : "#{k}=#{v}" ]
|
||||
else
|
||||
[ argument, key ]
|
||||
[ argument, k ]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module Mrsk
|
||||
VERSION = "0.8.2"
|
||||
VERSION = "0.6.4"
|
||||
end
|
||||
|
||||
@@ -14,7 +14,7 @@ class CliAccessoryTest < CliTestCase
|
||||
end
|
||||
|
||||
test "boot" do
|
||||
assert_match "Running docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=10m --publish 3306:3306 -e [REDACTED] -e MYSQL_ROOT_HOST=\"%\" --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", run_command("boot", "mysql")
|
||||
assert_match "Running docker run --name app-mysql -d --restart unless-stopped --log-opt max-size=10m -p 3306:3306 -e [REDACTED] -e MYSQL_ROOT_HOST=% --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=app-mysql mysql:5.7 on 1.1.1.3", run_command("boot", "mysql")
|
||||
end
|
||||
|
||||
test "exec" do
|
||||
@@ -31,14 +31,6 @@ class CliAccessoryTest < CliTestCase
|
||||
end
|
||||
end
|
||||
|
||||
test "remove with confirmation" do
|
||||
run_command("remove", "mysql", "-y").tap do |output|
|
||||
assert_match /docker container stop app-mysql/, output
|
||||
assert_match /docker image prune --all --force --filter label=service=app-mysql/, output
|
||||
assert_match /rm -rf app-mysql/, output
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def run_command(*command)
|
||||
stdouted { Mrsk::Cli::Accessory.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }
|
||||
|
||||
@@ -2,16 +2,7 @@ require_relative "cli_test_case"
|
||||
|
||||
class CliAppTest < CliTestCase
|
||||
test "boot" do
|
||||
# Stub current version fetch
|
||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture)
|
||||
.returns("999") # new version
|
||||
.then
|
||||
.returns("123") # old version
|
||||
|
||||
run_command("boot").tap do |output|
|
||||
assert_match /docker run --detach --restart unless-stopped/, output
|
||||
assert_match /docker container ls --all --filter name=app-123 --quiet | xargs docker stop/, output
|
||||
end
|
||||
assert_match /Running docker run -d --restart unless-stopped/, run_command("boot")
|
||||
end
|
||||
|
||||
test "boot will reboot if same version is already running" do
|
||||
@@ -20,17 +11,12 @@ class CliAppTest < CliTestCase
|
||||
# Prevent expected failures from outputting to terminal
|
||||
Thread.report_on_exception = false
|
||||
|
||||
MRSK.app.stubs(:run)
|
||||
.raises(SSHKit::Command::Failed.new("already in use"))
|
||||
.then
|
||||
.raises(SSHKit::Command::Failed.new("already in use"))
|
||||
.then
|
||||
.returns([ :docker, :run ])
|
||||
MRSK.app.stubs(:run).raises(SSHKit::Command::Failed.new("already in use")).then.returns([ :docker, :run ])
|
||||
|
||||
run_command("boot").tap do |output|
|
||||
assert_match /Rebooting container with same version 999 already deployed/, output # Can't start what's already running
|
||||
assert_match /docker container ls --all --filter name=app-999 --quiet | xargs docker container rm/, output # Stop old running
|
||||
assert_match /docker container ls --all --filter name=app-999 --quiet | xargs docker container rm/, output # Remove old container
|
||||
assert_match /Rebooting container with same version already deployed/, output # Can't start what's already running
|
||||
assert_match /docker ps -q --filter label=service=app \| xargs docker stop/, output # Stop what's running
|
||||
assert_match /docker container ls -a -f name=app-999 -q \| xargs docker container rm/, output # Remove old container
|
||||
assert_match /docker run/, output # Start new container
|
||||
end
|
||||
ensure
|
||||
@@ -45,7 +31,7 @@ class CliAppTest < CliTestCase
|
||||
|
||||
test "stop" do
|
||||
run_command("stop").tap do |output|
|
||||
assert_match /docker ps --quiet --filter label=service=app \| xargs docker stop/, output
|
||||
assert_match /docker ps -q --filter label=service=app \| xargs docker stop/, output
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,17 +41,9 @@ class CliAppTest < CliTestCase
|
||||
end
|
||||
end
|
||||
|
||||
test "remove" do
|
||||
run_command("remove").tap do |output|
|
||||
assert_match /docker ps --quiet --filter label=service=app | xargs docker stop/, output
|
||||
assert_match /docker container prune --force --filter label=service=app/, output
|
||||
assert_match /docker image prune --all --force --filter label=service=app/, output
|
||||
end
|
||||
end
|
||||
|
||||
test "remove_container" do
|
||||
run_command("remove_container", "1234567").tap do |output|
|
||||
assert_match /docker container ls --all --filter name=app-1234567 --quiet \| xargs docker container rm/, output
|
||||
assert_match /docker container ls -a -f name=app-1234567 -q \| xargs docker container rm/, output
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
require_relative "cli_test_case"
|
||||
|
||||
class CliBuildTest < CliTestCase
|
||||
test "pull" do
|
||||
run_command("pull").tap do |output|
|
||||
assert_match /docker image rm --force dhh\/app:999/, output
|
||||
assert_match /docker pull dhh\/app:999/, output
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def run_command(*command)
|
||||
stdouted { Mrsk::Cli::Build.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }
|
||||
end
|
||||
end
|
||||
@@ -5,52 +5,4 @@ class CliMainTest < CliTestCase
|
||||
version = stdouted { Mrsk::Cli::Main.new.version }
|
||||
assert_equal Mrsk::VERSION, version
|
||||
end
|
||||
|
||||
test "rollback bad version" do
|
||||
run_command("details") # Preheat MRSK const
|
||||
|
||||
run_command("rollback", "nonsense").tap do |output|
|
||||
assert_match /docker container ls --all --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 /Start version 123, then stop the old version/, output
|
||||
assert_match /docker ps -q --filter label=service=app | xargs docker stop/, output
|
||||
assert_match /docker start app-123/, output
|
||||
end
|
||||
end
|
||||
|
||||
test "remove with confirmation" do
|
||||
run_command("remove", "-y").tap do |output|
|
||||
assert_match /docker container stop traefik/, output
|
||||
assert_match /docker container prune --force --filter label=org.opencontainers.image.title=Traefik/, output
|
||||
assert_match /docker image prune --all --force --filter label=org.opencontainers.image.title=Traefik/, output
|
||||
|
||||
assert_match /docker ps --quiet --filter label=service=app | xargs docker stop/, output
|
||||
assert_match /docker container prune --force --filter label=service=app/, output
|
||||
assert_match /docker image prune --all --force --filter label=service=app/, output
|
||||
|
||||
assert_match /docker container stop app-mysql/, output
|
||||
assert_match /docker container prune --force --filter label=service=app-mysql/, output
|
||||
assert_match /docker image prune --all --force --filter label=service=app-mysql/, output
|
||||
assert_match /rm -rf app-mysql/, output
|
||||
|
||||
assert_match /docker container stop app-redis/, output
|
||||
assert_match /docker container prune --force --filter label=service=app-redis/, output
|
||||
assert_match /docker image prune --all --force --filter label=service=app-redis/, output
|
||||
assert_match /rm -rf app-redis/, output
|
||||
|
||||
assert_match /docker logout/, output
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def run_command(*command)
|
||||
stdouted { Mrsk::Cli::Main.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -49,11 +49,11 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
||||
|
||||
test "run" do
|
||||
assert_equal \
|
||||
"docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=10m --publish 3306:3306 -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" --label service=\"app-mysql\" mysql:8.0",
|
||||
"docker run --name app-mysql -d --restart unless-stopped --log-opt max-size=10m -p 3306:3306 -e MYSQL_ROOT_PASSWORD=secret123 -e MYSQL_ROOT_HOST=% --label service=app-mysql mysql:8.0",
|
||||
@mysql.run.join(" ")
|
||||
|
||||
assert_equal \
|
||||
"docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=10m --publish 6379:6379 -e SOMETHING=\"else\" --volume /var/lib/redis:/data --label service=\"app-redis\" --label cache=\"true\" redis:latest",
|
||||
"docker run --name app-redis -d --restart unless-stopped --log-opt max-size=10m -p 6379:6379 -e SOMETHING=else --volume /var/lib/redis:/data --label service=app-redis --label cache=true redis:latest",
|
||||
@redis.run.join(" ")
|
||||
end
|
||||
|
||||
@@ -78,7 +78,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
||||
|
||||
test "execute in new container" do
|
||||
assert_equal \
|
||||
"docker run --rm -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" mysql:8.0 mysql -u root",
|
||||
"docker run --rm -e MYSQL_ROOT_PASSWORD=secret123 -e MYSQL_ROOT_HOST=% mysql:8.0 mysql -u root",
|
||||
@mysql.execute_in_new_container("mysql", "-u", "root").join(" ")
|
||||
end
|
||||
|
||||
@@ -90,7 +90,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
||||
|
||||
test "execute in new container over ssh" do
|
||||
@mysql.stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) do
|
||||
assert_match %r|docker run -it --rm -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" mysql:8.0 mysql -u root|,
|
||||
assert_match %r|docker run -it --rm -e MYSQL_ROOT_PASSWORD=secret123 -e MYSQL_ROOT_HOST=% mysql:8.0 mysql -u root|,
|
||||
@mysql.execute_in_new_container_over_ssh("mysql", "-u", "root")
|
||||
end
|
||||
end
|
||||
@@ -106,29 +106,29 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
||||
|
||||
test "logs" do
|
||||
assert_equal \
|
||||
"docker logs app-mysql --timestamps 2>&1",
|
||||
"docker logs app-mysql -t 2>&1",
|
||||
@mysql.logs.join(" ")
|
||||
|
||||
assert_equal \
|
||||
"docker logs app-mysql --since 5m --tail 100 --timestamps 2>&1 | grep 'thing'",
|
||||
"docker logs app-mysql --since 5m -n 100 -t 2>&1 | grep 'thing'",
|
||||
@mysql.logs(since: "5m", lines: 100, grep: "thing").join(" ")
|
||||
end
|
||||
|
||||
test "follow logs" do
|
||||
assert_equal \
|
||||
"ssh -t root@1.1.1.5 'docker logs app-mysql --timestamps --tail 10 --follow 2>&1'",
|
||||
"ssh -t root@1.1.1.5 'docker logs app-mysql -t -n 10 -f 2>&1'",
|
||||
@mysql.follow_logs
|
||||
end
|
||||
|
||||
test "remove container" do
|
||||
assert_equal \
|
||||
"docker container prune --force --filter label=service=app-mysql",
|
||||
"docker container prune -f --filter label=service=app-mysql",
|
||||
@mysql.remove_container.join(" ")
|
||||
end
|
||||
|
||||
test "remove image" do
|
||||
assert_equal \
|
||||
"docker image prune --all --force --filter label=service=app-mysql",
|
||||
"docker image prune -a -f --filter label=service=app-mysql",
|
||||
@mysql.remove_image.join(" ")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,7 +14,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
||||
|
||||
test "run" do
|
||||
assert_equal \
|
||||
"docker run --detach --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=\"/up\" --label traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app.retry.attempts=\"5\" --label traefik.http.middlewares.app.retry.initialinterval=\"500ms\" dhh/app:999",
|
||||
"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=/up --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
|
||||
|
||||
@@ -22,15 +22,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
||||
@config[:volumes] = ["/local/path:/container/path" ]
|
||||
|
||||
assert_equal \
|
||||
"docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-999 -e RAILS_MASTER_KEY=\"456\" --volume /local/path:/container/path --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app.loadbalancer.healthcheck.path=\"/up\" --label traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app.retry.attempts=\"5\" --label traefik.http.middlewares.app.retry.initialinterval=\"500ms\" dhh/app:999",
|
||||
@app.run.join(" ")
|
||||
end
|
||||
|
||||
test "run with custom healthcheck path" do
|
||||
@config[:healthcheck] = { "path" => "/healthz" }
|
||||
|
||||
assert_equal \
|
||||
"docker run --detach --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=\"5\" --label traefik.http.middlewares.app.retry.initialinterval=\"500ms\" dhh/app:999",
|
||||
"docker run -d --restart unless-stopped --log-opt max-size=10m --name app-999 -e RAILS_MASTER_KEY=456 --volume /local/path:/container/path --label service=app --label role=web --label traefik.http.routers.app.rule='PathPrefix(`/`)' --label traefik.http.services.app.loadbalancer.healthcheck.path=/up --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
|
||||
|
||||
@@ -42,7 +34,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
||||
|
||||
test "stop" do
|
||||
assert_equal \
|
||||
"docker ps --quiet --filter label=service=app | xargs docker stop",
|
||||
"docker ps -q --filter label=service=app | xargs docker stop",
|
||||
@app.stop.join(" ")
|
||||
end
|
||||
|
||||
@@ -55,38 +47,38 @@ class CommandsAppTest < ActiveSupport::TestCase
|
||||
|
||||
test "logs" do
|
||||
assert_equal \
|
||||
"docker ps --quiet --filter label=service=app | xargs docker logs 2>&1",
|
||||
"docker ps -q --filter label=service=app | xargs docker logs 2>&1",
|
||||
@app.logs.join(" ")
|
||||
|
||||
assert_equal \
|
||||
"docker ps --quiet --filter label=service=app | xargs docker logs --since 5m 2>&1",
|
||||
"docker ps -q --filter label=service=app | xargs docker logs --since 5m 2>&1",
|
||||
@app.logs(since: "5m").join(" ")
|
||||
|
||||
assert_equal \
|
||||
"docker ps --quiet --filter label=service=app | xargs docker logs --tail 100 2>&1",
|
||||
"docker ps -q --filter label=service=app | xargs docker logs -n 100 2>&1",
|
||||
@app.logs(lines: "100").join(" ")
|
||||
|
||||
assert_equal \
|
||||
"docker ps --quiet --filter label=service=app | xargs docker logs --since 5m --tail 100 2>&1",
|
||||
"docker ps -q --filter label=service=app | xargs docker logs --since 5m -n 100 2>&1",
|
||||
@app.logs(since: "5m", lines: "100").join(" ")
|
||||
|
||||
assert_equal \
|
||||
"docker ps --quiet --filter label=service=app | xargs docker logs 2>&1 | grep 'my-id'",
|
||||
"docker ps -q --filter label=service=app | xargs docker logs 2>&1 | grep 'my-id'",
|
||||
@app.logs(grep: "my-id").join(" ")
|
||||
|
||||
assert_equal \
|
||||
"docker ps --quiet --filter label=service=app | xargs docker logs --since 5m 2>&1 | grep 'my-id'",
|
||||
"docker ps -q --filter label=service=app | xargs docker logs --since 5m 2>&1 | grep 'my-id'",
|
||||
@app.logs(since: "5m", grep: "my-id").join(" ")
|
||||
end
|
||||
|
||||
test "follow logs" do
|
||||
@app.stub(:run_over_ssh, ->(cmd, host:) { cmd.join(" ") }) do
|
||||
assert_equal \
|
||||
"docker ps --quiet --filter label=service=app | xargs docker logs --timestamps --tail 10 --follow 2>&1",
|
||||
"docker ps -q --filter label=service=app | xargs docker logs -t -n 10 -f 2>&1",
|
||||
@app.follow_logs(host: "app-1")
|
||||
|
||||
assert_equal \
|
||||
"docker ps --quiet --filter label=service=app | xargs docker logs --timestamps --tail 10 --follow 2>&1 | grep \"Completed\"",
|
||||
"docker ps -q --filter label=service=app | xargs docker logs -t -n 10 -f 2>&1 | grep \"Completed\"",
|
||||
@app.follow_logs(host: "app-1", grep: "Completed")
|
||||
end
|
||||
end
|
||||
@@ -94,7 +86,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
||||
|
||||
test "execute in new container" do
|
||||
assert_equal \
|
||||
"docker run --rm -e RAILS_MASTER_KEY=\"456\" dhh/app:999 bin/rails db:setup",
|
||||
"docker run --rm -e RAILS_MASTER_KEY=456 dhh/app:999 bin/rails db:setup",
|
||||
@app.execute_in_new_container("bin/rails", "db:setup").join(" ")
|
||||
end
|
||||
|
||||
@@ -106,7 +98,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
||||
|
||||
test "execute in new container over ssh" do
|
||||
@app.stub(:run_over_ssh, ->(cmd, host:) { cmd.join(" ") }) do
|
||||
assert_match %r|docker run -it --rm -e RAILS_MASTER_KEY=\"456\" dhh/app:999 bin/rails c|,
|
||||
assert_match %r|docker run -it --rm -e RAILS_MASTER_KEY=456 dhh/app:999 bin/rails c|,
|
||||
@app.execute_in_new_container_over_ssh("bin/rails", "c", host: "app-1")
|
||||
end
|
||||
end
|
||||
@@ -145,13 +137,13 @@ class CommandsAppTest < ActiveSupport::TestCase
|
||||
|
||||
test "current_container_id" do
|
||||
assert_equal \
|
||||
"docker ps --quiet --filter label=service=app",
|
||||
"docker ps -q --filter label=service=app",
|
||||
@app.current_container_id.join(" ")
|
||||
end
|
||||
|
||||
test "container_id_for" do
|
||||
assert_equal \
|
||||
"docker container ls --all --filter name=app-999 --quiet",
|
||||
"docker container ls -a -f name=app-999 -q",
|
||||
@app.container_id_for(container_name: "app-999").join(" ")
|
||||
end
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase
|
||||
|
||||
test "broadcast" do
|
||||
assert_match \
|
||||
/bin\/audit_broadcast '\[.*\] app removed container'/,
|
||||
/echo '.* app removed container' \| bin\/audit_broadcast/,
|
||||
new_command.broadcast("app removed container").join(" ")
|
||||
end
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
builder = new_builder_command
|
||||
assert_equal "multiarch", builder.name
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --builder mrsk-app-multiarch -t dhh/app:123 -t dhh/app:latest --label service=\"app\" .",
|
||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --builder mrsk-app-multiarch -t dhh/app:123 -t dhh/app:latest --label service=app .",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
@@ -17,7 +17,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
builder = new_builder_command(builder: { "multiarch" => false })
|
||||
assert_equal "native", builder.name
|
||||
assert_equal \
|
||||
"docker build -t dhh/app:123 -t dhh/app:latest --label service=\"app\" . && docker push dhh/app:123",
|
||||
"docker build -t dhh/app:123 -t dhh/app:latest --label service=app . && docker push dhh/app:123",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
builder = new_builder_command(builder: { "local" => { }, "remote" => { } })
|
||||
assert_equal "multiarch/remote", builder.name
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --builder mrsk-app-multiarch-remote -t dhh/app:123 -t dhh/app:latest --label service=\"app\" .",
|
||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --builder mrsk-app-multiarch-remote -t dhh/app:123 -t dhh/app:latest --label service=app .",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
@@ -33,42 +33,42 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
builder = new_builder_command(builder: { "remote" => { "arch" => "amd64" } })
|
||||
assert_equal "native/remote", builder.name
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64 --builder mrsk-app-native-remote -t dhh/app:123 -t dhh/app:latest --label service=\"app\" .",
|
||||
"docker buildx build --push --platform linux/amd64 --builder mrsk-app-native-remote -t dhh/app:123 -t dhh/app:latest --label service=app .",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
test "build args" do
|
||||
builder = new_builder_command(builder: { "args" => { "a" => 1, "b" => 2 } })
|
||||
assert_equal \
|
||||
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\"",
|
||||
"-t dhh/app:123 -t dhh/app:latest --label service=app --build-arg a=1 --build-arg b=2",
|
||||
builder.target.build_options.join(" ")
|
||||
end
|
||||
|
||||
test "build secrets" do
|
||||
builder = new_builder_command(builder: { "secrets" => ["token_a", "token_b"] })
|
||||
assert_equal \
|
||||
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"token_a\" --secret id=\"token_b\"",
|
||||
"-t dhh/app:123 -t dhh/app:latest --label service=app --secret id=token_a --secret id=token_b",
|
||||
builder.target.build_options.join(" ")
|
||||
end
|
||||
|
||||
test "native push with build args" do
|
||||
builder = new_builder_command(builder: { "multiarch" => false, "args" => { "a" => 1, "b" => 2 } })
|
||||
assert_equal \
|
||||
"docker build -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" . && docker push dhh/app:123",
|
||||
"docker build -t dhh/app:123 -t dhh/app:latest --label service=app --build-arg a=1 --build-arg b=2 . && docker push dhh/app:123",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
test "multiarch push with build args" do
|
||||
builder = new_builder_command(builder: { "args" => { "a" => 1, "b" => 2 } })
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --builder mrsk-app-multiarch -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" .",
|
||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --builder mrsk-app-multiarch -t dhh/app:123 -t dhh/app:latest --label service=app --build-arg a=1 --build-arg b=2 .",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
test "native push with with build secrets" do
|
||||
builder = new_builder_command(builder: { "multiarch" => false, "secrets" => [ "a", "b" ] })
|
||||
assert_equal \
|
||||
"docker build -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"a\" --secret id=\"b\" . && docker push dhh/app:123",
|
||||
"docker build -t dhh/app:123 -t dhh/app:latest --label service=app --secret id=a --secret id=b . && docker push dhh/app:123",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
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 --detach --name healthcheck-app-123 --publish 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 --detach --name healthcheck-app-123 --publish 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}' --max-time 2 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}' --max-time 2 http://localhost:3999/healthz",
|
||||
new_command.curl.join(" ")
|
||||
end
|
||||
|
||||
test "stop" do
|
||||
assert_equal \
|
||||
"docker container ls --all --filter name=healthcheck-app --quiet | xargs docker stop",
|
||||
new_command.stop.join(" ")
|
||||
end
|
||||
|
||||
test "remove" do
|
||||
assert_equal \
|
||||
"docker container ls --all --filter name=healthcheck-app --quiet | 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
|
||||
@@ -14,25 +14,10 @@ class CommandsRegistryTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
test "registry login" do
|
||||
assert_equal \
|
||||
"docker login hub.docker.com -u dhh -p secret",
|
||||
@registry.login.join(" ")
|
||||
end
|
||||
|
||||
test "registry login with ENV password" do
|
||||
ENV["MRSK_REGISTRY_PASSWORD"] = "more-secret"
|
||||
@config[:registry]["password"] = [ "MRSK_REGISTRY_PASSWORD" ]
|
||||
|
||||
assert_equal \
|
||||
"docker login hub.docker.com -u dhh -p more-secret",
|
||||
@registry.login.join(" ")
|
||||
ensure
|
||||
ENV.delete("MRSK_REGISTRY_PASSWORD")
|
||||
assert_equal [ :docker, :login, "hub.docker.com", "-u", "dhh", "-p", "secret" ], @registry.login
|
||||
end
|
||||
|
||||
test "registry logout" do
|
||||
assert_equal \
|
||||
"docker logout hub.docker.com",
|
||||
@registry.logout.join(" ")
|
||||
assert_equal [:docker, :logout, "hub.docker.com"], @registry.logout
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ class CommandsTraefikTest < ActiveSupport::TestCase
|
||||
|
||||
test "run" do
|
||||
assert_equal \
|
||||
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock traefik --providers.docker --log.level=DEBUG --accesslog.format json --metrics.prometheus.buckets 0.1,0.3,1.2,5.0",
|
||||
"docker run --name traefik -d --restart unless-stopped --log-opt max-size=10m -p 80:80 -v /var/run/docker.sock:/var/run/docker.sock traefik --providers.docker --log.level=DEBUG --accesslog.format json --metrics.prometheus.buckets 0.1,0.3,1.2,5.0",
|
||||
new_command.run.join(" ")
|
||||
end
|
||||
|
||||
@@ -34,49 +34,49 @@ class CommandsTraefikTest < ActiveSupport::TestCase
|
||||
|
||||
test "traefik logs" do
|
||||
assert_equal \
|
||||
"docker logs traefik --timestamps 2>&1",
|
||||
"docker logs traefik -t 2>&1",
|
||||
new_command.logs.join(" ")
|
||||
end
|
||||
|
||||
test "traefik logs since 2h" do
|
||||
assert_equal \
|
||||
"docker logs traefik --since 2h --timestamps 2>&1",
|
||||
"docker logs traefik --since 2h -t 2>&1",
|
||||
new_command.logs(since: '2h').join(" ")
|
||||
end
|
||||
|
||||
test "traefik logs last 10 lines" do
|
||||
assert_equal \
|
||||
"docker logs traefik --tail 10 --timestamps 2>&1",
|
||||
"docker logs traefik -n 10 -t 2>&1",
|
||||
new_command.logs(lines: 10).join(" ")
|
||||
end
|
||||
|
||||
test "traefik logs with grep hello!" do
|
||||
assert_equal \
|
||||
"docker logs traefik --timestamps 2>&1 | grep 'hello!'",
|
||||
"docker logs traefik -t 2>&1 | grep 'hello!'",
|
||||
new_command.logs(grep: 'hello!').join(" ")
|
||||
end
|
||||
|
||||
test "traefik remove container" do
|
||||
assert_equal \
|
||||
"docker container prune --force --filter label=org.opencontainers.image.title=Traefik",
|
||||
"docker container prune -f --filter label=org.opencontainers.image.title=Traefik",
|
||||
new_command.remove_container.join(" ")
|
||||
end
|
||||
|
||||
test "traefik remove image" do
|
||||
assert_equal \
|
||||
"docker image prune --all --force --filter label=org.opencontainers.image.title=Traefik",
|
||||
"docker image prune -a -f --filter label=org.opencontainers.image.title=Traefik",
|
||||
new_command.remove_image.join(" ")
|
||||
end
|
||||
|
||||
test "traefik follow logs" do
|
||||
assert_equal \
|
||||
"ssh -t root@1.1.1.1 'docker logs traefik --timestamps --tail 10 --follow 2>&1'",
|
||||
"ssh -t root@1.1.1.1 'docker logs traefik -t -n 10 -f 2>&1'",
|
||||
new_command.follow_logs(host: @config[:servers].first)
|
||||
end
|
||||
|
||||
test "traefik follow logs with grep hello!" do
|
||||
assert_equal \
|
||||
"ssh -t root@1.1.1.1 'docker logs traefik --timestamps --tail 10 --follow 2>&1 | grep \"hello!\"'",
|
||||
"ssh -t root@1.1.1.1 'docker logs traefik -t -n 10 -f 2>&1 | grep \"hello!\"'",
|
||||
new_command.follow_logs(host: @config[:servers].first, grep: 'hello!')
|
||||
end
|
||||
|
||||
|
||||
@@ -72,20 +72,20 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
test "label args" do
|
||||
assert_equal ["--label", "service=\"app-mysql\""], @config.accessory(:mysql).label_args
|
||||
assert_equal ["--label", "service=\"app-redis\"", "--label", "cache=\"true\""], @config.accessory(:redis).label_args
|
||||
assert_equal ["--label", "service=app-mysql"], @config.accessory(:mysql).label_args
|
||||
assert_equal ["--label", "service=app-redis", "--label", "cache=true"], @config.accessory(:redis).label_args
|
||||
end
|
||||
|
||||
test "env args with secret" do
|
||||
ENV["MYSQL_ROOT_PASSWORD"] = "secret123"
|
||||
assert_equal ["-e", "MYSQL_ROOT_PASSWORD=\"secret123\"", "-e", "MYSQL_ROOT_HOST=\"%\""], @config.accessory(:mysql).env_args
|
||||
assert_equal ["-e", "MYSQL_ROOT_PASSWORD=secret123", "-e", "MYSQL_ROOT_HOST=%"], @config.accessory(:mysql).env_args
|
||||
assert @config.accessory(:mysql).env_args[1].is_a?(SSHKit::Redaction)
|
||||
ensure
|
||||
ENV["MYSQL_ROOT_PASSWORD"] = nil
|
||||
end
|
||||
|
||||
test "env args without secret" do
|
||||
assert_equal ["-e", "SOMETHING=\"else\""], @config.accessory(:redis).env_args
|
||||
assert_equal ["-e", "SOMETHING=else"], @config.accessory(:redis).env_args
|
||||
end
|
||||
|
||||
test "volume args" do
|
||||
|
||||
@@ -38,11 +38,11 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
test "label args" do
|
||||
assert_equal [ "--label", "service=\"app\"", "--label", "role=\"workers\"" ], @config_with_roles.role(:workers).label_args
|
||||
assert_equal [ "--label", "service=app", "--label", "role=workers" ], @config_with_roles.role(:workers).label_args
|
||||
end
|
||||
|
||||
test "special label args for web" do
|
||||
assert_equal [ "--label", "service=\"app\"", "--label", "role=\"web\"", "--label", "traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\"", "--label", "traefik.http.services.app.loadbalancer.healthcheck.path=\"/up\"", "--label", "traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\"", "--label", "traefik.http.middlewares.app.retry.attempts=\"5\"", "--label", "traefik.http.middlewares.app.retry.initialinterval=\"500ms\""], @config.role(:web).label_args
|
||||
assert_equal [ "--label", "service=app", "--label", "role=web", "--label", "traefik.http.routers.app.rule='PathPrefix(`/`)'", "--label", "traefik.http.services.app.loadbalancer.healthcheck.path=/up", "--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"], @config.role(:web).label_args
|
||||
end
|
||||
|
||||
test "custom labels" do
|
||||
@@ -57,8 +57,8 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
test "overwriting default traefik label" do
|
||||
@deploy[:labels] = { "traefik.http.routers.app.rule" => "\"Host(\\`example.com\\`) || (Host(\\`example.org\\`) && Path(\\`/traefik\\`))\"" }
|
||||
assert_equal "\"Host(\\`example.com\\`) || (Host(\\`example.org\\`) && Path(\\`/traefik\\`))\"", @config.role(:web).labels["traefik.http.routers.app.rule"]
|
||||
@deploy[:labels] = { "traefik.http.routers.app.rule" => "'Host(`example.com`) || (Host(`example.org`) && Path(`/traefik`))'" }
|
||||
assert_equal "'Host(`example.com`) || (Host(`example.org`) && Path(`/traefik`))'", @config.role(:web).labels["traefik.http.routers.app.rule"]
|
||||
end
|
||||
|
||||
test "default traefik label on non-web role" do
|
||||
@@ -66,12 +66,12 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
||||
c[:servers]["beta"] = { "traefik" => "true", "hosts" => [ "1.1.1.5" ] }
|
||||
})
|
||||
|
||||
assert_equal [ "--label", "service=\"app\"", "--label", "role=\"beta\"", "--label", "traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\"", "--label", "traefik.http.services.app.loadbalancer.healthcheck.path=\"/up\"", "--label", "traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\"", "--label", "traefik.http.middlewares.app.retry.attempts=\"5\"", "--label", "traefik.http.middlewares.app.retry.initialinterval=\"500ms\"" ], config.role(:beta).label_args
|
||||
assert_equal [ "--label", "service=app", "--label", "role=beta", "--label", "traefik.http.routers.app.rule='PathPrefix(`/`)'", "--label", "traefik.http.services.app.loadbalancer.healthcheck.path=/up", "--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" ], config.role(:beta).label_args
|
||||
end
|
||||
|
||||
test "env overwritten by role" do
|
||||
assert_equal "redis://a/b", @config_with_roles.role(:workers).env["REDIS_URL"]
|
||||
assert_equal ["-e", "REDIS_URL=\"redis://a/b\"", "-e", "WEB_CONCURRENCY=\"4\""], @config_with_roles.role(:workers).env_args
|
||||
assert_equal ["-e", "REDIS_URL=redis://a/b", "-e", "WEB_CONCURRENCY=4"], @config_with_roles.role(:workers).env_args
|
||||
end
|
||||
|
||||
test "env secret overwritten by role" do
|
||||
@@ -95,9 +95,9 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
||||
}
|
||||
|
||||
ENV["REDIS_PASSWORD"] = "secret456"
|
||||
ENV["DB_PASSWORD"] = "secret&\"123"
|
||||
ENV["DB_PASSWORD"] = "secret123"
|
||||
|
||||
assert_equal ["-e", "REDIS_PASSWORD=\"secret456\"", "-e", "DB_PASSWORD=\"secret&\\\"123\"", "-e", "REDIS_URL=\"redis://a/b\"", "-e", "WEB_CONCURRENCY=\"4\""], @config_with_roles.role(:workers).env_args
|
||||
assert_equal ["-e", "REDIS_PASSWORD=secret456", "-e", "DB_PASSWORD=secret123", "-e", "REDIS_URL=redis://a/b", "-e", "WEB_CONCURRENCY=4"], @config_with_roles.role(:workers).env_args
|
||||
ensure
|
||||
ENV["REDIS_PASSWORD"] = nil
|
||||
ENV["DB_PASSWORD"] = nil
|
||||
@@ -116,7 +116,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
||||
|
||||
ENV["DB_PASSWORD"] = "secret123"
|
||||
|
||||
assert_equal ["-e", "DB_PASSWORD=\"secret123\"", "-e", "REDIS_URL=\"redis://a/b\"", "-e", "WEB_CONCURRENCY=\"4\""], @config_with_roles.role(:workers).env_args
|
||||
assert_equal ["-e", "DB_PASSWORD=secret123", "-e", "REDIS_URL=redis://a/b", "-e", "WEB_CONCURRENCY=4"], @config_with_roles.role(:workers).env_args
|
||||
ensure
|
||||
ENV["DB_PASSWORD"] = nil
|
||||
end
|
||||
@@ -133,7 +133,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
||||
|
||||
ENV["REDIS_PASSWORD"] = "secret456"
|
||||
|
||||
assert_equal ["-e", "REDIS_PASSWORD=\"secret456\"", "-e", "REDIS_URL=\"redis://a/b\"", "-e", "WEB_CONCURRENCY=\"4\""], @config_with_roles.role(:workers).env_args
|
||||
assert_equal ["-e", "REDIS_PASSWORD=secret456", "-e", "REDIS_URL=redis://a/b", "-e", "WEB_CONCURRENCY=4"], @config_with_roles.role(:workers).env_args
|
||||
ensure
|
||||
ENV["REDIS_PASSWORD"] = nil
|
||||
end
|
||||
|
||||
@@ -89,7 +89,7 @@ class ConfigurationTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
test "env args" do
|
||||
assert_equal [ "-e", "REDIS_URL=\"redis://x/y\"" ], @config.env_args
|
||||
assert_equal [ "-e", "REDIS_URL=redis://x/y" ], @config.env_args
|
||||
end
|
||||
|
||||
test "env args with clear and secrets" do
|
||||
@@ -98,7 +98,7 @@ class ConfigurationTest < ActiveSupport::TestCase
|
||||
env: { "clear" => { "PORT" => "3000" }, "secret" => [ "PASSWORD" ] }
|
||||
}) })
|
||||
|
||||
assert_equal [ "-e", "PASSWORD=\"secret123\"", "-e", "PORT=\"3000\"" ], config.env_args
|
||||
assert_equal [ "-e", "PASSWORD=secret123", "-e", "PORT=3000" ], config.env_args
|
||||
assert config.env_args[1].is_a?(SSHKit::Redaction)
|
||||
ensure
|
||||
ENV["PASSWORD"] = nil
|
||||
@@ -109,7 +109,7 @@ class ConfigurationTest < ActiveSupport::TestCase
|
||||
env: { "clear" => { "PORT" => "3000" } }
|
||||
}) })
|
||||
|
||||
assert_equal [ "-e", "PORT=\"3000\"" ], config.env_args
|
||||
assert_equal [ "-e", "PORT=3000" ], config.env_args
|
||||
end
|
||||
|
||||
test "env args with only secrets" do
|
||||
@@ -118,7 +118,7 @@ class ConfigurationTest < ActiveSupport::TestCase
|
||||
env: { "secret" => [ "PASSWORD" ] }
|
||||
}) })
|
||||
|
||||
assert_equal [ "-e", "PASSWORD=\"secret123\"" ], config.env_args
|
||||
assert_equal [ "-e", "PASSWORD=secret123" ], config.env_args
|
||||
assert config.env_args[1].is_a?(SSHKit::Redaction)
|
||||
ensure
|
||||
ENV["PASSWORD"] = nil
|
||||
@@ -181,6 +181,6 @@ class ConfigurationTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
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"], :healthcheck=>{"path"=>"/up", "port"=>3000 }}, @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"] }, @config.to_h)
|
||||
end
|
||||
end
|
||||
|
||||
2
test/fixtures/deploy_with_accessories.yml
vendored
2
test/fixtures/deploy_with_accessories.yml
vendored
@@ -27,5 +27,3 @@ accessories:
|
||||
port: 6379
|
||||
directories:
|
||||
- data:/data
|
||||
|
||||
readiness_delay: 0
|
||||
Reference in New Issue
Block a user