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  }