Merge pull request #256 from Jberczel/check-local-dependencies

Add local dependencies check
This commit is contained in:
David Heinemeier Hansson
2023-05-02 14:13:23 +02:00
committed by GitHub
7 changed files with 83 additions and 5 deletions

View File

@@ -1,4 +1,6 @@
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
@@ -14,7 +16,9 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
run_locally do run_locally do
begin begin
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push } if cli.verify_local_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"
@@ -77,4 +81,22 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
puts capture(*MRSK.builder.info) puts capture(*MRSK.builder.info)
end end
end end
desc "", "" # Really a private method, but needed to be invoked from #push
def verify_local_dependencies
run_locally do
begin
execute *MRSK.builder.ensure_local_dependencies_installed
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

View File

@@ -17,7 +17,7 @@ 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 say "Ensure curl and Docker are installed on servers...", :magenta
invoke "mrsk:cli:server:bootstrap", [], invoke_options invoke "mrsk:cli:server:bootstrap", [], invoke_options
say "Log into image registry...", :magenta say "Log into image registry...", :magenta

View File

@@ -2,7 +2,7 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base
delegate :create, :remove, :push, :clean, :pull, :info, to: :target delegate :create, :remove, :push, :clean, :pull, :info, to: :target
def name def name
target.class.to_s.remove("Mrsk::Commands::Builder::").underscore target.class.to_s.remove("Mrsk::Commands::Builder::").underscore.inquiry
end end
def target def target
@@ -33,4 +33,24 @@ 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 ensure_local_dependencies_installed
if name.native?
ensure_local_docker_installed
else
combine \
ensure_local_docker_installed,
ensure_local_buildx_installed
end
end
private
def ensure_local_docker_installed
docker "--version"
end
def ensure_local_buildx_installed
docker :buildx, "version"
end
end end

View File

@@ -1,4 +1,7 @@
class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
class BuilderError < StandardError; end
delegate :argumentize, to: Mrsk::Utils delegate :argumentize, to: Mrsk::Utils
def clean def clean
@@ -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

View File

@@ -9,6 +9,7 @@ class CliBuildTest < CliTestCase
end end
test "push" do test "push" do
Mrsk::Cli::Build.any_instance.stubs(:verify_local_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(:verify_local_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,23 @@ class CliBuildTest < CliTestCase
end end
end end
test "verify local dependencies" do
Mrsk::Commands::Builder.any_instance.stubs(:name).returns("remote".inquiry)
run_command("verify_local_dependencies").tap do |output|
assert_match /docker --version && docker buildx version/, output
end
end
test "verify local 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("verify_local_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"]) }

View File

@@ -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 \

View File

@@ -48,7 +48,7 @@ class ConfigurationTest < ActiveSupport::TestCase
end end
test "role" do test "role" do
assert_equal "web", @config.role(:web).name assert @config.role(:web).name.web?
assert_equal "workers", @config_with_roles.role(:workers).name assert_equal "workers", @config_with_roles.role(:workers).name
assert_nil @config.role(:missing) assert_nil @config.role(:missing)
end end