Merge branch 'main' into cleanup-excessive-containers-running
* main: (24 commits) Bump version for 0.11.0 Labels can be added to Traefik Make rollbacks role-aware fix typo role to roles Explained the latest modifications of Traefik container labels Remove .idea folder Updated README.md with new healthcheck.max_attempts option Fix test case: console output message was not updated to display the current/total attempts Require net-ssh ~> 7.0 for SHA-2 support Improved deploy lock acquisition Excess CR Style Simpler Make it explicit, focus on Ubuntu More explicit Not that --bundle is a Rails 7+ option Update README.md Update README.md Improved: configurable max_attempts for healthcheck Traefik service name to be derived from role and destination ...
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
mrsk (0.10.1)
|
mrsk (0.11.0)
|
||||||
activesupport (>= 7.0)
|
activesupport (>= 7.0)
|
||||||
bcrypt_pbkdf (~> 1.0)
|
bcrypt_pbkdf (~> 1.0)
|
||||||
dotenv (~> 2.8)
|
dotenv (~> 2.8)
|
||||||
ed25519 (~> 1.2)
|
ed25519 (~> 1.2)
|
||||||
|
net-ssh (~> 7.0)
|
||||||
sshkit (~> 1.21)
|
sshkit (~> 1.21)
|
||||||
thor (~> 1.2)
|
thor (~> 1.2)
|
||||||
zeitwerk (~> 2.5)
|
zeitwerk (~> 2.5)
|
||||||
|
|||||||
87
README.md
87
README.md
@@ -6,6 +6,8 @@ Watch the screencast: https://www.youtube.com/watch?v=LL1cV2FXZ5I
|
|||||||
|
|
||||||
Join us on Discord: https://discord.gg/YgHVT7GCXS
|
Join us on Discord: https://discord.gg/YgHVT7GCXS
|
||||||
|
|
||||||
|
Ask questions: https://github.com/mrsked/mrsk/discussions
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
If you have a Ruby environment available, you can install MRSK globally with:
|
If you have a Ruby environment available, you can install MRSK globally with:
|
||||||
@@ -14,13 +16,13 @@ If you have a Ruby environment available, you can install MRSK globally with:
|
|||||||
gem install mrsk
|
gem install mrsk
|
||||||
```
|
```
|
||||||
|
|
||||||
...otherwise, you can run a dockerized version via an alias (add this to your ${SHELL}rc to simplify re-use):
|
...otherwise, you can run a dockerized version via an alias (add this to your .bashrc or similar to simplify re-use):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
alias mrsk='docker run --rm -it -v $HOME/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock -v ${PWD}/:/workdir ghcr.io/mrsked/mrsk'
|
alias mrsk='docker run --rm -it -v $HOME/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock -v ${PWD}/:/workdir ghcr.io/mrsked/mrsk'
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, inside your app directory, run `mrsk init` (or `mrsk init --bundle` within Rails apps where you want a bin/mrsk binstub). Now edit the new file `config/deploy.yml`. It could look as simple as this:
|
Then, inside your app directory, run `mrsk init` (or `mrsk init --bundle` within Rails 7+ apps where you want a bin/mrsk binstub). Now edit the new file `config/deploy.yml`. It could look as simple as this:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
service: hey
|
service: hey
|
||||||
@@ -191,6 +193,15 @@ ssh:
|
|||||||
user: app
|
user: app
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you are using non-root user, you need to bootstrap your servers manually, before using them with MRSK. On Ubuntu, you'd do:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt upgrade -y
|
||||||
|
sudo apt install -y docker.io curl git
|
||||||
|
sudo usermod -a -G docker ubuntu
|
||||||
|
```
|
||||||
|
|
||||||
### Using a proxy SSH host
|
### Using a proxy SSH host
|
||||||
|
|
||||||
If you need to connect to server through a proxy host, you can use `ssh/proxy`:
|
If you need to connect to server through a proxy host, you can use `ssh/proxy`:
|
||||||
@@ -207,6 +218,13 @@ ssh:
|
|||||||
proxy: "app@192.168.0.1"
|
proxy: "app@192.168.0.1"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Also if you need specific proxy command to connect to the server:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ssh:
|
||||||
|
proxy_command: aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p' --region=us-east-1 ## ssh via aws ssm
|
||||||
|
```
|
||||||
|
|
||||||
### Using env variables
|
### Using env variables
|
||||||
|
|
||||||
You can inject env variables into the app containers using `env`:
|
You can inject env variables into the app containers using `env`:
|
||||||
@@ -288,8 +306,9 @@ You can specialize the default Traefik rules by setting labels on the containers
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
labels:
|
labels:
|
||||||
traefik.http.routers.hey.rule: Host(`app.hey.com`)
|
traefik.http.routers.hey-web.rule: Host(`app.hey.com`)
|
||||||
```
|
```
|
||||||
|
Traefik rules are in the "service-role-destination" format. The default role will be `web` if no rule is specified. If the destination is not specified, it is not included. To give an example, the above rule would become "traefik.http.routers.hey-web.rule" if it was for the "staging" destination.
|
||||||
|
|
||||||
Note: The backticks are needed to ensure the rule is passed in correctly and not treated as command substitution by Bash!
|
Note: The backticks are needed to ensure the rule is passed in correctly and not treated as command substitution by Bash!
|
||||||
|
|
||||||
@@ -439,9 +458,9 @@ RUN --mount=type=secret,id=GITHUB_TOKEN \
|
|||||||
rm -rf /usr/local/bundle/cache
|
rm -rf /usr/local/bundle/cache
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using command arguments for Traefik
|
### Traefik command arguments
|
||||||
|
|
||||||
You can customize the traefik command line:
|
Customize the Traefik command line using `args`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
traefik:
|
traefik:
|
||||||
@@ -450,20 +469,38 @@ traefik:
|
|||||||
accesslog.format: json
|
accesslog.format: json
|
||||||
```
|
```
|
||||||
|
|
||||||
This will start the traefik container with `--accesslog=true accesslog.format=json`.
|
This starts the Traefik container with `--accesslog=true --accesslog.format=json` arguments.
|
||||||
|
|
||||||
### Traefik's host port binding
|
### Traefik host port binding
|
||||||
|
|
||||||
By default Traefik binds to port 80 of the host machine, it can be configured to use an alternative port:
|
Traefik binds to port 80 by default. Specify an alternative port using `host_port`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
traefik:
|
traefik:
|
||||||
host_port: 8080
|
host_port: 8080
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configure docker options for traefik
|
### Traefik version, upgrades, and custom images
|
||||||
|
|
||||||
We allow users to pass additional docker options to the trafik container like
|
MRSK runs the traefik:v2.9 image to track Traefik 2.9.x releases.
|
||||||
|
|
||||||
|
To pin Traefik to a specific version or an image published to your registry,
|
||||||
|
specify `image`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
traefik:
|
||||||
|
image: traefik:v2.10.0-rc1
|
||||||
|
```
|
||||||
|
|
||||||
|
This is useful for downgrading Traefik if there's an unexpected breaking
|
||||||
|
change in a minor version release, upgrading Traefik to test forthcoming
|
||||||
|
releases, or running your own Traefik-derived image.
|
||||||
|
|
||||||
|
MRSK has not been tested for compatibility with Traefik 3 betas. Please do!
|
||||||
|
|
||||||
|
### Traefik container configuration
|
||||||
|
|
||||||
|
Pass additional Docker configuration for the Traefik container using `options`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
traefik:
|
traefik:
|
||||||
@@ -475,12 +512,27 @@ traefik:
|
|||||||
memory: 512m
|
memory: 512m
|
||||||
```
|
```
|
||||||
|
|
||||||
This will start the traefik container with a command like: `docker run ... --volume /tmp/example.json:/tmp/example.json --publish 8080:8080 `
|
This starts the Traefik container with `--volume /tmp/example.json:/tmp/example.json --publish 8080:8080 --memory 512m` arguments to `docker run`.
|
||||||
|
|
||||||
|
### Traefik container lables
|
||||||
|
|
||||||
### Configure alternate entrypoints for traefik
|
Add labels to Traefik Docker container.
|
||||||
|
|
||||||
You can configure multiple entrypoints for traefik like so:
|
```yaml
|
||||||
|
traefik:
|
||||||
|
lables:
|
||||||
|
- traefik.enable: true
|
||||||
|
- traefik.http.routers.dashboard.rule: Host(`traefik.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))
|
||||||
|
- traefik.http.routers.dashboard.service: api@internal
|
||||||
|
- traefik.http.routers.dashboard.middlewares: auth
|
||||||
|
- traefik.http.middlewares.auth.basicauth.users: test:$2y$05$H2o72tMaO.TwY1wNQUV1K.fhjRgLHRDWohFvUZOJHBEtUXNKrqUKi # test:password
|
||||||
|
```
|
||||||
|
|
||||||
|
This labels Traefik container with `--label traefik.http.routers.dashboard.middlewares=\"auth\"` and so on.
|
||||||
|
|
||||||
|
### Traefik alternate entrypoints
|
||||||
|
|
||||||
|
You can configure multiple entrypoints for Traefik like so:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
service: myservice
|
service: myservice
|
||||||
@@ -540,7 +592,7 @@ accessories:
|
|||||||
memory: "2GB"
|
memory: "2GB"
|
||||||
redis:
|
redis:
|
||||||
image: redis:latest
|
image: redis:latest
|
||||||
role:
|
roles:
|
||||||
- web
|
- web
|
||||||
port: "36379:6379"
|
port: "36379:6379"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -610,18 +662,21 @@ That'll post a line like follows to a preconfigured chatbot in Basecamp:
|
|||||||
[My App] [dhh] Rolled back to version d264c4e92470ad1bd18590f04466787262f605de
|
[My App] [dhh] Rolled back to version d264c4e92470ad1bd18590f04466787262f605de
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using custom healthcheck path or port
|
### Custom healthcheck
|
||||||
|
|
||||||
MRSK defaults to checking the health of your application again `/up` on port 3000. You can tailor both with the `healthcheck` setting:
|
MRSK defaults to checking the health of your application again `/up` on port 3000 up to 7 times. You can tailor the behaviour with the `healthcheck` setting:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
healthcheck:
|
healthcheck:
|
||||||
path: /healthz
|
path: /healthz
|
||||||
port: 4000
|
port: 4000
|
||||||
|
max_attempts: 7
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
The healthcheck also allows for an optional `max_attempts` setting, which will attempt the healthcheck up to the specified number of times before failing the deploy. This is useful for applications that take a while to start up. The default is 7.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
### Running commands on servers
|
### Running commands on servers
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
module Mrsk::Cli
|
module Mrsk::Cli
|
||||||
|
class LockError < StandardError; end
|
||||||
end
|
end
|
||||||
|
|
||||||
# SSHKit uses instance eval, so we need a global const for ergonomics
|
# SSHKit uses instance eval, so we need a global const for ergonomics
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ module Mrsk::Cli
|
|||||||
class Base < Thor
|
class Base < Thor
|
||||||
include SSHKit::DSL
|
include SSHKit::DSL
|
||||||
|
|
||||||
class LockError < StandardError; end
|
|
||||||
|
|
||||||
def self.exit_on_failure?() true end
|
def self.exit_on_failure?() true end
|
||||||
|
|
||||||
class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
|
class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
|
||||||
@@ -82,8 +80,11 @@ module Mrsk::Cli
|
|||||||
acquire_lock
|
acquire_lock
|
||||||
|
|
||||||
yield
|
yield
|
||||||
ensure
|
|
||||||
release_lock
|
release_lock
|
||||||
|
rescue
|
||||||
|
error " \e[31mDeploy lock was not released\e[0m" if MRSK.lock_count > 0
|
||||||
|
raise
|
||||||
end
|
end
|
||||||
|
|
||||||
def acquire_lock
|
def acquire_lock
|
||||||
@@ -95,9 +96,10 @@ module Mrsk::Cli
|
|||||||
rescue SSHKit::Runner::ExecuteError => e
|
rescue SSHKit::Runner::ExecuteError => e
|
||||||
if e.message =~ /cannot create directory/
|
if e.message =~ /cannot create directory/
|
||||||
invoke "mrsk:cli:lock:status", []
|
invoke "mrsk:cli:lock:status", []
|
||||||
|
raise LockError, "Deploy lock found"
|
||||||
|
else
|
||||||
|
raise e
|
||||||
end
|
end
|
||||||
|
|
||||||
raise LockError, "Deploy lock found"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def release_lock
|
def release_lock
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
|
class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
|
||||||
MAX_ATTEMPTS = 7
|
|
||||||
|
|
||||||
class HealthcheckError < StandardError; end
|
class HealthcheckError < StandardError; end
|
||||||
|
|
||||||
@@ -13,6 +12,7 @@ class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
|
|||||||
|
|
||||||
target = "Health check against #{MRSK.config.healthcheck["path"]}"
|
target = "Health check against #{MRSK.config.healthcheck["path"]}"
|
||||||
attempt = 1
|
attempt = 1
|
||||||
|
max_attempts = MRSK.config.healthcheck["max_attempts"]
|
||||||
|
|
||||||
begin
|
begin
|
||||||
status = capture_with_info(*MRSK.healthcheck.curl)
|
status = capture_with_info(*MRSK.healthcheck.curl)
|
||||||
@@ -23,8 +23,8 @@ class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
|
|||||||
raise HealthcheckError, "#{target} failed with status #{status}"
|
raise HealthcheckError, "#{target} failed with status #{status}"
|
||||||
end
|
end
|
||||||
rescue SSHKit::Command::Failed
|
rescue SSHKit::Command::Failed
|
||||||
if attempt <= MAX_ATTEMPTS
|
if attempt <= max_attempts
|
||||||
info "#{target} failed to respond, retrying in #{attempt}s..."
|
info "#{target} failed to respond, retrying in #{attempt}s (attempt #{attempt}/#{max_attempts})..."
|
||||||
sleep attempt
|
sleep attempt
|
||||||
attempt += 1
|
attempt += 1
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class Mrsk::Cli::Lock < Mrsk::Cli::Base
|
|||||||
message = options[:message]
|
message = options[:message]
|
||||||
handle_missing_lock do
|
handle_missing_lock do
|
||||||
on(MRSK.primary_host) { execute *MRSK.lock.acquire(message, MRSK.config.version) }
|
on(MRSK.primary_host) { execute *MRSK.lock.acquire(message, MRSK.config.version) }
|
||||||
say "Set the deploy lock"
|
say "Acquired the deploy lock"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ class Mrsk::Cli::Lock < Mrsk::Cli::Base
|
|||||||
def release
|
def release
|
||||||
handle_missing_lock do
|
handle_missing_lock do
|
||||||
on(MRSK.primary_host) { execute *MRSK.lock.release }
|
on(MRSK.primary_host) { execute *MRSK.lock.release }
|
||||||
say "Removed the deploy lock"
|
say "Released the deploy lock"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -83,21 +83,26 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|||||||
with_lock do
|
with_lock do
|
||||||
MRSK.config.version = version
|
MRSK.config.version = version
|
||||||
|
|
||||||
if container_name_available?(MRSK.config.service_with_version)
|
if container_available?(version)
|
||||||
say "Start version #{version}, then wait #{MRSK.config.readiness_delay}s for app to boot before stopping the old version...", :magenta
|
say "Start version #{version}, then wait #{MRSK.config.readiness_delay}s for app to boot before stopping the old version...", :magenta
|
||||||
|
|
||||||
cli = self
|
cli = self
|
||||||
old_version = nil
|
old_version = nil
|
||||||
|
|
||||||
on(MRSK.hosts) do |host|
|
on(MRSK.hosts) do |host|
|
||||||
old_version = capture_with_info(*MRSK.app.current_running_version).strip.presence
|
roles = MRSK.roles_on(host)
|
||||||
|
|
||||||
execute *MRSK.app.start
|
roles.each do |role|
|
||||||
|
app = MRSK.app(role: role)
|
||||||
|
old_version = capture_with_info(*app.current_running_version).strip.presence
|
||||||
|
|
||||||
if old_version
|
execute *app.start
|
||||||
sleep MRSK.config.readiness_delay
|
|
||||||
|
|
||||||
execute *MRSK.app.stop(version: old_version), raise_on_non_zero_exit: false
|
if old_version
|
||||||
|
sleep MRSK.config.readiness_delay
|
||||||
|
|
||||||
|
execute *app.stop(version: old_version), raise_on_non_zero_exit: false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -220,10 +225,15 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|||||||
subcommand "lock", Mrsk::Cli::Lock
|
subcommand "lock", Mrsk::Cli::Lock
|
||||||
|
|
||||||
private
|
private
|
||||||
def container_name_available?(container_name, host: MRSK.primary_host)
|
def container_available?(version, host: MRSK.primary_host)
|
||||||
container_names = nil
|
available = nil
|
||||||
on(host) { container_names = capture_with_info(*MRSK.app.list_container_names).split("\n") }
|
|
||||||
Array(container_names).include?(container_name)
|
on(host) do
|
||||||
|
first_role = MRSK.roles_on(host).first
|
||||||
|
available = capture_with_info(*MRSK.app(role: first_role).container_id_for_version(version)).present?
|
||||||
|
end
|
||||||
|
|
||||||
|
available
|
||||||
end
|
end
|
||||||
|
|
||||||
def deploy_options
|
def deploy_options
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
|
|||||||
desc "boot", "Boot Traefik on servers"
|
desc "boot", "Boot Traefik on servers"
|
||||||
def boot
|
def boot
|
||||||
with_lock do
|
with_lock do
|
||||||
on(MRSK.traefik_hosts) { execute *MRSK.traefik.run, raise_on_non_zero_exit: false }
|
on(MRSK.traefik_hosts) do
|
||||||
|
execute *MRSK.registry.login
|
||||||
|
execute *MRSK.traefik.run, raise_on_non_zero_exit: false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
||||||
delegate :optionize, to: Mrsk::Utils
|
delegate :argumentize, :optionize, to: Mrsk::Utils
|
||||||
|
|
||||||
IMAGE = "traefik:v2.9.9"
|
DEFAULT_IMAGE = "traefik:v2.9"
|
||||||
CONTAINER_PORT = 80
|
CONTAINER_PORT = 80
|
||||||
|
|
||||||
def run
|
def run
|
||||||
@@ -11,8 +11,9 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|||||||
"--publish", port,
|
"--publish", port,
|
||||||
"--volume", "/var/run/docker.sock:/var/run/docker.sock",
|
"--volume", "/var/run/docker.sock:/var/run/docker.sock",
|
||||||
*config.logging_args,
|
*config.logging_args,
|
||||||
|
*label_args,
|
||||||
*docker_options_args,
|
*docker_options_args,
|
||||||
IMAGE,
|
image,
|
||||||
"--providers.docker",
|
"--providers.docker",
|
||||||
"--log.level=DEBUG",
|
"--log.level=DEBUG",
|
||||||
*cmd_option_args
|
*cmd_option_args
|
||||||
@@ -56,6 +57,18 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
def label_args
|
||||||
|
argumentize "--label", labels
|
||||||
|
end
|
||||||
|
|
||||||
|
def labels
|
||||||
|
config.traefik["labels"] || []
|
||||||
|
end
|
||||||
|
|
||||||
|
def image
|
||||||
|
config.traefik.fetch("image") { DEFAULT_IMAGE }
|
||||||
|
end
|
||||||
|
|
||||||
def docker_options_args
|
def docker_options_args
|
||||||
optionize(config.traefik["options"] || {})
|
optionize(config.traefik["options"] || {})
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -143,6 +143,8 @@ class Mrsk::Configuration
|
|||||||
if raw_config.ssh.present? && raw_config.ssh["proxy"]
|
if raw_config.ssh.present? && raw_config.ssh["proxy"]
|
||||||
Net::SSH::Proxy::Jump.new \
|
Net::SSH::Proxy::Jump.new \
|
||||||
raw_config.ssh["proxy"].include?("@") ? raw_config.ssh["proxy"] : "root@#{raw_config.ssh["proxy"]}"
|
raw_config.ssh["proxy"].include?("@") ? raw_config.ssh["proxy"] : "root@#{raw_config.ssh["proxy"]}"
|
||||||
|
elsif raw_config.ssh.present? && raw_config.ssh["proxy_command"]
|
||||||
|
Net::SSH::Proxy::Command.new(raw_config.ssh["proxy_command"])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -156,7 +158,7 @@ class Mrsk::Configuration
|
|||||||
end
|
end
|
||||||
|
|
||||||
def healthcheck
|
def healthcheck
|
||||||
{ "path" => "/up", "port" => 3000 }.merge(raw_config.healthcheck || {})
|
{ "path" => "/up", "port" => 3000, "max_attempts" => 7 }.merge(raw_config.healthcheck || {})
|
||||||
end
|
end
|
||||||
|
|
||||||
def readiness_delay
|
def readiness_delay
|
||||||
|
|||||||
@@ -74,18 +74,22 @@ class Mrsk::Configuration::Role
|
|||||||
def traefik_labels
|
def traefik_labels
|
||||||
if running_traefik?
|
if running_traefik?
|
||||||
{
|
{
|
||||||
"traefik.http.routers.#{config.service}.rule" => "PathPrefix(`/`)",
|
"traefik.http.routers.#{traefik_service}.rule" => "PathPrefix(`/`)",
|
||||||
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => config.healthcheck["path"],
|
"traefik.http.services.#{traefik_service}.loadbalancer.healthcheck.path" => config.healthcheck["path"],
|
||||||
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.interval" => "1s",
|
"traefik.http.services.#{traefik_service}.loadbalancer.healthcheck.interval" => "1s",
|
||||||
"traefik.http.middlewares.#{config.service}-retry.retry.attempts" => "5",
|
"traefik.http.middlewares.#{traefik_service}-retry.retry.attempts" => "5",
|
||||||
"traefik.http.middlewares.#{config.service}-retry.retry.initialinterval" => "500ms",
|
"traefik.http.middlewares.#{traefik_service}-retry.retry.initialinterval" => "500ms",
|
||||||
"traefik.http.routers.#{config.service}.middlewares" => "#{config.service}-retry@docker"
|
"traefik.http.routers.#{traefik_service}.middlewares" => "#{traefik_service}-retry@docker"
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{}
|
{}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def traefik_service
|
||||||
|
[ config.service, name, config.destination ].compact.join("-")
|
||||||
|
end
|
||||||
|
|
||||||
def custom_labels
|
def custom_labels
|
||||||
Hash.new.tap do |labels|
|
Hash.new.tap do |labels|
|
||||||
labels.merge!(config.labels) if config.labels.present?
|
labels.merge!(config.labels) if config.labels.present?
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module Mrsk
|
module Mrsk
|
||||||
VERSION = "0.10.1"
|
VERSION = "0.11.0"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Gem::Specification.new do |spec|
|
|||||||
|
|
||||||
spec.add_dependency "activesupport", ">= 7.0"
|
spec.add_dependency "activesupport", ">= 7.0"
|
||||||
spec.add_dependency "sshkit", "~> 1.21"
|
spec.add_dependency "sshkit", "~> 1.21"
|
||||||
|
spec.add_dependency "net-ssh", "~> 7.0"
|
||||||
spec.add_dependency "thor", "~> 1.2"
|
spec.add_dependency "thor", "~> 1.2"
|
||||||
spec.add_dependency "dotenv", "~> 2.8"
|
spec.add_dependency "dotenv", "~> 2.8"
|
||||||
spec.add_dependency "zeitwerk", "~> 2.5"
|
spec.add_dependency "zeitwerk", "~> 2.5"
|
||||||
|
|||||||
@@ -22,4 +22,8 @@ class CliTestCase < ActiveSupport::TestCase
|
|||||||
def stdouted
|
def stdouted
|
||||||
capture(:stdout) { yield }.strip
|
capture(:stdout) { yield }.strip
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
def stderred
|
||||||
|
capture(:stderr) { yield }.strip
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ class CliHealthcheckTest < CliTestCase
|
|||||||
.returns("200")
|
.returns("200")
|
||||||
|
|
||||||
run_command("perform").tap do |output|
|
run_command("perform").tap do |output|
|
||||||
assert_match "Health check against /up failed to respond, retrying in 1s...", output
|
assert_match "Health check against /up failed to respond, retrying in 1s (attempt 1/7)...", output
|
||||||
assert_match "Health check against /up failed to respond, retrying in 2s...", output
|
assert_match "Health check against /up failed to respond, retrying in 2s (attempt 2/7)...", output
|
||||||
assert_match "Health check against /up succeeded with 200 OK!", output
|
assert_match "Health check against /up succeeded with 200 OK!", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -57,6 +57,46 @@ class CliMainTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "deploy when locked" do
|
||||||
|
Thread.report_on_exception = false
|
||||||
|
|
||||||
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
|
.with { |*arg| arg[0..1] == [:mkdir, :mrsk_lock] }
|
||||||
|
.raises(RuntimeError, "mkdir: cannot create directory ‘mrsk_lock’: File exists")
|
||||||
|
|
||||||
|
Mrsk::Cli::Base.any_instance.expects(:invoke).with("mrsk:cli:lock:status", [])
|
||||||
|
|
||||||
|
assert_raises(Mrsk::Cli::LockError) do
|
||||||
|
run_command("deploy")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deploy error when locking" do
|
||||||
|
Thread.report_on_exception = false
|
||||||
|
|
||||||
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
|
.with { |*arg| arg[0..1] == [:mkdir, :mrsk_lock] }
|
||||||
|
.raises(SocketError, "getaddrinfo: nodename nor servname provided, or not known")
|
||||||
|
|
||||||
|
assert_raises(SSHKit::Runner::ExecuteError) do
|
||||||
|
run_command("deploy")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deploy errors leave lock in place" do
|
||||||
|
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "skip_broadcast" => false, "version" => "999" }
|
||||||
|
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke)
|
||||||
|
.with("mrsk:cli:server:bootstrap", [], invoke_options)
|
||||||
|
.raises(RuntimeError)
|
||||||
|
|
||||||
|
assert_equal 0, MRSK.lock_count
|
||||||
|
assert_raises(RuntimeError) do
|
||||||
|
stderred { run_command("deploy") }
|
||||||
|
end
|
||||||
|
assert_equal 1, MRSK.lock_count
|
||||||
|
end
|
||||||
|
|
||||||
test "redeploy" do
|
test "redeploy" do
|
||||||
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "skip_broadcast" => false, "version" => "999" }
|
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "skip_broadcast" => false, "version" => "999" }
|
||||||
|
|
||||||
@@ -86,32 +126,34 @@ class CliMainTest < CliTestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "rollback bad version" do
|
test "rollback bad version" do
|
||||||
|
# Mrsk::Cli::Main.any_instance.stubs(:container_available?).returns(false)
|
||||||
run_command("details") # Preheat MRSK const
|
run_command("details") # Preheat MRSK const
|
||||||
|
|
||||||
run_command("rollback", "nonsense").tap do |output|
|
run_command("rollback", "nonsense").tap do |output|
|
||||||
assert_match /docker container ls --all --filter label=service=app --format '{{ .Names }}'/, output
|
assert_match /docker container ls --all --filter name=\^app-web-nonsense\$ --quiet/, output
|
||||||
assert_match /The app version 'nonsense' is not available as a container/, output
|
assert_match /The app version 'nonsense' is not available as a container/, output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "rollback good version" do
|
test "rollback good version" do
|
||||||
Mrsk::Cli::Main.any_instance.stubs(:container_name_available?).returns(true)
|
Mrsk::Cli::Main.any_instance.stubs(:container_available?).returns(true)
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info).with(:docker, :ps, "--filter", "label=service=app", "--filter", "status=running", "--latest", "--format", "\"{{.Names}}\"", "|", "grep -oE \"\\-[^-]+$\"", "|", "cut -c 2-").returns("version-to-rollback\n").at_least_once
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info).with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=web", "--filter", "status=running", "--latest", "--format", "\"{{.Names}}\"", "|", "grep -oE \"\\-[^-]+$\"", "|", "cut -c 2-").returns("version-to-rollback\n").at_least_once
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info).with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=workers", "--filter", "status=running", "--latest", "--format", "\"{{.Names}}\"", "|", "grep -oE \"\\-[^-]+$\"", "|", "cut -c 2-").returns("version-to-rollback\n").at_least_once
|
||||||
|
|
||||||
run_command("rollback", "123", config_file: "deploy_with_accessories").tap do |output|
|
run_command("rollback", "123", config_file: "deploy_with_accessories").tap do |output|
|
||||||
assert_match "Start version 123", output
|
assert_match "Start version 123", output
|
||||||
assert_match "docker start app-123", output
|
assert_match "docker start app-web-123", output
|
||||||
assert_match "docker container ls --all --filter name=^app-version-to-rollback$ --quiet | xargs docker stop", output, "Should stop the container that was previously running"
|
assert_match "docker container ls --all --filter name=^app-web-version-to-rollback$ --quiet | xargs docker stop", output, "Should stop the container that was previously running"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "rollback without old version" do
|
test "rollback without old version" do
|
||||||
Mrsk::Cli::Main.any_instance.stubs(:container_name_available?).returns(true)
|
Mrsk::Cli::Main.any_instance.stubs(:container_available?).returns(true)
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info).with(:docker, :ps, "--filter", "label=service=app", "--filter", "status=running", "--latest", "--format", "\"{{.Names}}\"", "|", "grep -oE \"\\-[^-]+$\"", "|", "cut -c 2-").returns("").at_least_once
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info).with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=web", "--filter", "status=running", "--latest", "--format", "\"{{.Names}}\"", "|", "grep -oE \"\\-[^-]+$\"", "|", "cut -c 2-").returns("").at_least_once
|
||||||
|
|
||||||
run_command("rollback", "123").tap do |output|
|
run_command("rollback", "123").tap do |output|
|
||||||
assert_match "Start version 123", output
|
assert_match "Start version 123", output
|
||||||
assert_match "docker start app-123", output
|
assert_match "docker start app-web-123", output
|
||||||
assert_no_match "docker stop", output
|
assert_no_match "docker stop", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ require_relative "cli_test_case"
|
|||||||
class CliTraefikTest < CliTestCase
|
class CliTraefikTest < CliTestCase
|
||||||
test "boot" do
|
test "boot" do
|
||||||
run_command("boot").tap do |output|
|
run_command("boot").tap do |output|
|
||||||
assert_match "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" traefik:v2.9.9 --providers.docker --log.level=DEBUG", output
|
assert_match "docker login", output
|
||||||
|
assert_match "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" #{Mrsk::Commands::Traefik::DEFAULT_IMAGE} --providers.docker --log.level=DEBUG", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "run" do
|
test "run" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --name app-web-999 -e MRSK_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --log-opt max-size=\"10m\" --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.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999",
|
"docker run --detach --restart unless-stopped --name app-web-999 -e MRSK_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app-web.loadbalancer.healthcheck.path=\"/up\" --label traefik.http.services.app-web.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
@config[:volumes] = ["/local/path:/container/path" ]
|
@config[:volumes] = ["/local/path:/container/path" ]
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --name app-web-999 -e MRSK_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --log-opt max-size=\"10m\" --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.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999",
|
"docker run --detach --restart unless-stopped --name app-web-999 -e MRSK_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --log-opt max-size=\"10m\" --volume /local/path:/container/path --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app-web.loadbalancer.healthcheck.path=\"/up\" --label traefik.http.services.app-web.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
@config[:healthcheck] = { "path" => "/healthz" }
|
@config[:healthcheck] = { "path" => "/healthz" }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --name app-web-999 -e MRSK_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --log-opt max-size=\"10m\" --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.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999",
|
"docker run --detach --restart unless-stopped --name app-web-999 -e MRSK_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app-web.loadbalancer.healthcheck.path=\"/healthz\" --label traefik.http.services.app-web.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --name app-web-999 -e MRSK_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --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.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999",
|
"docker run --detach --restart unless-stopped --name app-web-999 -e MRSK_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app-web.loadbalancer.healthcheck.path=\"/up\" --label traefik.http.services.app-web.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -2,53 +2,66 @@ require "test_helper"
|
|||||||
|
|
||||||
class CommandsTraefikTest < ActiveSupport::TestCase
|
class CommandsTraefikTest < ActiveSupport::TestCase
|
||||||
setup do
|
setup do
|
||||||
|
@image = "traefik:test"
|
||||||
|
|
||||||
@config = {
|
@config = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
||||||
traefik: { "args" => { "accesslog.format" => "json", "api.insecure" => true, "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
traefik: { "image" => @image, "args" => { "accesslog.format" => "json", "api.insecure" => true, "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run" do
|
test "run" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" traefik:v2.9.9 --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" #{@image} --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
|
|
||||||
@config[:traefik]["host_port"] = "8080"
|
@config[:traefik]["host_port"] = "8080"
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name traefik --detach --restart unless-stopped --publish 8080:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" traefik:v2.9.9 --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
"docker run --name traefik --detach --restart unless-stopped --publish 8080:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" #{@image} --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run with ports configured" do
|
test "run with ports configured" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" traefik:v2.9.9 --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" #{@image} --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
|
|
||||||
@config[:traefik]["options"] = {"publish" => %w[9000:9000 9001:9001]}
|
@config[:traefik]["options"] = {"publish" => %w[9000:9000 9001:9001]}
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --publish \"9000:9000\" --publish \"9001:9001\" traefik:v2.9.9 --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --publish \"9000:9000\" --publish \"9001:9001\" #{@image} --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run with volumes configured" do
|
test "run with volumes configured" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" traefik:v2.9.9 --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" #{@image} --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
|
|
||||||
@config[:traefik]["options"] = {"volume" => %w[./letsencrypt/acme.json:/letsencrypt/acme.json] }
|
@config[:traefik]["options"] = {"volume" => %w[./letsencrypt/acme.json:/letsencrypt/acme.json] }
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --volume \"./letsencrypt/acme.json:/letsencrypt/acme.json\" traefik:v2.9.9 --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --volume \"./letsencrypt/acme.json:/letsencrypt/acme.json\" #{@image} --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run with several options configured" do
|
test "run with several options configured" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" traefik:v2.9.9 --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" #{@image} --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
|
|
||||||
@config[:traefik]["options"] = {"volume" => %w[./letsencrypt/acme.json:/letsencrypt/acme.json], "publish" => %w[8080:8080], "memory" => "512m"}
|
@config[:traefik]["options"] = {"volume" => %w[./letsencrypt/acme.json:/letsencrypt/acme.json], "publish" => %w[8080:8080], "memory" => "512m"}
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --volume \"./letsencrypt/acme.json:/letsencrypt/acme.json\" --publish \"8080:8080\" --memory \"512m\" traefik:v2.9.9 --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --volume \"./letsencrypt/acme.json:/letsencrypt/acme.json\" --publish \"8080:8080\" --memory \"512m\" #{@image} --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
|
new_command.run.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "run with labels configured" do
|
||||||
|
assert_equal \
|
||||||
|
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" #{@image} --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
|
new_command.run.join(" ")
|
||||||
|
|
||||||
|
@config[:traefik]["labels"] = { "traefik.http.routers.dashboard.service" => "api@internal", "traefik.http.routers.dashboard.middlewares" => "auth" }
|
||||||
|
assert_equal \
|
||||||
|
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.dashboard.service=\"api@internal\" --label traefik.http.routers.dashboard.middlewares=\"auth\" #{@image} --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -56,7 +69,7 @@ class CommandsTraefikTest < ActiveSupport::TestCase
|
|||||||
@config.delete(:traefik)
|
@config.delete(:traefik)
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" traefik:v2.9.9 --providers.docker --log.level=DEBUG",
|
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" #{Mrsk::Commands::Traefik::DEFAULT_IMAGE} --providers.docker --log.level=DEBUG",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -64,7 +77,7 @@ class CommandsTraefikTest < ActiveSupport::TestCase
|
|||||||
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" traefik:v2.9.9 --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" #{@image} --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "special label args for web" do
|
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.retry.attempts=\"5\"", "--label", "traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\"", "--label", "traefik.http.routers.app.middlewares=\"app-retry@docker\"" ], @config.role(:web).label_args
|
assert_equal [ "--label", "service=\"app\"", "--label", "role=\"web\"", "--label", "traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\"", "--label", "traefik.http.services.app-web.loadbalancer.healthcheck.path=\"/up\"", "--label", "traefik.http.services.app-web.loadbalancer.healthcheck.interval=\"1s\"", "--label", "traefik.http.middlewares.app-web-retry.retry.attempts=\"5\"", "--label", "traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\"", "--label", "traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\"" ], @config.role(:web).label_args
|
||||||
end
|
end
|
||||||
|
|
||||||
test "custom labels" do
|
test "custom labels" do
|
||||||
@@ -66,7 +66,15 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
|||||||
c[:servers]["beta"] = { "traefik" => "true", "hosts" => [ "1.1.1.5" ] }
|
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.retry.attempts=\"5\"", "--label", "traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\"", "--label", "traefik.http.routers.app.middlewares=\"app-retry@docker\"" ], config.role(:beta).label_args
|
assert_equal [ "--label", "service=\"app\"", "--label", "role=\"beta\"", "--label", "traefik.http.routers.app-beta.rule=\"PathPrefix(\\`/\\`)\"", "--label", "traefik.http.services.app-beta.loadbalancer.healthcheck.path=\"/up\"", "--label", "traefik.http.services.app-beta.loadbalancer.healthcheck.interval=\"1s\"", "--label", "traefik.http.middlewares.app-beta-retry.retry.attempts=\"5\"", "--label", "traefik.http.middlewares.app-beta-retry.retry.initialinterval=\"500ms\"", "--label", "traefik.http.routers.app-beta.middlewares=\"app-beta-retry@docker\"" ], config.role(:beta).label_args
|
||||||
|
end
|
||||||
|
|
||||||
|
test "default traefik label for non-web role with destination" do
|
||||||
|
config = Mrsk::Configuration.new(@deploy_with_roles.tap { |c|
|
||||||
|
c[:servers]["beta"] = { "traefik" => "true", "hosts" => [ "1.1.1.5" ] }
|
||||||
|
}, destination: "staging")
|
||||||
|
|
||||||
|
assert_equal [ "--label", "service=\"app\"", "--label", "role=\"beta\"", "--label", "destination=\"staging\"", "--label", "traefik.http.routers.app-beta-staging.rule=\"PathPrefix(\\`/\\`)\"", "--label", "traefik.http.services.app-beta-staging.loadbalancer.healthcheck.path=\"/up\"", "--label", "traefik.http.services.app-beta-staging.loadbalancer.healthcheck.interval=\"1s\"", "--label", "traefik.http.middlewares.app-beta-staging-retry.retry.attempts=\"5\"", "--label", "traefik.http.middlewares.app-beta-staging-retry.retry.initialinterval=\"500ms\"", "--label", "traefik.http.routers.app-beta-staging.middlewares=\"app-beta-staging-retry@docker\"" ], config.role(:beta).label_args
|
||||||
end
|
end
|
||||||
|
|
||||||
test "env overwritten by role" do
|
test "env overwritten by role" do
|
||||||
|
|||||||
@@ -249,6 +249,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"], :logging=>["--log-opt", "max-size=\"10m\""], :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"], :logging=>["--log-opt", "max-size=\"10m\""], :healthcheck=>{"path"=>"/up", "port"=>3000, "max_attempts" => 7 }}, @config.to_h)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user