diff --git a/README.md b/README.md index 62941f54..e34db4ff 100644 --- a/README.md +++ b/README.md @@ -302,9 +302,9 @@ Now run `mrsk accessory start mysql` to start the MySQL server on 1.1.1.3. See ` ## 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 # Runs command on all servers @@ -347,13 +347,25 @@ Database adapter sqlite3 Database schema version 20221231233303 # 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 ``` -### 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 diff --git a/lib/mrsk/cli/app.rb b/lib/mrsk/cli/app.rb index dd0644dc..abab40a5 100644 --- a/lib/mrsk/cli/app.rb +++ b/lib/mrsk/cli/app.rb @@ -47,49 +47,40 @@ class Mrsk::Cli::App < Mrsk::Cli::Base end 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) - runner = \ - case options[:method] - when "exec" then "exec" - when "run" then "run_exec" - when "ssh" then "exec_over_ssh" - else raise "Unknown method: #{options[:method]}" - 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) + case + when options[:interactive] && options[:reuse] + say "Get current version of running container...", :magenta unless options[:version] + using_version(options[:version] || current_running_version) do |version| + say "Launching interactive command with version #{version} via SSH from existing container on #{MRSK.primary_host}...", :magenta + run_locally { exec MRSK.app.execute_in_existing_container_over_ssh(cmd, host: MRSK.primary_host) } 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 - 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 - 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" def containers on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_containers) } diff --git a/lib/mrsk/commands/app.rb b/lib/mrsk/commands/app.rb index ae8a1fc9..45012d9f 100644 --- a/lib/mrsk/commands/app.rb +++ b/lib/mrsk/commands/app.rb @@ -42,14 +42,14 @@ class Mrsk::Commands::App < Mrsk::Commands::Base ("grep '#{grep}'" if grep) end - def exec(*command, interactive: false) + def execute_in_existing_container(*command, interactive: false) docker :exec, ("-it" if interactive), config.service_with_version, *command end - - def run_exec(*command, interactive: false) + + def execute_in_new_container(*command, interactive: false) docker :run, ("-it" if interactive), "--rm", @@ -60,8 +60,12 @@ class Mrsk::Commands::App < Mrsk::Commands::Base *command end - def exec_over_ssh(*command, host:) - run_over_ssh run_exec(*command, interactive: true).join(" "), host: host + def execute_in_existing_container_over_ssh(*command, 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 def follow_logs(host:, grep: nil) @@ -72,14 +76,6 @@ class Mrsk::Commands::App < Mrsk::Commands::Base ).join(" "), host: host 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 docker :container, :ls, "-a", *service_filter end diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index 51ee7b3a..5c8bcda1 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -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 end - test "run with" do + test "execute in new container" do assert_equal \ [ :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 test "run without master key" do