Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add local dependencies check #256

Merged
merged 11 commits into from
May 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion lib/mrsk/cli/build.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
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
Expand All @@ -14,7 +16,9 @@ def push

run_locally do
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
if e.message =~ /(no builder)|(no such file or directory)/
error "Missing compatible builder, so creating a new one first"
Expand Down Expand Up @@ -77,4 +81,22 @@ def details
puts capture(*MRSK.builder.info)
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
2 changes: 1 addition & 1 deletion lib/mrsk/cli/main.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def deploy
invoke_options = deploy_options

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

say "Log into image registry...", :magenta
Expand Down
22 changes: 21 additions & 1 deletion lib/mrsk/commands/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base
delegate :create, :remove, :push, :clean, :pull, :info, to: :target

def name
target.class.to_s.remove("Mrsk::Commands::Builder::").underscore
target.class.to_s.remove("Mrsk::Commands::Builder::").underscore.inquiry
end

def target
Expand Down Expand Up @@ -33,4 +33,24 @@ def multiarch
def multiarch_remote
@multiarch_remote ||= Mrsk::Commands::Builder::Multiarch::Remote.new(config)
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
10 changes: 9 additions & 1 deletion lib/mrsk/commands/builder/base.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@

class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
class BuilderError < StandardError; end

delegate :argumentize, to: Mrsk::Utils

def clean
Expand All @@ -17,6 +20,7 @@ def build_context
context
end


private
def build_tags
[ "-t", config.absolute_image, "-t", config.latest_image ]
Expand All @@ -35,7 +39,11 @@ def build_secrets
end

def build_dockerfile
argumentize "--file", dockerfile
if Pathname.new(File.expand_path(dockerfile)).exist?
argumentize "--file", dockerfile
else
raise BuilderError, "Missing #{dockerfile}"
end
end

def args
Expand Down
19 changes: 19 additions & 0 deletions test/cli/build_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ class CliBuildTest < CliTestCase
end

test "push" do
Mrsk::Cli::Build.any_instance.stubs(:verify_local_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
end

test "push without builder" do
stub_locking
Mrsk::Cli::Build.any_instance.stubs(:verify_local_dependencies).returns(true)
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
.with { |arg| arg == :docker }
.raises(SSHKit::Command::Failed.new("no builder"))
Expand Down Expand Up @@ -68,6 +70,23 @@ class CliBuildTest < CliTestCase
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
def run_command(*command)
stdouted { Mrsk::Cli::Build.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }
Expand Down
9 changes: 9 additions & 0 deletions test/commands/builder_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
2 changes: 1 addition & 1 deletion test/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class ConfigurationTest < ActiveSupport::TestCase
end

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_nil @config.role(:missing)
end
Expand Down