github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/libpod/image/utils.go (about)

     1  package image
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/url"
     7  	"regexp"
     8  	"strings"
     9  
    10  	cp "github.com/containers/image/v5/copy"
    11  	"github.com/containers/image/v5/docker/reference"
    12  	"github.com/containers/image/v5/signature"
    13  	"github.com/containers/image/v5/types"
    14  	"github.com/containers/storage"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  // findImageInRepotags takes an imageParts struct and searches images' repotags for
    19  // a match on name:tag
    20  func findImageInRepotags(search imageParts, images []*Image) (*storage.Image, error) {
    21  	_, searchName, searchSuspiciousTagValueForSearch := search.suspiciousRefNameTagValuesForSearch()
    22  	var results []*storage.Image
    23  	for _, image := range images {
    24  		for _, name := range image.Names() {
    25  			d, err := decompose(name)
    26  			// if we get an error, ignore and keep going
    27  			if err != nil {
    28  				continue
    29  			}
    30  			_, dName, dSuspiciousTagValueForSearch := d.suspiciousRefNameTagValuesForSearch()
    31  			if dName == searchName && dSuspiciousTagValueForSearch == searchSuspiciousTagValueForSearch {
    32  				results = append(results, image.image)
    33  				continue
    34  			}
    35  			// account for registry:/somedir/image
    36  			if strings.HasSuffix(dName, searchName) && dSuspiciousTagValueForSearch == searchSuspiciousTagValueForSearch {
    37  				results = append(results, image.image)
    38  				continue
    39  			}
    40  		}
    41  	}
    42  	if len(results) == 0 {
    43  		return &storage.Image{}, errors.Errorf("unable to find a name and tag match for %s in repotags", searchName)
    44  	} else if len(results) > 1 {
    45  		return &storage.Image{}, errors.Errorf("found multiple name and tag matches for %s in repotags", searchName)
    46  	}
    47  	return results[0], nil
    48  }
    49  
    50  // getCopyOptions constructs a new containers/image/copy.Options{} struct from the given parameters, inheriting some from sc.
    51  func getCopyOptions(sc *types.SystemContext, reportWriter io.Writer, srcDockerRegistry, destDockerRegistry *DockerRegistryOptions, signing SigningOptions, manifestType string, additionalDockerArchiveTags []reference.NamedTagged) *cp.Options {
    52  	if srcDockerRegistry == nil {
    53  		srcDockerRegistry = &DockerRegistryOptions{}
    54  	}
    55  	if destDockerRegistry == nil {
    56  		destDockerRegistry = &DockerRegistryOptions{}
    57  	}
    58  	srcContext := srcDockerRegistry.GetSystemContext(sc, additionalDockerArchiveTags)
    59  	destContext := destDockerRegistry.GetSystemContext(sc, additionalDockerArchiveTags)
    60  	return &cp.Options{
    61  		RemoveSignatures:      signing.RemoveSignatures,
    62  		SignBy:                signing.SignBy,
    63  		ReportWriter:          reportWriter,
    64  		SourceCtx:             srcContext,
    65  		DestinationCtx:        destContext,
    66  		ForceManifestMIMEType: manifestType,
    67  	}
    68  }
    69  
    70  // getPolicyContext sets up, initializes and returns a new context for the specified policy
    71  func getPolicyContext(ctx *types.SystemContext) (*signature.PolicyContext, error) {
    72  	policy, err := signature.DefaultPolicy(ctx)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	policyContext, err := signature.NewPolicyContext(policy)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	return policyContext, nil
    82  }
    83  
    84  // hasTransport determines if the image string contains '://', returns bool
    85  func hasTransport(image string) bool {
    86  	return strings.Contains(image, "://")
    87  }
    88  
    89  // ReposToMap parses the specified repotags and returns a map with repositories
    90  // as keys and the corresponding arrays of tags or digests-as-strings as values.
    91  func ReposToMap(names []string) (map[string][]string, error) {
    92  	// map format is repo -> []tag-or-digest
    93  	repos := make(map[string][]string)
    94  	for _, name := range names {
    95  		var repository, tag string
    96  		if len(name) > 0 {
    97  			named, err := reference.ParseNormalizedNamed(name)
    98  			if err != nil {
    99  				return nil, err
   100  			}
   101  			repository = named.Name()
   102  			if ref, ok := named.(reference.NamedTagged); ok {
   103  				tag = ref.Tag()
   104  			} else if ref, ok := named.(reference.Canonical); ok {
   105  				tag = ref.Digest().String()
   106  			}
   107  		}
   108  		repos[repository] = append(repos[repository], tag)
   109  	}
   110  	if len(repos) == 0 {
   111  		repos["<none>"] = []string{"<none>"}
   112  	}
   113  	return repos, nil
   114  }
   115  
   116  // GetAdditionalTags returns a list of reference.NamedTagged for the
   117  // additional tags given in images
   118  func GetAdditionalTags(images []string) ([]reference.NamedTagged, error) {
   119  	var allTags []reference.NamedTagged
   120  	for _, img := range images {
   121  		ref, err := reference.ParseNormalizedNamed(img)
   122  		if err != nil {
   123  			return nil, errors.Wrapf(err, "error parsing additional tags")
   124  		}
   125  		refTagged, isTagged := ref.(reference.NamedTagged)
   126  		if isTagged {
   127  			allTags = append(allTags, refTagged)
   128  		}
   129  	}
   130  	return allTags, nil
   131  }
   132  
   133  // IsValidImageURI checks if image name has valid format
   134  func IsValidImageURI(imguri string) (bool, error) {
   135  	uri := "http://" + imguri
   136  	u, err := url.Parse(uri)
   137  	if err != nil {
   138  		return false, errors.Wrapf(err, "invalid image uri: %s", imguri)
   139  	}
   140  	reg := regexp.MustCompile(`^[a-zA-Z0-9-_\.]+\/?:?[0-9]*[a-z0-9-\/:]*$`)
   141  	ret := reg.FindAllString(u.Host, -1)
   142  	if len(ret) == 0 {
   143  		return false, errors.Wrapf(err, "invalid image uri: %s", imguri)
   144  	}
   145  	reg = regexp.MustCompile(`^[a-z0-9-:\./]*$`)
   146  	ret = reg.FindAllString(u.Fragment, -1)
   147  	if len(ret) == 0 {
   148  		return false, errors.Wrapf(err, "invalid image uri: %s", imguri)
   149  	}
   150  	return true, nil
   151  }
   152  
   153  // imageNameForSaveDestination returns a Docker-like reference appropriate for saving img,
   154  // which the user referred to as imgUserInput; or an empty string, if there is no appropriate
   155  // reference.
   156  func imageNameForSaveDestination(img *Image, imgUserInput string) string {
   157  	if strings.Contains(img.ID(), imgUserInput) {
   158  		return ""
   159  	}
   160  
   161  	prepend := ""
   162  	localRegistryPrefix := fmt.Sprintf("%s/", DefaultLocalRegistry)
   163  	if !strings.HasPrefix(imgUserInput, localRegistryPrefix) {
   164  		// we need to check if localhost was added to the image name in NewFromLocal
   165  		for _, name := range img.Names() {
   166  			// If the user is saving an image in the localhost registry,  getLocalImage need
   167  			// a name that matches the format localhost/<tag1>:<tag2> or localhost/<tag>:latest to correctly
   168  			// set up the manifest and save.
   169  			if strings.HasPrefix(name, localRegistryPrefix) && (strings.HasSuffix(name, imgUserInput) || strings.HasSuffix(name, fmt.Sprintf("%s:latest", imgUserInput))) {
   170  				prepend = localRegistryPrefix
   171  				break
   172  			}
   173  		}
   174  	}
   175  	return fmt.Sprintf("%s%s", prepend, imgUserInput)
   176  }