Add local dependencies check

Add checks for:

* Docker installed locally
* Docker buildx plugin installed locally
* Dockerfile exists

If checks fail, it will halt deployment and provide more specific error messages.

Also adds a cli subcommand:
`mrsk build dependencies`

Fixes: #109 and #237
This commit is contained in:
Jberczel
2023-05-01 15:26:50 -04:00
parent 4fa6a6c06d
commit bfb70b2118
5 changed files with 83 additions and 4 deletions

View File

@@ -1,4 +1,7 @@
class Mrsk::Cli::Build < Mrsk::Cli::Base
class BuildError < StandardError; end
desc "deliver", "Build app and push app image to registry then pull image on servers"
def deliver
with_lock do
@@ -10,16 +13,18 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
desc "push", "Build and push app image to registry"
def push
with_lock do
cli = self
cli_build = self
run_locally do
begin
if cli_build.dependencies
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
end
rescue SSHKit::Command::Failed => e
if e.message =~ /(no builder)|(no such file or directory)/
error "Missing compatible builder, so creating a new one first"
if cli.create
if cli_build.create
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
end
else
@@ -77,4 +82,19 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
puts capture(*MRSK.builder.info)
end
end
desc "dependencies", "Check local dependencies"
def dependencies
run_locally do
begin
execute *MRSK.builder.dependencies
rescue SSHKit::Command::Failed => e
build_error = e.message =~ /command not found/ ?
"Docker is not installed locally" :
"Docker buildx plugin is not installed locally"
raise BuildError, build_error
end
end
true
end
end

View File

@@ -33,4 +33,28 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base
def multiarch_remote
@multiarch_remote ||= Mrsk::Commands::Builder::Multiarch::Remote.new(config)
end
def native_and_local?
name == 'native'
end
def dependencies
if native_and_local?
docker_version
else
combine \
docker_version,
docker_buildx_version
end
end
private
def docker_version
docker "--version"
end
def docker_buildx_version
docker :buildx, "version"
end
end

View File

@@ -1,6 +1,9 @@
class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
delegate :argumentize, to: Mrsk::Utils
class BuilderError < StandardError; end
def clean
docker :image, :rm, "--force", config.absolute_image
end
@@ -17,6 +20,7 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
context
end
private
def build_tags
[ "-t", config.absolute_image, "-t", config.latest_image ]
@@ -35,7 +39,11 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
end
def build_dockerfile
if Pathname.new(File.expand_path(dockerfile)).exist?
argumentize "--file", dockerfile
else
raise BuilderError, "Missing Dockerfile"
end
end
def args

View File

@@ -9,6 +9,7 @@ class CliBuildTest < CliTestCase
end
test "push" do
Mrsk::Cli::Build.any_instance.stubs(:dependencies).returns(true)
run_command("push").tap do |output|
assert_match /docker buildx build --push --platform linux\/amd64,linux\/arm64 --builder mrsk-app-multiarch -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output
end
@@ -16,6 +17,7 @@ class CliBuildTest < CliTestCase
test "push without builder" do
stub_locking
Mrsk::Cli::Build.any_instance.stubs(:dependencies).returns(true)
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
.with { |arg| arg == :docker }
.raises(SSHKit::Command::Failed.new("no builder"))
@@ -68,6 +70,22 @@ class CliBuildTest < CliTestCase
end
end
test "dependencies" do
Mrsk::Commands::Builder.any_instance.stubs(:native_and_local?).returns(false)
run_command("dependencies").tap do |output|
assert_match /docker --version && docker buildx version/, output
end
end
test "dependencies with no buildx plugin" do
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
.with(:docker, "--version", "&&", :docker, :buildx, "version")
.raises(SSHKit::Command::Failed.new("no buildx"))
Mrsk::Commands::Builder.any_instance.stubs(:native_and_local?).returns(false)
assert_raises(Mrsk::Cli::Build::BuildError) { run_command("dependencies") }
end
private
def run_command(*command)
stdouted { Mrsk::Cli::Build.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }

View File

@@ -52,12 +52,21 @@ class CommandsBuilderTest < ActiveSupport::TestCase
end
test "build dockerfile" do
Pathname.any_instance.expects(:exist?).returns(true).once
builder = new_builder_command(builder: { "dockerfile" => "Dockerfile.xyz" })
assert_equal \
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile.xyz",
builder.target.build_options.join(" ")
end
test "missing dockerfile" do
Pathname.any_instance.expects(:exist?).returns(false).once
builder = new_builder_command(builder: { "dockerfile" => "Dockerfile.xyz" })
assert_raises(Mrsk::Commands::Builder::Base::BuilderError) do
builder.target.build_options.join(" ")
end
end
test "build context" do
builder = new_builder_command(builder: { "context" => ".." })
assert_equal \