Bootstrap: use multi-platform installer

* Limit auto-install to root users; otherwise, give manual install guidance
* Support non-Debian/Ubuntu with the multi-OS get.docker.com installer
This commit is contained in:
Jeremy Daer
2023-04-10 10:42:29 -07:00
committed by Jeremy Daer
parent 4fa6a6c06d
commit e85bd5ff63
7 changed files with 88 additions and 22 deletions

View File

@@ -17,9 +17,6 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
invoke_options = deploy_options invoke_options = deploy_options
runtime = print_runtime do runtime = print_runtime do
say "Ensure curl and Docker are installed...", :magenta
invoke "mrsk:cli:server:bootstrap", [], invoke_options
say "Log into image registry...", :magenta say "Log into image registry...", :magenta
invoke "mrsk:cli:registry:login", [], invoke_options invoke "mrsk:cli:registry:login", [], invoke_options

View File

@@ -1,17 +1,21 @@
class Mrsk::Cli::Server < Mrsk::Cli::Base class Mrsk::Cli::Server < Mrsk::Cli::Base
desc "bootstrap", "Ensure curl and Docker are installed on servers" desc "bootstrap", "Set up Docker to run MRSK apps"
def bootstrap def bootstrap
with_lock do missing = []
on(MRSK.hosts + MRSK.accessory_hosts) do
dependencies_to_install = Array.new.tap do |dependencies|
dependencies << "curl" unless execute "which curl", raise_on_non_zero_exit: false
dependencies << "docker.io" unless execute "which docker", raise_on_non_zero_exit: false
end
if dependencies_to_install.any? on(MRSK.hosts | MRSK.accessory_hosts) do |host|
execute "apt-get update -y && apt-get install #{dependencies_to_install.join(" ")} -y" unless execute(*MRSK.docker.installed?, raise_on_non_zero_exit: false)
if execute(*MRSK.docker.superuser?, raise_on_non_zero_exit: false)
info "Missing Docker on #{host}. Installing…"
execute *MRSK.docker.install
else
missing << host
end end
end end
end end
if missing.any?
raise "Docker is not installed on #{missing.join(", ")} and can't be automatically installed without having root access and the `curl` command available. Install Docker manually: https://docs.docker.com/engine/install/"
end
end end
end end

View File

@@ -84,6 +84,10 @@ class Mrsk::Commander
@builder ||= Mrsk::Commands::Builder.new(config) @builder ||= Mrsk::Commands::Builder.new(config)
end end
def docker
@docker ||= Mrsk::Commands::Docker.new(config)
end
def healthcheck def healthcheck
@healthcheck ||= Mrsk::Commands::Healthcheck.new(config) @healthcheck ||= Mrsk::Commands::Healthcheck.new(config)
end end

View File

@@ -0,0 +1,21 @@
class Mrsk::Commands::Docker < Mrsk::Commands::Base
# Install Docker using the https://github.com/docker/docker-install convenience script.
def install
pipe [ :curl, "-fsSL", "https://get.docker.com" ], :sh
end
# Checks the Docker client version. Fails if Docker is not installed.
def installed?
docker "-v"
end
# Checks the Docker server version. Fails if Docker is not running.
def running?
docker :version
end
# Do we have superuser access to install Docker and start system services?
def superuser?
[ '[ "${EUID:-$(id -u)}" -eq 0 ]' ]
end
end

View File

@@ -12,7 +12,6 @@ class CliMainTest < CliTestCase
test "deploy" do test "deploy" 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" }
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:server:bootstrap", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:registry:login", [], invoke_options) Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:registry:login", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:build:deliver", [], invoke_options) Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:build:deliver", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:traefik:boot", [], invoke_options) Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:traefik:boot", [], invoke_options)
@@ -22,7 +21,6 @@ class CliMainTest < CliTestCase
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:prune:all", [], invoke_options) Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:prune:all", [], invoke_options)
run_command("deploy").tap do |output| run_command("deploy").tap do |output|
assert_match /Ensure curl and Docker are installed/, output
assert_match /Log into image registry/, output assert_match /Log into image registry/, output
assert_match /Build and push app image/, output assert_match /Build and push app image/, output
assert_match /Ensure Traefik is running/, output assert_match /Ensure Traefik is running/, output
@@ -35,7 +33,6 @@ class CliMainTest < CliTestCase
test "deploy with skip_push" do test "deploy with skip_push" 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" }
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:server:bootstrap", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:registry:login", [], invoke_options) Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:registry:login", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:build:pull", [], invoke_options) Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:build:pull", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:traefik:boot", [], invoke_options) Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:traefik:boot", [], invoke_options)
@@ -46,7 +43,6 @@ class CliMainTest < CliTestCase
run_command("deploy", "--skip_push").tap do |output| run_command("deploy", "--skip_push").tap do |output|
assert_match /Acquiring the deploy lock/, output assert_match /Acquiring the deploy lock/, output
assert_match /Ensure curl and Docker are installed/, output
assert_match /Log into image registry/, output assert_match /Log into image registry/, output
assert_match /Pull app image/, output assert_match /Pull app image/, output
assert_match /Ensure Traefik is running/, output assert_match /Ensure Traefik is running/, output
@@ -87,7 +83,6 @@ class CliMainTest < CliTestCase
test "deploy errors during critical section leave lock in place" do test "deploy errors during critical section leave lock in place" 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" }
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:server:bootstrap", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:registry:login", [], invoke_options) Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:registry:login", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:build:deliver", [], invoke_options) Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:build:deliver", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:app:stale_containers", [], invoke_options) Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:app:stale_containers", [], invoke_options)
@@ -106,7 +101,7 @@ class CliMainTest < CliTestCase
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" }
Mrsk::Cli::Main.any_instance.expects(:invoke) Mrsk::Cli::Main.any_instance.expects(:invoke)
.with("mrsk:cli:server:bootstrap", [], invoke_options) .with("mrsk:cli:registry:login", [], invoke_options)
.raises(RuntimeError) .raises(RuntimeError)
assert !MRSK.holding_lock? assert !MRSK.holding_lock?

View File

@@ -1,11 +1,30 @@
require_relative "cli_test_case" require_relative "cli_test_case"
class CliServerTest < CliTestCase class CliServerTest < CliTestCase
test "bootstrap" do test "bootstrap already installed" do
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(true).at_least_once
assert_equal "", run_command("bootstrap")
end
test "bootstrap install as non-root user" do
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(false).at_least_once
SSHKit::Backend::Abstract.any_instance.expects(:execute).with('[ "${EUID:-$(id -u)}" -eq 0 ]', raise_on_non_zero_exit: false).returns(false).at_least_once
assert_raise RuntimeError, "Docker is not installed on 1.1.1.1, 1.1.1.3, 1.1.1.4, 1.1.1.2 and can't be automatically intalled without having root access and the `curl` command available. Install Docker manually: https://docs.docker.com/engine/install/" do
run_command("bootstrap")
end
end
test "bootstrap install as root user" do
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(false).at_least_once
SSHKit::Backend::Abstract.any_instance.expects(:execute).with('[ "${EUID:-$(id -u)}" -eq 0 ]', raise_on_non_zero_exit: false).returns(true).at_least_once
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:curl, "-fsSL", "https://get.docker.com", "|", :sh).at_least_once
run_command("bootstrap").tap do |output| run_command("bootstrap").tap do |output|
assert_match /which curl/, output ("1.1.1.1".."1.1.1.4").map do |host|
assert_match /which docker/, output assert_match "Missing Docker on #{host}. Installing…", output
assert_match /apt-get update -y && apt-get install curl docker.io -y/, output end
end end
end end

View File

@@ -0,0 +1,26 @@
require "test_helper"
class CommandsDockerTest < ActiveSupport::TestCase
setup do
@config = {
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ]
}
@docker = Mrsk::Commands::Docker.new(Mrsk::Configuration.new(@config))
end
test "install" do
assert_equal "curl -fsSL https://get.docker.com | sh", @docker.install.join(" ")
end
test "installed?" do
assert_equal "docker -v", @docker.installed?.join(" ")
end
test "running?" do
assert_equal "docker version", @docker.running?.join(" ")
end
test "superuser?" do
assert_equal '[ "${EUID:-$(id -u)}" -eq 0 ]', @docker.superuser?.join(" ")
end
end