From 3ffaee91c2702db5611b82c900cfe786fc570738 Mon Sep 17 00:00:00 2001 From: Adam Leff Date: Fri, 1 Dec 2017 04:24:15 -0500 Subject: [PATCH] docker_image resource: properly handle registries in image strings (#2356) When supplying a docker image that contains a registry with a port number, such as `localhost:5000/chef/inspec:1.46.3`, the docker_image resource was unable to locate the image in question due to incorrect parsing of the repository and tag. Signed-off-by: Adam Leff --- lib/resources/docker_image.rb | 62 +++++++++++++++++------- test/unit/resources/docker_image_test.rb | 49 +++++++++++++++++++ 2 files changed, 94 insertions(+), 17 deletions(-) diff --git a/lib/resources/docker_image.rb b/lib/resources/docker_image.rb index 8bc807adb..245de6590 100644 --- a/lib/resources/docker_image.rb +++ b/lib/resources/docker_image.rb @@ -63,25 +63,19 @@ module Inspec::Resources private def sanitize_options(opts) - if !opts[:image].nil? - if !opts[:image].index(':').nil? - repo, tag = opts[:image].split(':') - else - opts[:repo] = opts[:image] - opts[:image] = nil - end - opts[:repo] ||= repo - opts[:tag] ||= tag - end - - if !opts[:id].nil? - if opts[:id].index(':').nil? - opts[:id] = 'sha256:' + opts[:id] - end - end + opts.merge!(parse_components_from_image(opts[:image])) + # assume a "latest" tag if we don't have one opts[:tag] ||= 'latest' - opts[:image] ||= "#{opts[:repo]}:#{opts[:tag]}" unless opts[:repo].nil? + + # if the ID isn't nil and doesn't contain a hash indicator (indicated by the presence + # of a colon, which separates the indicator from the actual hash), we assume it's sha256. + opts[:id] = 'sha256:' + opts[:id] unless opts[:id].nil? || opts[:id].include?(':') + + # Assemble/reassemble the image from the repo and tag + opts[:image] = "#{opts[:repo]}:#{opts[:tag]}" unless opts[:repo].nil? + + # return the santized opts back to the caller opts end @@ -92,5 +86,39 @@ module Inspec::Resources (repository == opts[:repo] && tag == opts[:tag]) || (!id.nil? && !opts[:id].nil? && (id == opts[:id] || id.start_with?(opts[:id]))) } end + + def parse_components_from_image(image_string) + # if the user did not supply an image string, they likely supplied individual + # option parameters, such as repo and tag. Return empty data back to the caller. + return {} if image_string.nil? + + first_colon = image_string.index(':') || -1 + first_slash = image_string.index('/') || -1 + + if image_string.count(':') == 2 + # If there are two colons in the image string, it contains a repo-with-port and a tag. + # example: localhost:5000/chef/inspec:1.46.3 + partitioned_string = image_string.rpartition(':') + repo = partitioned_string.first + tag = partitioned_string.last + elsif image_string.count(':') == 1 && first_colon < first_slash + # If there's one colon in the image string, and it comes before a forward-slash, + # it contains a repo-with-port but no tag. + # example: localhost:5000/ubuntu + repo = image_string + tag = nil + else + # If there's one colon in the image string and it doesn't preceed a slash, or if + # there is no colon at all, then it separates the repo from the tag, if there is a tag. + # example: chef/inspec:1.46.3 + # example: chef/inspec + # example: ubuntu:14.04 + repo, tag = image_string.split(':') + end + + # return the repo and tag parsed from the string, which can be merged into + # the rest of the user-supplied options + { repo: repo, tag: tag } + end end end diff --git a/test/unit/resources/docker_image_test.rb b/test/unit/resources/docker_image_test.rb index 4fcbfcc16..582eef0ee 100644 --- a/test/unit/resources/docker_image_test.rb +++ b/test/unit/resources/docker_image_test.rb @@ -19,4 +19,53 @@ describe 'Inspec::Resources::DockerImage' do resource.to_s.must_equal 'Docker Image alpine:latest' end end + + describe '#parse_components_from_image' do + let(:resource) { load_resource('docker_image', 'alpine') } + let(:parsed) { resource.send(:parse_components_from_image, image_string) } + + describe 'a nil image string' do + let(:image_string) { nil } + + it 'returns an empty hash' do + parsed.must_equal({}) + end + end + + describe 'an image string containing a simple repo' do + let(:image_string) { 'chef/inspec' } + + it 'returns correct data' do + parsed[:repo].must_equal 'chef/inspec' + parsed[:tag].must_be_nil + end + end + + describe 'parses an image string containing a repo with a port number' do + let(:image_string) { 'localhost:5000/chef/inspec' } + + it 'returns correct data' do + parsed[:repo].must_equal 'localhost:5000/chef/inspec' + parsed[:tag].must_be_nil + end + end + + describe 'parses an image string containing a repo with a tag' do + let(:image_string) { 'chef/inspec:1.46.3' } + + it 'returns correct data' do + parsed[:repo].must_equal 'chef/inspec' + parsed[:tag].must_equal '1.46.3' + end + end + + describe 'parses an image string containing a repo with a port number and a tag' do + let(:image_string) { 'localhost:5000/chef/inspec:1.46.3' } + + it 'returns correct data' do + parsed[:repo].must_equal 'localhost:5000/chef/inspec' + parsed[:tag].must_equal '1.46.3' + end + end + end end