github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/image/save/utils.go (about) 1 // Copyright © 2021 Alibaba Group Holding Ltd. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package save 16 17 import ( 18 "fmt" 19 "strings" 20 21 "github.com/sirupsen/logrus" 22 23 v1 "github.com/sealerio/sealer/types/api/v1" 24 25 "github.com/distribution/distribution/v3" 26 "github.com/opencontainers/go-digest" 27 "k8s.io/apimachinery/pkg/util/json" 28 ) 29 30 // this package contains some utils to handle docker image name 31 const ( 32 legacyDefaultDomain = "index.docker.io" 33 defaultDomain = "docker.io" 34 officialRepoName = "library" 35 defaultTag = "latest" 36 ) 37 38 // docker image name struct 39 type Named struct { 40 domain string //eg. docker.io 41 repo string //eg. library/ubuntu 42 tag string //eg. latest 43 } 44 45 func (n Named) String() string { 46 return n.Name() 47 } 48 49 func (n Named) Name() string { 50 if n.domain == "" { 51 return n.Repo() 52 } 53 return n.domain + "/" + n.Repo() 54 } 55 56 func (n Named) FullName() string { 57 return n.domain + "/" + n.repo + ":" + n.tag 58 } 59 60 func (n Named) Domain() string { 61 return n.domain 62 } 63 64 func (n Named) Repo() string { 65 return n.repo 66 } 67 68 func (n Named) Tag() string { 69 return n.tag 70 } 71 72 func splitDockerDomain(name string, registry string) (domain, remainder string) { 73 i := strings.IndexRune(name, '/') 74 if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost" && strings.ToLower(name[:i]) == name[:i]) { 75 if registry != "" { 76 domain, remainder = registry, name 77 } else { 78 domain, remainder = defaultDomain, name 79 } 80 } else { 81 domain, remainder = name[:i], name[i+1:] 82 } 83 84 if domain == legacyDefaultDomain { 85 domain = defaultDomain 86 } 87 if domain == defaultDomain && !strings.ContainsRune(remainder, '/') { 88 remainder = officialRepoName + "/" + remainder 89 } 90 return 91 } 92 93 func ParseNormalizedNamed(s string, registry string) (Named, error) { 94 domain, remainder := splitDockerDomain(s, registry) 95 var remoteName, tag string 96 if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 { 97 tag = remainder[tagSep+1:] 98 remoteName = remainder[:tagSep] 99 } else { 100 tag = defaultTag 101 remoteName = remainder 102 } 103 if strings.ToLower(remoteName) != remoteName { 104 return Named{}, fmt.Errorf("invalid reference format: repository name (%s) must be lowercase", remoteName) 105 } 106 107 named := Named{ 108 domain: domain, 109 repo: remoteName, 110 tag: tag, 111 } 112 return named, nil 113 } 114 115 // BlobList this package unmarshal blobs from json into a BlobList struct 116 // then return a slice of blob digest 117 type BlobList struct { 118 Layers []distribution.Descriptor `json:"layers"` 119 Config distribution.Descriptor `json:"config"` 120 MediaType string `json:"mediaType"` 121 Schema int `json:"schemaVersion"` 122 } 123 124 func getBlobList(blobListJSON distribution.Manifest) ([]digest.Digest, error) { 125 _, list, err := blobListJSON.Payload() 126 if err != nil { 127 return nil, fmt.Errorf("failed to get blob list: %v", err) 128 } 129 var blobList BlobList 130 err = json.Unmarshal(list, &blobList) 131 if err != nil { 132 return nil, fmt.Errorf("json unmarshal error: %v", err) 133 } 134 var blobDigests []digest.Digest 135 blobDigests = append(blobDigests, blobList.Config.Digest) 136 for _, layer := range blobList.Layers { 137 blobDigests = append(blobDigests, layer.Digest) 138 } 139 return blobDigests, nil 140 } 141 142 // ManifestList this package unmarshal manifests from json into a ManifestList struct 143 // then choose corresponding manifest by platform 144 type ManifestList struct { 145 List []ImageManifest `json:"manifests"` 146 MediaType string `json:"mediaType"` 147 Schema int `json:"schemaVersion"` 148 } 149 150 type ImageManifest struct { 151 Digest string `json:"digest"` 152 MediaType string `json:"mediaType"` 153 Platform v1.Platform `json:"platform"` 154 Size int `json:"size"` 155 } 156 157 func getImageManifestDigest(payload []byte, plat v1.Platform) (digest.Digest, error) { 158 var ( 159 manifestList ManifestList 160 ) 161 162 err := json.Unmarshal(payload, &manifestList) 163 if err != nil { 164 return "", fmt.Errorf("json unmarshal error: %v", err) 165 } 166 167 var resDigest []digest.Digest 168 for _, item := range manifestList.List { 169 if matched(item.Platform, plat) { 170 resDigest = append(resDigest, digest.Digest(item.Digest)) 171 } 172 } 173 174 if len(resDigest) == 0 { 175 return "", fmt.Errorf("no manifest of the corresponding platform") 176 } 177 178 if len(resDigest) > 1 { 179 logrus.Warn("multiple matches in manifest list") 180 } 181 return resDigest[0], nil 182 } 183 184 // Matched check if src == dest 185 func matched(src, dest v1.Platform) bool { 186 if src.OS == dest.OS && 187 src.Architecture == "arm64" && dest.Architecture == "arm64" { 188 return true 189 } 190 191 return src.OS == dest.OS && 192 src.Architecture == dest.Architecture && 193 src.Variant == dest.Variant 194 }