Clarify exec modes and drop tailored versions

This commit is contained in:
David Heinemeier Hansson
2023-02-03 16:07:25 +01:00
parent a3d998508b
commit 3c1053fedd
4 changed files with 56 additions and 57 deletions

View File

@@ -302,9 +302,9 @@ Now run `mrsk accessory start mysql` to start the MySQL server on 1.1.1.3. See `
## Commands ## Commands
### Running remote execution and runners ### Running commands on servers
If you need to execute commands inside the Rails containers, you can use `mrsk app exec` and `mrsk app runner`. Examples: You can execute one-off commands on the servers:
```bash ```bash
# Runs command on all servers # Runs command on all servers
@@ -347,13 +347,25 @@ Database adapter sqlite3
Database schema version 20221231233303 Database schema version 20221231233303
# Run Rails runner on primary server # Run Rails runner on primary server
mrsk app runner -p 'puts Rails.application.config.time_zone' mrsk app exec -p 'bin/rails runner "puts Rails.application.config.time_zone"'
UTC UTC
``` ```
### Running a Rails console ### Running interactive commands over SSH
You can run interactive commands, like a Rails console or a bash session, on a server (default is primary, use `--hosts` to connect to another):
```bash
# Starts a bash session in a new container made from the most recent app image
mrsk app exec -i bash
# Starts a bash session in the currently running container for the app
mrsk app exec -i --reuse bash
# Starts a Rails console in a new container made from the most recent app image
mrsk app exec -i 'bin/rails console'
```
If you need to interact with the production console for the app, you can use `mrsk app console`, which will start a Rails console session on the primary host. You can start the console on a different host using `mrsk app console --host 192.168.0.2`. Be mindful that this is a live wire! Any changes made to the production database will take effect immeditately.
### Running details to see state of containers ### Running details to see state of containers

View File

@@ -47,49 +47,40 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
end end
desc "exec [CMD]", "Execute a custom command on servers" desc "exec [CMD]", "Execute a custom command on servers"
option :method, aliases: "-m", default: "exec", desc: "Execution method: [exec] perform inside app container / [run] perform in new container / [ssh] perform over ssh" 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) def exec(cmd)
runner = \ case
case options[:method] when options[:interactive] && options[:reuse]
when "exec" then "exec" say "Get current version of running container...", :magenta unless options[:version]
when "run" then "run_exec" using_version(options[:version] || current_running_version) do |version|
when "ssh" then "exec_over_ssh" say "Launching interactive command with version #{version} via SSH from existing container on #{MRSK.primary_host}...", :magenta
else raise "Unknown method: #{options[:method]}" run_locally { exec MRSK.app.execute_in_existing_container_over_ssh(cmd, host: MRSK.primary_host) }
end.inquiry
if runner.exec_over_ssh?
run_locally do
info "Launching command on #{MRSK.primary_host}"
exec MRSK.app.exec_over_ssh(cmd, host: MRSK.primary_host)
end end
when options[:interactive]
say "Get most recent version available as an image...", :magenta unless options[:version]
using_version(options[:version] || most_recent_version_available) do |version|
say "Launching interactive command with version #{version} via SSH from new container on #{MRSK.primary_host}...", :magenta
run_locally { exec MRSK.app.execute_in_new_container_over_ssh(cmd, host: MRSK.primary_host) }
end
when options[:reuse]
say "Get current version of running container...", :magenta unless options[:version]
using_version(options[:version] || current_running_version) do |version|
say "Launching command with version #{version} from existing container on #{MRSK.primary_host}", :magenta
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.execute_in_existing_container(cmd)) }
end
else else
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.send(runner, cmd)) } say "Get most recent version available as an image...", :magenta unless options[:version]
using_version(options[:version] || most_recent_version_available) do |version|
say "Launching command with version #{version} from new container on #{MRSK.primary_host}", :magenta
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.execute_in_new_container(cmd)) }
end
end end
end end
desc "console", "Start Rails Console on primary host (or specific host set by --hosts)"
def console
say "Get most recent version available as an image...", :magenta unless options[:version]
using_version(options[:version] || most_recent_version_available) do |version|
say "Launching Rails console of version #{version} on #{MRSK.primary_host}...", :magenta
run_locally { exec MRSK.app.console(host: MRSK.primary_host) }
end
end
desc "bash", "Start a bash session on primary host (or specific host set by --hosts)"
def bash
say "Get most recent version available as an image...", :magenta unless options[:version]
using_version(options[:version] || most_recent_version_available) do |version|
say "Launching bash session of version #{version} on #{MRSK.primary_host}...", :magenta
run_locally { exec MRSK.app.bash(host: MRSK.primary_host) }
end
end
desc "runner [EXPRESSION]", "Execute Rails runner with given expression"
def runner(expression)
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.exec("bin/rails", "runner", "'#{expression}'")) }
end
desc "containers", "List all the app containers currently on servers" desc "containers", "List all the app containers currently on servers"
def containers def containers
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_containers) } on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_containers) }

View File

@@ -42,14 +42,14 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
("grep '#{grep}'" if grep) ("grep '#{grep}'" if grep)
end end
def exec(*command, interactive: false) def execute_in_existing_container(*command, interactive: false)
docker :exec, docker :exec,
("-it" if interactive), ("-it" if interactive),
config.service_with_version, config.service_with_version,
*command *command
end end
def run_exec(*command, interactive: false) def execute_in_new_container(*command, interactive: false)
docker :run, docker :run,
("-it" if interactive), ("-it" if interactive),
"--rm", "--rm",
@@ -60,8 +60,12 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
*command *command
end end
def exec_over_ssh(*command, host:) def execute_in_existing_container_over_ssh(*command, host:)
run_over_ssh run_exec(*command, interactive: true).join(" "), host: host run_over_ssh execute_in_existing_container(*command, interactive: true).join(" "), host: host
end
def execute_in_new_container_over_ssh(*command, host:)
run_over_ssh execute_in_new_container(*command, interactive: true).join(" "), host: host
end end
def follow_logs(host:, grep: nil) def follow_logs(host:, grep: nil)
@@ -72,14 +76,6 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
).join(" "), host: host ).join(" "), host: host
end end
def console(host:)
exec_over_ssh "bin/rails", "c", host: host
end
def bash(host:)
exec_over_ssh "bash", host: host
end
def list_containers def list_containers
docker :container, :ls, "-a", *service_filter docker :container, :ls, "-a", *service_filter
end end

View File

@@ -26,10 +26,10 @@ class CommandsAppTest < ActiveSupport::TestCase
[:docker, :run, "-d", "--restart unless-stopped", "--name", "app-missing", "-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:missing"], @app.run [:docker, :run, "-d", "--restart unless-stopped", "--name", "app-missing", "-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:missing"], @app.run
end end
test "run with" do test "execute in new container" do
assert_equal \ assert_equal \
[ :docker, :run, "--rm", "-e", "RAILS_MASTER_KEY=456", "dhh/app:missing", "bin/rails", "db:setup" ], [ :docker, :run, "--rm", "-e", "RAILS_MASTER_KEY=456", "dhh/app:missing", "bin/rails", "db:setup" ],
@app.run_exec("bin/rails", "db:setup") @app.execute_in_new_container("bin/rails", "db:setup")
end end
test "run without master key" do test "run without master key" do