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 }