sigs.k8s.io/cluster-api@v1.7.1/util/container/image.go (about) 1 /* 2 Copyright 2020 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package container implements container utility functionality. 18 package container 19 20 import ( 21 _ "crypto/sha256" // Import the crypto/sha256 algorithm for the docker image parser to work with sha256 hashes. 22 _ "crypto/sha512" // Import the crypto/sha512 algorithm for the docker image parser to work with 384 and 512 sha hashes. 23 "fmt" 24 "path" 25 "regexp" 26 27 "github.com/distribution/reference" 28 "github.com/pkg/errors" 29 ) 30 31 var ( 32 ociTagAllowedChars = regexp.MustCompile(`[^-a-zA-Z0-9_\.]`) 33 ) 34 35 // Image type represents the container image details. 36 type Image struct { 37 Repository string 38 Name string 39 Tag string 40 Digest string 41 } 42 43 // ImageFromString parses a docker image string into three parts: repo, tag and digest. 44 func ImageFromString(image string) (Image, error) { 45 named, err := reference.ParseNamed(image) 46 if err != nil { 47 return Image{}, fmt.Errorf("couldn't parse image name: %v", err) 48 } 49 50 var repo, tag, digest string 51 _, nameOnly := path.Split(reference.Path(named)) 52 if nameOnly != "" { 53 // split out the part of the name after the last / 54 lenOfCompleteName := len(named.Name()) 55 repo = named.Name()[:lenOfCompleteName-len(nameOnly)-1] 56 } 57 58 tagged, ok := named.(reference.Tagged) 59 if ok { 60 tag = tagged.Tag() 61 } 62 63 digested, ok := named.(reference.Digested) 64 if ok { 65 digest = digested.Digest().String() 66 } 67 68 return Image{Repository: repo, Name: nameOnly, Tag: tag, Digest: digest}, nil 69 } 70 71 func (i Image) String() string { 72 // repo/name [ ":" tag ] [ "@" digest ] 73 ref := fmt.Sprintf("%s/%s", i.Repository, i.Name) 74 if i.Tag != "" { 75 ref = fmt.Sprintf("%s:%s", ref, i.Tag) 76 } 77 if i.Digest != "" { 78 ref = fmt.Sprintf("%s@%s", ref, i.Digest) 79 } 80 return ref 81 } 82 83 // ModifyImageRepository takes an imageName (e.g., repository/image:tag), and returns an image name with updated repository. 84 func ModifyImageRepository(imageName, repositoryName string) (string, error) { 85 image, err := ImageFromString(imageName) 86 if err != nil { 87 return "", errors.Wrap(err, "failed to parse image name") 88 } 89 nameUpdated, err := reference.WithName(path.Join(repositoryName, image.Name)) 90 if err != nil { 91 return "", errors.Wrap(err, "failed to update repository name") 92 } 93 if image.Tag != "" { 94 retagged, err := reference.WithTag(nameUpdated, image.Tag) 95 if err != nil { 96 return "", errors.Wrap(err, "failed to parse image tag") 97 } 98 return reference.FamiliarString(retagged), nil 99 } 100 return "", errors.New("image must be tagged") 101 } 102 103 // ModifyImageTag takes an imageName (e.g., repository/image:tag), and returns an image name with updated tag. 104 func ModifyImageTag(imageName, tagName string) (string, error) { 105 normalisedTagName := SemverToOCIImageTag(tagName) 106 107 namedRef, err := reference.ParseNormalizedNamed(imageName) 108 if err != nil { 109 return "", errors.Wrap(err, "failed to parse image name") 110 } 111 // return error if images use digest as version instead of tag 112 if _, isCanonical := namedRef.(reference.Canonical); isCanonical { 113 return "", errors.New("image uses digest as version, cannot update tag ") 114 } 115 116 // update the image tag with tagName 117 namedTagged, err := reference.WithTag(namedRef, normalisedTagName) 118 if err != nil { 119 return "", errors.Wrap(err, "failed to update image tag") 120 } 121 122 return reference.TagNameOnly(namedTagged).String(), nil 123 } 124 125 // ImageTagIsValid ensures that a given image tag is compliant with the OCI spec. 126 func ImageTagIsValid(tagName string) bool { 127 return !ociTagAllowedChars.MatchString(tagName) 128 } 129 130 // SemverToOCIImageTag is a helper function that replaces all 131 // non-allowed symbols in tag strings with underscores. 132 // Image tag can only contain lowercase and uppercase letters, digits, 133 // underscores, periods and dashes. 134 // Current usage is for CI images where all of symbols except '+' are valid, 135 // but function is for generic usage where input can't be always pre-validated. 136 // Taken from k8s.io/cmd/kubeadm/app/util. 137 func SemverToOCIImageTag(version string) string { 138 return ociTagAllowedChars.ReplaceAllString(version, "_") 139 }