Skip to content

Commit 7c22bc9

Browse files
authored
Refactoring Docker URI Parser (#4448)
* refactor: sha256 and tag parser refactored. Digest's hash value moved to function parse_docker_uri * fix: double-quated converted to single-quoted * refactor: splitting tag and digest from path refactored * fix: rubocop suggestions are corrected
1 parent cd3fcac commit 7c22bc9

File tree

3 files changed

+44
-16
lines changed

3 files changed

+44
-16
lines changed

lib/cloud_controller/diego/docker/docker_uri_converter.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,12 @@ class DockerURIConverter
55
def convert(docker_uri)
66
raise UriUtils::InvalidDockerURI.new "Docker URI [#{docker_uri}] should not contain scheme" if docker_uri.include? '://'
77

8-
host, path, tag = UriUtils.parse_docker_uri(docker_uri)
8+
host, path, tag_digest = UriUtils.parse_docker_uri(docker_uri)
99

10-
if !tag.nil? && tag.start_with?('@sha256:')
11-
path = "#{path}@sha256"
12-
tag.slice!('@sha256:')
13-
end
14-
15-
Addressable::URI.new(scheme: 'docker', host: host, path: path, fragment: tag).to_s
10+
# add tag or digest part as fragment to the uri, since ruby uri parser confuses with ':'
11+
# when it presented in path. We convert user's uri to, for example;
12+
# docker://docker.io/publish/ubuntu:latest -> docker://docker.io/publish/ubuntu#latest
13+
Addressable::URI.new(scheme: 'docker', host: host, path: path, fragment: tag_digest).to_s
1614
end
1715
end
1816
end

lib/utils/uri_utils.rb

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ module UriUtils
44
SSH_REGEX = %r{ \A (?:ssh://)? git@ .+? : .+? \.git \z }x
55
GIT_REGEX = %r{ \A git:// .+? : .+? \.git \z }x
66
DOCKER_INDEX_SERVER = 'docker.io'.freeze
7+
DOCKER_PATH_REGEX = %r{\A[a-z0-9_\-\.\/]{2,255}\Z}
8+
DOCKER_TAG_REGEX = /[a-zA-Z0-9_\-\.]{1,128}/
9+
DOCKER_DIGEST_REGEX = /sha256:[a-z0-9]{64}/
10+
DOCKER_TAG_DIGEST_REGEX = Regexp.new("\\A(#{DOCKER_TAG_REGEX.source} |
11+
(#{DOCKER_TAG_REGEX.source}@#{DOCKER_DIGEST_REGEX.source}) | #{DOCKER_DIGEST_REGEX.source})\\Z", Regexp::EXTENDED)
712

813
class InvalidDockerURI < StandardError; end
914

@@ -62,13 +67,20 @@ def self.parse_docker_uri(docker_uri)
6267
end
6368

6469
path = 'library/' + path if (official_docker_registry(name_parts[0]) || missing_registry(name_parts)) && path.exclude?('/')
70+
path, tag_digest = parse_docker_tag_digest_from_path(path)
6571

66-
path, tag = parse_docker_repository_tag(path)
72+
raise InvalidDockerURI.new "Invalid image name [#{path}]" unless DOCKER_PATH_REGEX =~ path
73+
raise InvalidDockerURI.new "Invalid image tag [#{tag_digest}]" if tag_digest && DOCKER_TAG_DIGEST_REGEX !~ tag_digest
6774

68-
raise InvalidDockerURI.new "Invalid image name [#{path}]" unless %r{\A[a-z0-9_\-\.\/]{2,255}\Z} =~ path
69-
raise InvalidDockerURI.new "Invalid image tag [#{tag}]" if tag && !(/\A(([a-zA-Z0-9_\-\.]{1,128})|(([a-zA-Z0-9_\-\.]{0,128})(@sha256:[a-z0-9]{64})))\Z/ =~ tag)
75+
# if only sha256 presented, we add hash value as fragment to the uri,
76+
# since the ruby uri parser confuses because of second ':' in uri's path part.
77+
if tag_digest && tag_digest.start_with?('sha256:')
78+
_, hash_value = tag_digest.split(':')
79+
path += '@sha256'
80+
tag_digest = hash_value
81+
end
7082

71-
[host, path, tag]
83+
[host, path, tag_digest]
7284
end
7385

7486
private_class_method def self.official_docker_registry(host)
@@ -81,11 +93,29 @@ def self.parse_docker_uri(docker_uri)
8193
(host.exclude?('.') && host.exclude?(':') && host != 'localhost')
8294
end
8395

84-
private_class_method def self.parse_docker_repository_tag(path)
85-
path, tag = path.split(/(?=@)|:/, 2)
96+
private_class_method def self.parse_docker_tag_digest_from_path(path)
97+
# Split path into base path and digest if digest is present (after '@')
98+
base_path, digest = path.split('@', 2)
99+
100+
if digest
101+
# If digest is present and base_path contains a tag (':'), split it
102+
if base_path.include?(':')
103+
base_path, tag = base_path.split(':', 2)
104+
# Return path and combined tag@digest
105+
return [base_path, "#{tag}@#{digest}"]
106+
end
107+
108+
# Return path and digest if no tag present
109+
return [base_path, digest]
110+
end
111+
112+
# No digest present, check for tag
113+
base_path, tag = base_path.split(':', 2)
86114

87-
return [path, tag] unless tag && tag.include?('/')
115+
# If tag is present but looks like a path segment (contains '/'), treat as no tag
116+
return [base_path, 'latest'] if tag&.include?('/')
88117

89-
[path, 'latest']
118+
# Return path and tag (or nil if no tag)
119+
[base_path, tag]
90120
end
91121
end

spec/unit/lib/utils/uri_utils_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@
159159
expect(UriUtils.parse_docker_uri('publish/buildpack:tag')).to eq ['', 'publish/buildpack', 'tag']
160160

161161
actual_result = UriUtils.parse_docker_uri('publish/buildpack@sha256:e118d023acaee5cf13471ead39f68416ad6172ff0899f3257ce1481cd2b28a6a')
162-
expected_result = ['', 'publish/buildpack', '@sha256:e118d023acaee5cf13471ead39f68416ad6172ff0899f3257ce1481cd2b28a6a']
162+
expected_result = ['', 'publish/buildpack@sha256', 'e118d023acaee5cf13471ead39f68416ad6172ff0899f3257ce1481cd2b28a6a']
163163
expect(actual_result).to eq expected_result
164164

165165
actual_result = UriUtils.parse_docker_uri('publish/buildpack:tag@sha256:e118d023acaee5cf13471ead39f68416ad6172ff0899f3257ce1481cd2b28a6a')

0 commit comments

Comments
 (0)