11/*
2- * Copyright 2012-2023 the original author or authors.
2+ * Copyright 2012-2022 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1616
1717package org .springframework .boot .docker .compose .core ;
1818
19+ import java .util .regex .Matcher ;
20+
21+ import org .springframework .util .Assert ;
22+ import org .springframework .util .ObjectUtils ;
23+
1924/**
20- * A docker image reference of form
21- * {@code [<registry>/][<project>/]<image>[:<tag>|@<digest>]}.
25+ * A reference to a Docker image of the form {@code "imagename[:tag|@digest]"}.
2226 *
23- * @author Moritz Halbritter
24- * @author Andy Wilkinson
2527 * @author Phillip Webb
26- * @since 3.1.0
27- * @see <a href="https://docs.docker.com/compose/compose-file/#image">docker
28- * documentation</a>
28+ * @author Scott Frederick
29+ * @since 2.3.0
2930 */
3031public final class ImageReference {
3132
32- private final String reference ;
33+ private final ImageName name ;
34+
35+ private final String tag ;
36+
37+ private final String digest ;
38+
39+ private final String string ;
40+
41+ private ImageReference (ImageName name , String tag , String digest ) {
42+ Assert .notNull (name , "Name must not be null" );
43+ this .name = name ;
44+ this .tag = tag ;
45+ this .digest = digest ;
46+ this .string = buildString (name .toString (), tag , digest );
47+ }
48+
49+ /**
50+ * Return the domain for this image name.
51+ * @return the domain
52+ * @see ImageName#getDomain()
53+ */
54+ public String getDomain () {
55+ return this .name .getDomain ();
56+ }
57+
58+ /**
59+ * Return the name of this image.
60+ * @return the image name
61+ * @see ImageName#getName()
62+ */
63+ public String getName () {
64+ return this .name .getName ();
65+ }
3366
34- private final String imageName ;
67+ /**
68+ * Return the tag from the reference or {@code null}.
69+ * @return the referenced tag
70+ */
71+ public String getTag () {
72+ return this .tag ;
73+ }
3574
36- ImageReference (String reference ) {
37- this .reference = reference ;
38- int lastSlashIndex = reference .lastIndexOf ('/' );
39- String imageTagDigest = (lastSlashIndex != -1 ) ? reference .substring (lastSlashIndex + 1 ) : reference ;
40- int digestIndex = imageTagDigest .indexOf ('@' );
41- String imageTag = (digestIndex != -1 ) ? imageTagDigest .substring (0 , digestIndex ) : imageTagDigest ;
42- int colon = imageTag .indexOf (':' );
43- this .imageName = (colon != -1 ) ? imageTag .substring (0 , colon ) : imageTag ;
75+ /**
76+ * Return the digest from the reference or {@code null}.
77+ * @return the referenced digest
78+ */
79+ public String getDigest () {
80+ return this .digest ;
4481 }
4582
4683 @ Override
@@ -52,35 +89,84 @@ public boolean equals(Object obj) {
5289 return false ;
5390 }
5491 ImageReference other = (ImageReference ) obj ;
55- return this .reference .equals (other .reference );
92+ boolean result = true ;
93+ result = result && this .name .equals (other .name );
94+ result = result && ObjectUtils .nullSafeEquals (this .tag , other .tag );
95+ result = result && ObjectUtils .nullSafeEquals (this .digest , other .digest );
96+ return result ;
5697 }
5798
5899 @ Override
59100 public int hashCode () {
60- return this .reference .hashCode ();
101+ final int prime = 31 ;
102+ int result = 1 ;
103+ result = prime * result + this .name .hashCode ();
104+ result = prime * result + ObjectUtils .nullSafeHashCode (this .tag );
105+ result = prime * result + ObjectUtils .nullSafeHashCode (this .digest );
106+ return result ;
61107 }
62108
63109 @ Override
64110 public String toString () {
65- return this .reference ;
111+ return this .string ;
66112 }
67113
68- /**
69- * Return the referenced image, excluding the registry or project. For example, a
70- * reference of {@code my_private.registry:5000/redis:5} would return {@code redis}.
71- * @return the referenced image
72- */
73- public String getImageName () {
74- return this .imageName ;
114+ private String buildString (String name , String tag , String digest ) {
115+ StringBuilder string = new StringBuilder (name );
116+ if (tag != null ) {
117+ string .append (":" ).append (tag );
118+ }
119+ if (digest != null ) {
120+ string .append ("@" ).append (digest );
121+ }
122+ return string .toString ();
75123 }
76124
77125 /**
78- * Create an image reference from the given String value.
79- * @param value the string used to create the reference
126+ * Create a new {@link ImageReference} from the given value. The following value forms
127+ * can be used:
128+ * <ul>
129+ * <li>{@code name} (maps to {@code docker.io/library/name})</li>
130+ * <li>{@code domain/name}</li>
131+ * <li>{@code domain:port/name}</li>
132+ * <li>{@code domain:port/name:tag}</li>
133+ * <li>{@code domain:port/name@digest}</li>
134+ * </ul>
135+ * @param value the value to parse
80136 * @return an {@link ImageReference} instance
81137 */
82138 public static ImageReference of (String value ) {
83- return (value != null ) ? new ImageReference (value ) : null ;
139+ Assert .hasText (value , "Value must not be null" );
140+ String domain = ImageName .parseDomain (value );
141+ String path = (domain != null ) ? value .substring (domain .length () + 1 ) : value ;
142+ String digest = null ;
143+ int digestSplit = path .indexOf ("@" );
144+ if (digestSplit != -1 ) {
145+ String remainder = path .substring (digestSplit + 1 );
146+ Matcher matcher = Regex .DIGEST .matcher (remainder );
147+ if (matcher .find ()) {
148+ digest = remainder .substring (0 , matcher .end ());
149+ remainder = remainder .substring (matcher .end ());
150+ path = path .substring (0 , digestSplit ) + remainder ;
151+ }
152+ }
153+ String tag = null ;
154+ int tagSplit = path .lastIndexOf (":" );
155+ if (tagSplit != -1 ) {
156+ String remainder = path .substring (tagSplit + 1 );
157+ Matcher matcher = Regex .TAG .matcher (remainder );
158+ if (matcher .find ()) {
159+ tag = remainder .substring (0 , matcher .end ());
160+ remainder = remainder .substring (matcher .end ());
161+ path = path .substring (0 , tagSplit ) + remainder ;
162+ }
163+ }
164+ Assert .isTrue (Regex .PATH .matcher (path ).matches (),
165+ () -> "Unable to parse image reference \" " + value + "\" . "
166+ + "Image reference must be in the form '[domainHost:port/][path/]name[:tag][@digest]', "
167+ + "with 'path' and 'name' containing only [a-z0-9][.][_][-]" );
168+ ImageName name = new ImageName (domain , path );
169+ return new ImageReference (name , tag , digest );
84170 }
85171
86172}
0 commit comments