github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/cache/lookup.go (about)

     1  /*
     2  Copyright 2019 The Skaffold 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 cache
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"sync"
    24  
    25  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
    26  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/instrumentation"
    27  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    28  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform"
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/tag"
    31  )
    32  
    33  func (c *cache) lookupArtifacts(ctx context.Context, tags tag.ImageTags, platforms platform.Resolver, artifacts []*latest.Artifact) []cacheDetails {
    34  	details := make([]cacheDetails, len(artifacts))
    35  	// Create a new `artifactHasher` on every new dev loop.
    36  	// This way every artifact hash is calculated at most once in a single dev loop, and recalculated on every dev loop.
    37  
    38  	ctx, endTrace := instrumentation.StartTrace(ctx, "lookupArtifacts_CacheLookupArtifacts")
    39  	defer endTrace()
    40  	h := newArtifactHasherFunc(c.artifactGraph, c.lister, c.cfg.Mode())
    41  	var wg sync.WaitGroup
    42  	for i := range artifacts {
    43  		wg.Add(1)
    44  
    45  		i := i
    46  		go func() {
    47  			details[i] = c.lookup(ctx, artifacts[i], tags[artifacts[i].ImageName], platforms, h)
    48  			wg.Done()
    49  		}()
    50  	}
    51  	wg.Wait()
    52  
    53  	return details
    54  }
    55  
    56  func (c *cache) lookup(ctx context.Context, a *latest.Artifact, tag string, platforms platform.Resolver, h artifactHasher) cacheDetails {
    57  	ctx, endTrace := instrumentation.StartTrace(ctx, "lookup_CacheLookupOneArtifact", map[string]string{
    58  		"ImageName": instrumentation.PII(a.ImageName),
    59  	})
    60  	defer endTrace()
    61  
    62  	hash, err := h.hash(ctx, a, platforms)
    63  	if err != nil {
    64  		return failed{err: fmt.Errorf("getting hash for artifact %q: %s", a.ImageName, err)}
    65  	}
    66  
    67  	c.cacheMutex.RLock()
    68  	entry, cacheHit := c.artifactCache[hash]
    69  	c.cacheMutex.RUnlock()
    70  	if !cacheHit {
    71  		if entry, err = c.tryImport(ctx, a, tag, hash); err != nil {
    72  			log.Entry(ctx).Debugf("Could not import artifact from Docker, building instead (%s)", err)
    73  			return needsBuilding{hash: hash}
    74  		}
    75  	}
    76  
    77  	if isLocal, err := c.isLocalImage(a.ImageName); err != nil {
    78  		return failed{err}
    79  	} else if isLocal {
    80  		return c.lookupLocal(ctx, hash, tag, entry)
    81  	}
    82  	return c.lookupRemote(ctx, hash, tag, entry)
    83  }
    84  
    85  func (c *cache) lookupLocal(ctx context.Context, hash, tag string, entry ImageDetails) cacheDetails {
    86  	if entry.ID == "" {
    87  		return needsBuilding{hash: hash}
    88  	}
    89  
    90  	// Check the imageID for the tag
    91  	idForTag, err := c.client.ImageID(ctx, tag)
    92  	if err != nil {
    93  		// Rely on actionable errors thrown from pkg/skaffold/docker.LocalDaemon api.
    94  		return failed{err: err}
    95  	}
    96  
    97  	// Image exists locally with the same tag
    98  	if idForTag == entry.ID {
    99  		return found{hash: hash}
   100  	}
   101  
   102  	// Image exists locally with a different tag
   103  	if c.client.ImageExists(ctx, entry.ID) {
   104  		return needsLocalTagging{hash: hash, tag: tag, imageID: entry.ID}
   105  	}
   106  
   107  	return needsBuilding{hash: hash}
   108  }
   109  
   110  func (c *cache) lookupRemote(ctx context.Context, hash, tag string, entry ImageDetails) cacheDetails {
   111  	if remoteDigest, err := docker.RemoteDigest(tag, c.cfg, nil); err == nil {
   112  		// Image exists remotely with the same tag and digest
   113  		if remoteDigest == entry.Digest {
   114  			return found{hash: hash}
   115  		}
   116  	}
   117  
   118  	// Image exists remotely with a different tag
   119  	fqn := tag + "@" + entry.Digest // Actual tag will be ignored but we need the registry and the digest part of it.
   120  	if remoteDigest, err := docker.RemoteDigest(fqn, c.cfg, nil); err == nil {
   121  		if remoteDigest == entry.Digest {
   122  			return needsRemoteTagging{hash: hash, tag: tag, digest: entry.Digest}
   123  		}
   124  	}
   125  
   126  	// Image exists locally
   127  	if entry.ID != "" && c.client != nil && c.client.ImageExists(ctx, entry.ID) {
   128  		return needsPushing{hash: hash, tag: tag, imageID: entry.ID}
   129  	}
   130  
   131  	return needsBuilding{hash: hash}
   132  }
   133  
   134  func (c *cache) tryImport(ctx context.Context, a *latest.Artifact, tag string, hash string) (ImageDetails, error) {
   135  	entry := ImageDetails{}
   136  
   137  	if importMissing, err := c.importMissingImage(a.ImageName); err != nil {
   138  		return entry, err
   139  	} else if !importMissing {
   140  		return ImageDetails{}, fmt.Errorf("import of missing images disabled")
   141  	}
   142  
   143  	if !c.client.ImageExists(ctx, tag) {
   144  		log.Entry(ctx).Debugf("Importing artifact %s from docker registry", tag)
   145  		err := c.client.Pull(ctx, ioutil.Discard, tag)
   146  		if err != nil {
   147  			return entry, err
   148  		}
   149  	} else {
   150  		log.Entry(ctx).Debugf("Importing artifact %s from local docker", tag)
   151  	}
   152  
   153  	imageID, err := c.client.ImageID(ctx, tag)
   154  	if err != nil {
   155  		return entry, err
   156  	}
   157  
   158  	if imageID != "" {
   159  		entry.ID = imageID
   160  	}
   161  
   162  	if digest, err := docker.RemoteDigest(tag, c.cfg, nil); err == nil {
   163  		log.Entry(ctx).Debugf("Added digest for %s to cache entry", tag)
   164  		entry.Digest = digest
   165  	}
   166  
   167  	c.cacheMutex.Lock()
   168  	c.artifactCache[hash] = entry
   169  	c.cacheMutex.Unlock()
   170  	return entry, nil
   171  }