mirror of https://github.com/portainer/portainer
				
				
				
			
		
			
				
	
	
		
			185 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
| package images
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	dockerclient "github.com/portainer/portainer/api/docker/client"
 | |
| 
 | |
| 	"github.com/containers/image/v5/docker"
 | |
| 	imagetypes "github.com/containers/image/v5/types"
 | |
| 	"github.com/docker/docker/api/types"
 | |
| 	"github.com/opencontainers/go-digest"
 | |
| 	"github.com/pkg/errors"
 | |
| 	"github.com/rs/zerolog/log"
 | |
| )
 | |
| 
 | |
| // Options holds docker registry object options
 | |
| type Options struct {
 | |
| 	Auth    imagetypes.DockerAuthConfig
 | |
| 	Timeout time.Duration
 | |
| }
 | |
| 
 | |
| type DigestClient struct {
 | |
| 	clientFactory  *dockerclient.ClientFactory
 | |
| 	opts           Options
 | |
| 	sysCtx         *imagetypes.SystemContext
 | |
| 	registryClient *RegistryClient
 | |
| }
 | |
| 
 | |
| func NewClientWithRegistry(registryClient *RegistryClient, clientFactory *dockerclient.ClientFactory) *DigestClient {
 | |
| 	return &DigestClient{
 | |
| 		clientFactory:  clientFactory,
 | |
| 		registryClient: registryClient,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *DigestClient) RemoteDigest(image Image) (digest.Digest, error) {
 | |
| 	ctx, cancel := c.timeoutContext()
 | |
| 	defer cancel()
 | |
| 	// Docker references with both a tag and digest are currently not supported
 | |
| 	if image.Tag != "" && image.Digest != "" {
 | |
| 		err := image.trimDigest()
 | |
| 		if err != nil {
 | |
| 			return "", err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	rmRef, err := ParseReference(image.String())
 | |
| 	if err != nil {
 | |
| 		return "", errors.Wrap(err, "Cannot parse the image reference")
 | |
| 	}
 | |
| 
 | |
| 	sysCtx := c.sysCtx
 | |
| 	if c.registryClient != nil {
 | |
| 		username, password, err := c.registryClient.RegistryAuth(image)
 | |
| 		if err != nil {
 | |
| 			log.Info().Str("image up to date indicator", image.String()).Msg("No environment registry credentials found, using anonymous access")
 | |
| 		} else {
 | |
| 			sysCtx = &imagetypes.SystemContext{
 | |
| 				DockerAuthConfig: &imagetypes.DockerAuthConfig{
 | |
| 					Username: username,
 | |
| 					Password: password,
 | |
| 				},
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Retrieve remote digest through HEAD request
 | |
| 	rmDigest, err := docker.GetDigest(ctx, sysCtx, rmRef)
 | |
| 	if err != nil {
 | |
| 		// fallback to public registry for hub
 | |
| 		if image.HubLink != "" {
 | |
| 			rmDigest, err = docker.GetDigest(ctx, c.sysCtx, rmRef)
 | |
| 			if err == nil {
 | |
| 				return rmDigest, nil
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		log.Debug().Err(err).Msg("get remote digest error")
 | |
| 
 | |
| 		return "", errors.Wrap(err, "Cannot get image digest from HEAD request")
 | |
| 	}
 | |
| 
 | |
| 	return rmDigest, nil
 | |
| }
 | |
| 
 | |
| func ParseLocalImage(inspect types.ImageInspect) (*Image, error) {
 | |
| 	if IsLocalImage(inspect) || IsDanglingImage(inspect) {
 | |
| 		return nil, errors.New("the image is not regular")
 | |
| 	}
 | |
| 
 | |
| 	fromRepoDigests, err := ParseImage(ParseImageOptions{
 | |
| 		// including image name but no tag
 | |
| 		Name: inspect.RepoDigests[0],
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if IsNoTagImage(inspect) {
 | |
| 		return &fromRepoDigests, nil
 | |
| 	}
 | |
| 
 | |
| 	fromRepoTags, err := ParseImage(ParseImageOptions{
 | |
| 		Name: inspect.RepoTags[0],
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	fromRepoDigests.Tag = fromRepoTags.Tag
 | |
| 
 | |
| 	return &fromRepoDigests, nil
 | |
| }
 | |
| 
 | |
| func ParseRepoDigests(repoDigests []string) []digest.Digest {
 | |
| 	digests := make([]digest.Digest, 0)
 | |
| 	for _, repoDigest := range repoDigests {
 | |
| 		d := ParseRepoDigest(repoDigest)
 | |
| 		if d == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		digests = append(digests, d)
 | |
| 	}
 | |
| 
 | |
| 	return digests
 | |
| }
 | |
| 
 | |
| func ParseRepoTags(repoTags []string) []*Image {
 | |
| 	images := make([]*Image, 0)
 | |
| 	for _, repoTag := range repoTags {
 | |
| 		image := ParseRepoTag(repoTag)
 | |
| 		if image != nil {
 | |
| 			images = append(images, image)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return images
 | |
| }
 | |
| 
 | |
| func ParseRepoDigest(repoDigest string) digest.Digest {
 | |
| 	if !strings.ContainsAny(repoDigest, "@") {
 | |
| 		return ""
 | |
| 	}
 | |
| 
 | |
| 	d, err := digest.Parse(strings.Split(repoDigest, "@")[1])
 | |
| 	if err != nil {
 | |
| 		log.Warn().Msgf("Skip invalid repo digest item: %s [error: %v]", repoDigest, err)
 | |
| 
 | |
| 		return ""
 | |
| 	}
 | |
| 
 | |
| 	return d
 | |
| }
 | |
| 
 | |
| func ParseRepoTag(repoTag string) *Image {
 | |
| 	if repoTag == "" {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	image, err := ParseImage(ParseImageOptions{
 | |
| 		Name: repoTag,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		log.Warn().Err(err).Str("repoTag", repoTag).Msg("RepoTag cannot be parsed.")
 | |
| 
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	return &image
 | |
| }
 | |
| 
 | |
| func (c *DigestClient) timeoutContext() (context.Context, context.CancelFunc) {
 | |
| 	ctx := context.Background()
 | |
| 	var cancel context.CancelFunc = func() {}
 | |
| 
 | |
| 	if c.opts.Timeout > 0 {
 | |
| 		ctx, cancel = context.WithTimeout(ctx, c.opts.Timeout)
 | |
| 	}
 | |
| 
 | |
| 	return ctx, cancel
 | |
| }
 |