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:
@@ -1,4 +1,7 @@
|
|||||||
class Mrsk::Cli::Build < Mrsk::Cli::Base
|
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"
|
desc "deliver", "Build app and push app image to registry then pull image on servers"
|
||||||
def deliver
|
def deliver
|
||||||
with_lock do
|
with_lock do
|
||||||
@@ -10,16 +13,18 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
|
|||||||
desc "push", "Build and push app image to registry"
|
desc "push", "Build and push app image to registry"
|
||||||
def push
|
def push
|
||||||
with_lock do
|
with_lock do
|
||||||
cli = self
|
cli_build = self
|
||||||
|
|
||||||
run_locally do
|
run_locally do
|
||||||
begin
|
begin
|
||||||
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
|
if cli_build.dependencies
|
||||||
|
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
|
||||||
|
end
|
||||||
rescue SSHKit::Command::Failed => e
|
rescue SSHKit::Command::Failed => e
|
||||||
if e.message =~ /(no builder)|(no such file or directory)/
|
if e.message =~ /(no builder)|(no such file or directory)/
|
||||||
error "Missing compatible builder, so creating a new one first"
|
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 }
|
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -77,4 +82,19 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
|
|||||||
puts capture(*MRSK.builder.info)
|
puts capture(*MRSK.builder.info)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
@@ -33,4 +33,28 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base
|
|||||||
def multiarch_remote
|
def multiarch_remote
|
||||||
@multiarch_remote ||= Mrsk::Commands::Builder::Multiarch::Remote.new(config)
|
@multiarch_remote ||= Mrsk::Commands::Builder::Multiarch::Remote.new(config)
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
|
||||||
class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
|
class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
|
||||||
delegate :argumentize, to: Mrsk::Utils
|
delegate :argumentize, to: Mrsk::Utils
|
||||||
|
|
||||||
|
class BuilderError < StandardError; end
|
||||||
|
|
||||||
def clean
|
def clean
|
||||||
docker :image, :rm, "--force", config.absolute_image
|
docker :image, :rm, "--force", config.absolute_image
|
||||||
end
|
end
|
||||||
@@ -17,6 +20,7 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
|
|||||||
context
|
context
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def build_tags
|
def build_tags
|
||||||
[ "-t", config.absolute_image, "-t", config.latest_image ]
|
[ "-t", config.absolute_image, "-t", config.latest_image ]
|
||||||
@@ -35,7 +39,11 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def build_dockerfile
|
def build_dockerfile
|
||||||
argumentize "--file", dockerfile
|
if Pathname.new(File.expand_path(dockerfile)).exist?
|
||||||
|
argumentize "--file", dockerfile
|
||||||
|
else
|
||||||
|
raise BuilderError, "Missing Dockerfile"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def args
|
def args
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class CliBuildTest < CliTestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "push" do
|
test "push" do
|
||||||
|
Mrsk::Cli::Build.any_instance.stubs(:dependencies).returns(true)
|
||||||
run_command("push").tap do |output|
|
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
|
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
|
end
|
||||||
@@ -16,6 +17,7 @@ class CliBuildTest < CliTestCase
|
|||||||
|
|
||||||
test "push without builder" do
|
test "push without builder" do
|
||||||
stub_locking
|
stub_locking
|
||||||
|
Mrsk::Cli::Build.any_instance.stubs(:dependencies).returns(true)
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with { |arg| arg == :docker }
|
.with { |arg| arg == :docker }
|
||||||
.raises(SSHKit::Command::Failed.new("no builder"))
|
.raises(SSHKit::Command::Failed.new("no builder"))
|
||||||
@@ -68,6 +70,22 @@ class CliBuildTest < CliTestCase
|
|||||||
end
|
end
|
||||||
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
|
private
|
||||||
def run_command(*command)
|
def run_command(*command)
|
||||||
stdouted { Mrsk::Cli::Build.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }
|
stdouted { Mrsk::Cli::Build.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }
|
||||||
|
|||||||
@@ -52,12 +52,21 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "build dockerfile" do
|
test "build dockerfile" do
|
||||||
|
Pathname.any_instance.expects(:exist?).returns(true).once
|
||||||
builder = new_builder_command(builder: { "dockerfile" => "Dockerfile.xyz" })
|
builder = new_builder_command(builder: { "dockerfile" => "Dockerfile.xyz" })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile.xyz",
|
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile.xyz",
|
||||||
builder.target.build_options.join(" ")
|
builder.target.build_options.join(" ")
|
||||||
end
|
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
|
test "build context" do
|
||||||
builder = new_builder_command(builder: { "context" => ".." })
|
builder = new_builder_command(builder: { "context" => ".." })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
|
|||||||
Reference in New Issue
Block a user