Enhance docker build matching for determining built container ID

Prior to this commit, docker would look for a container ID based on
"Successfully built" string. This output does not exist if a user has
enabled the experimental feature buildkit. This commit updates the build
behavior to match against both kinds of outputs, and instead of using
`scan`, it uses MatchData and groups the container id with match group
name `:id` instead of making hard assumptions with the matches being
contained inside arrays from scan.
This commit is contained in:
Brian Cain 2019-11-19 10:59:28 -08:00
parent 4fc8b07974
commit 4d70856b8a
No known key found for this signature in database
GPG Key ID: 9FC4639B2E4510A0
5 changed files with 42 additions and 9 deletions

View File

@ -15,23 +15,30 @@ module VagrantPlugins
@executor = Executor::Local.new
end
# Returns the id for a new container built from `docker build`. Raises
# an exception if the id was unable to be captured from the output
#
# @return [String] id - ID matched from the docker build output.
def build(dir, **opts, &block)
args = Array(opts[:extra_args])
args << dir
result = execute('docker', 'build', '--progress', 'plain', *args, &block)
matches = result.scan(/Successfully built (.+)$/i)
if matches.empty?
result = execute('docker', 'build', *args, &block)
matches = result.match(/Successfully built (?<id>.+)$/i)
if !matches
# Check for the new output format 'writing image sha256...'
matches = result.scan(/writing image sha256:([0-9a-z]+) +$/i)
if matches.empty?
# In this case, docker builtkit is enabled. Its format is different
# from standard docker
@logger.warn("Could not determine docker container ID. Scanning for buildkit output instead")
matches = result.match(/writing image .+:(?<id>[0-9a-z]+) done/i)
if !matches
# This will cause a stack trace in Vagrant, but it is a bug
# if this happens anyways.
raise "UNKNOWN OUTPUT: #{result}"
raise Errors::BuildError, result: result
end
end
# Return the last match, and the capture of it
matches[-1][0]
# Return the matched group `id`
matches[:id]
end
def create(params, **opts, &block)

View File

@ -5,6 +5,10 @@ module VagrantPlugins
error_namespace("docker_provider.errors")
end
class BuildError < DockerError
error_key(:build_error)
end
class CommunicatorNonDocker < DockerError
error_key(:communicator_non_docker)
end

View File

@ -27,7 +27,6 @@ module VagrantPlugins
stdout: result.stdout
end
# If the new buildkit-based docker build is used, stdout is empty, and the output is in stderr
if result.stdout.to_s.strip.length == 0
result.stderr
else

View File

@ -159,6 +159,8 @@ en:
run exits and doesn't keep running.
errors:
build_error: |-
Vagrant received unknown output from docker: %{result}
compose_lock_timeout: |-
Vagrant encountered a timeout waiting for the docker compose driver
to become available. Please try to run your command again. If you

View File

@ -152,6 +152,27 @@ describe VagrantPlugins::DockerProvider::Driver do
].to_json }
describe '#build' do
let(:result) { "Successfully built 1a2b3c4d" }
let(:buildkit_result) { "writing image sha256:1a2b3c4d done" }
let(:cid) { "1a2b3c4d" }
it "builds a container with standard docker" do
allow(subject).to receive(:execute).and_return(result)
container_id = subject.build("/tmp/fakedir")
expect(container_id).to eq(cid)
end
it "builds a container with buildkit docker" do
allow(subject).to receive(:execute).and_return(buildkit_result)
container_id = subject.build("/tmp/fakedir")
expect(container_id).to eq(cid)
end
end
describe '#create' do
let(:params) { {