Skip to content

Commit 49c70bc

Browse files
author
Priya Wadhwa
committed
Reorganized to get details about a cached artifact
Get details about an artifact first (if it needs to be rebuilt, retagged or pushed), and then execute. Also added a unit test for this.
1 parent 555bd81 commit 49c70bc

File tree

2 files changed

+216
-45
lines changed

2 files changed

+216
-45
lines changed

pkg/skaffold/build/cache.go

+54-45
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import (
3535
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
3636
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
3737
"github.com/google/go-containerregistry/pkg/name"
38-
"github.com/google/go-containerregistry/pkg/v1/remote"
3938
homedir "github.com/mitchellh/go-homedir"
4039
"github.com/pkg/errors"
4140
"github.com/sirupsen/logrus"
@@ -63,13 +62,10 @@ var (
6362
// For testing
6463
hashForArtifact = getHashForArtifact
6564
localCluster = config.GetLocalCluster
65+
remoteDigest = docker.RemoteDigest
6666
noCache = &Cache{}
6767
)
6868

69-
const (
70-
prefixLength = len("sha256:")
71-
)
72-
7369
// NewCache returns the current state of the cache
7470
func NewCache(useCache bool, cacheFile string) *Cache {
7571
if !useCache {
@@ -132,7 +128,7 @@ func (c *Cache) RetrieveCachedArtifacts(ctx context.Context, out io.Writer, arti
132128
var needToBuild []*latest.Artifact
133129
var built []Artifact
134130
for _, a := range artifacts {
135-
artifact, err := c.retrieveCachedArtifact(ctx, out, a)
131+
artifact, err := c.resolveCachedArtifact(ctx, out, a)
136132
if err != nil {
137133
logrus.Debugf("error retrieving cached artifact for %s: %v\n", a.ImageName, err)
138134
needToBuild = append(needToBuild, a)
@@ -147,40 +143,67 @@ func (c *Cache) RetrieveCachedArtifacts(ctx context.Context, out io.Writer, arti
147143
return needToBuild, built
148144
}
149145

150-
func (c *Cache) retrieveCachedArtifact(ctx context.Context, out io.Writer, a *latest.Artifact) (*Artifact, error) {
146+
func (c *Cache) resolveCachedArtifact(ctx context.Context, out io.Writer, a *latest.Artifact) (*Artifact, error) {
147+
details, err := c.retrieveCachedArtifactDetails(ctx, out, a)
148+
if err != nil {
149+
return nil, errors.Wrap(err, "getting cached artifact details")
150+
}
151+
if details.needsRebuild {
152+
return nil, nil
153+
}
154+
color.Green.Fprintf(out, "Found %s locally, retagging and pushing if necessary ...\n", a.ImageName)
155+
if details.needsRetag {
156+
if err := c.client.Tag(ctx, details.prebuiltImage, details.hashTag); err != nil {
157+
return nil, errors.Wrap(err, "retagging image")
158+
}
159+
}
160+
if details.needsPush {
161+
if _, err := c.client.Push(ctx, out, details.hashTag); err != nil {
162+
return nil, errors.Wrap(err, "pushing image")
163+
}
164+
}
165+
color.Green.Fprintf(out, "Resolved %s, skipping rebuild.\n", details.hashTag)
166+
return &Artifact{
167+
ImageName: a.ImageName,
168+
Tag: details.hashTag,
169+
}, nil
170+
}
171+
172+
type cachedArtifactDetails struct {
173+
needsRebuild bool
174+
needsRetag bool
175+
needsPush bool
176+
prebuiltImage string
177+
hashTag string
178+
}
179+
180+
func (c *Cache) retrieveCachedArtifactDetails(ctx context.Context, out io.Writer, a *latest.Artifact) (*cachedArtifactDetails, error) {
151181
hash, err := hashForArtifact(ctx, a)
152182
if err != nil {
153183
return nil, errors.Wrapf(err, "getting hash for artifact %s", a.ImageName)
154184
}
155185
a.WorkspaceHash = hash
186+
localCluster, _ := localCluster()
156187
imageDetails, cacheHit := c.artifactCache[hash]
157188
if !cacheHit {
158-
return nil, nil
189+
return &cachedArtifactDetails{
190+
needsRebuild: true,
191+
}, nil
159192
}
160193
hashTag := fmt.Sprintf("%s:%s", a.ImageName, hash)
161194

162195
// Check if we are using a local cluster
163-
local, _ := localCluster()
164196
var existsRemotely bool
165-
if !local {
197+
if !localCluster {
166198
// Check if tagged image exists remotely with the same digest
167199
existsRemotely = imageExistsRemotely(hashTag, imageDetails.Digest)
168200
}
169201

170202
// See if this image exists in the local daemon
171203
if c.client.ImageExists(ctx, hashTag) {
172-
color.Green.Fprintf(out, "Found %s locally...\n", a.ImageName)
173-
// Push if the image doesn't exist remotely
174-
if !existsRemotely && !local {
175-
color.Green.Fprintf(out, "Pushing %s since it doesn't exist remotely...\n", a.ImageName)
176-
if _, err := c.client.Push(ctx, out, hashTag); err != nil {
177-
return nil, errors.Wrapf(err, "pushing %s", hashTag)
178-
}
179-
}
180-
color.Green.Fprintf(out, "%s ready, skipping rebuild\n", hashTag)
181-
return &Artifact{
182-
ImageName: a.ImageName,
183-
Tag: hashTag,
204+
return &cachedArtifactDetails{
205+
needsPush: !existsRemotely && !localCluster,
206+
hashTag: hashTag,
184207
}, nil
185208
}
186209
// Check for a local image with the same digest as the image we want to build
@@ -191,20 +214,12 @@ func (c *Cache) retrieveCachedArtifact(ctx context.Context, out io.Writer, a *la
191214
if prebuiltImage == "" {
192215
return nil, errors.New("no tagged prebuilt image")
193216
}
194-
color.Green.Fprintf(out, "Found %s locally, retagging and pushing...\n", a.ImageName)
195-
// Retag the image
196-
if err := c.client.Tag(ctx, prebuiltImage, hashTag); err != nil {
197-
return nil, errors.Wrap(err, "retagging image")
198-
}
199-
// Push the retagged image
200-
if _, err := c.client.Push(ctx, out, hashTag); err != nil {
201-
return nil, errors.Wrap(err, "pushing image")
202-
}
203217

204-
color.Green.Fprintf(out, "Retagged %s, skipping rebuild.\n", prebuiltImage)
205-
return &Artifact{
206-
ImageName: a.ImageName,
207-
Tag: hashTag,
218+
return &cachedArtifactDetails{
219+
needsRetag: true,
220+
needsPush: !localCluster,
221+
prebuiltImage: prebuiltImage,
222+
hashTag: hashTag,
208223
}, nil
209224
}
210225

@@ -230,21 +245,15 @@ func (c *Cache) retrievePrebuiltImage(ctx context.Context, details ImageDetails)
230245

231246
func imageExistsRemotely(image, digest string) bool {
232247
if digest == "" {
248+
logrus.Debugf("Checking if %s exists remotely, but digest is empty", image)
233249
return false
234250
}
235-
ref, err := name.ParseReference(image, name.WeakValidation)
236-
if err != nil {
237-
return false
238-
}
239-
img, err := remote.Image(ref)
240-
if err != nil {
241-
return false
242-
}
243-
d, err := img.Digest()
251+
d, err := remoteDigest(image)
244252
if err != nil {
253+
logrus.Debugf("Checking if %s exists remotely, can't get digest: %v", image, err)
245254
return false
246255
}
247-
return d.Hex == digest[prefixLength:]
256+
return d == digest
248257
}
249258

250259
// CacheArtifacts determines the hash for each artifact, stores it in the artifact cache, and saves the cache at the end

pkg/skaffold/build/cache_test.go

+162
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,165 @@ func createTempCacheFile(t *testing.T, cacheFileContents interface{}) string {
208208
}
209209
return temp.Name()
210210
}
211+
212+
func TestRetrieveCachedArtifactDetails(t *testing.T) {
213+
tests := []struct {
214+
name string
215+
localCluster bool
216+
artifact *latest.Artifact
217+
hashes map[string]string
218+
digest string
219+
api testutil.FakeAPIClient
220+
cache *Cache
221+
expected *cachedArtifactDetails
222+
}{
223+
{
224+
name: "image doesn't exist in cache, remote cluster",
225+
artifact: &latest.Artifact{ImageName: "image"},
226+
hashes: map[string]string{"image": "hash"},
227+
cache: noCache,
228+
expected: &cachedArtifactDetails{
229+
needsRebuild: true,
230+
},
231+
},
232+
{
233+
name: "image doesn't exist in cache, local cluster",
234+
artifact: &latest.Artifact{ImageName: "image"},
235+
hashes: map[string]string{"image": "hash"},
236+
localCluster: true,
237+
cache: noCache,
238+
expected: &cachedArtifactDetails{
239+
needsRebuild: true,
240+
},
241+
},
242+
{
243+
name: "image in cache and exists remotely, remote cluster",
244+
artifact: &latest.Artifact{ImageName: "image"},
245+
hashes: map[string]string{"image": "hash"},
246+
api: testutil.FakeAPIClient{
247+
TagToImageID: map[string]string{"image:hash": "image:tag"},
248+
ImageSummaries: []types.ImageSummary{
249+
{
250+
RepoDigests: []string{"digest"},
251+
RepoTags: []string{"image:hash"},
252+
},
253+
},
254+
},
255+
cache: &Cache{
256+
useCache: true,
257+
artifactCache: ArtifactCache{"hash": ImageDetails{Digest: "digest"}},
258+
},
259+
digest: "digest",
260+
expected: &cachedArtifactDetails{
261+
hashTag: "image:hash",
262+
},
263+
},
264+
{
265+
name: "image in cache and exists in daemon, local cluster",
266+
artifact: &latest.Artifact{ImageName: "image"},
267+
hashes: map[string]string{"image": "hash"},
268+
localCluster: true,
269+
api: testutil.FakeAPIClient{
270+
TagToImageID: map[string]string{"image:hash": "image:tag"},
271+
ImageSummaries: []types.ImageSummary{
272+
{
273+
RepoDigests: []string{"digest"},
274+
RepoTags: []string{"image:hash"},
275+
},
276+
},
277+
},
278+
cache: &Cache{
279+
useCache: true,
280+
artifactCache: ArtifactCache{"hash": ImageDetails{Digest: "digest"}},
281+
},
282+
digest: "digest",
283+
expected: &cachedArtifactDetails{
284+
hashTag: "image:hash",
285+
},
286+
},
287+
{
288+
name: "image in cache, prebuilt image exists, remote cluster",
289+
artifact: &latest.Artifact{ImageName: "image"},
290+
hashes: map[string]string{"image": "hash"},
291+
api: testutil.FakeAPIClient{
292+
ImageSummaries: []types.ImageSummary{
293+
{
294+
RepoDigests: []string{"digest"},
295+
RepoTags: []string{"anotherimage:hash"},
296+
},
297+
},
298+
},
299+
cache: &Cache{
300+
useCache: true,
301+
artifactCache: ArtifactCache{"hash": ImageDetails{Digest: "digest"}},
302+
},
303+
digest: "digest",
304+
expected: &cachedArtifactDetails{
305+
needsRetag: true,
306+
needsPush: true,
307+
prebuiltImage: "anotherimage:hash",
308+
hashTag: "image:hash",
309+
},
310+
},
311+
{
312+
name: "image in cache, prebuilt image exists, local cluster",
313+
artifact: &latest.Artifact{ImageName: "image"},
314+
hashes: map[string]string{"image": "hash"},
315+
localCluster: true,
316+
api: testutil.FakeAPIClient{
317+
ImageSummaries: []types.ImageSummary{
318+
{
319+
RepoDigests: []string{"digest"},
320+
RepoTags: []string{"anotherimage:hash"},
321+
},
322+
},
323+
},
324+
cache: &Cache{
325+
useCache: true,
326+
artifactCache: ArtifactCache{"hash": ImageDetails{Digest: "digest"}},
327+
},
328+
digest: "digest",
329+
expected: &cachedArtifactDetails{
330+
needsRetag: true,
331+
prebuiltImage: "anotherimage:hash",
332+
hashTag: "image:hash",
333+
},
334+
},
335+
}
336+
337+
for _, test := range tests {
338+
t.Run(test.name, func(t *testing.T) {
339+
originalHash := hashForArtifact
340+
hashForArtifact = mockHashForArtifact(test.hashes)
341+
defer func() {
342+
hashForArtifact = originalHash
343+
}()
344+
345+
originalLocal := localCluster
346+
localCluster = func() (bool, error) {
347+
return test.localCluster, nil
348+
}
349+
defer func() {
350+
localCluster = originalLocal
351+
}()
352+
353+
originalRemoteDigest := remoteDigest
354+
remoteDigest = func(string) (string, error) {
355+
return test.digest, nil
356+
}
357+
defer func() {
358+
remoteDigest = originalRemoteDigest
359+
}()
360+
361+
test.cache.client = docker.NewLocalDaemon(&test.api, nil)
362+
actual, err := test.cache.retrieveCachedArtifactDetails(context.Background(), os.Stdout, test.artifact)
363+
if err != nil {
364+
t.Fatalf("error retrieving artifact details: %v", err)
365+
}
366+
// cmp.Diff cannot access unexported fields, so use reflect.DeepEqual here directly
367+
if !reflect.DeepEqual(test.expected, actual) {
368+
t.Errorf("Expected: %v, Actual: %v", test.expected, actual)
369+
}
370+
})
371+
}
372+
}

0 commit comments

Comments
 (0)