github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/cache/retrieve.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"
    23  	"time"
    24  
    25  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
    26  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
    27  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
    28  	sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/errors"
    29  	eventV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/event/v2"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
    31  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/instrumentation"
    32  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output"
    33  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    34  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform"
    35  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    36  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/tag"
    37  	timeutil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util/time"
    38  )
    39  
    40  func (c *cache) Build(ctx context.Context, out io.Writer, tags tag.ImageTags, artifacts []*latest.Artifact, platforms platform.Resolver, buildAndTest BuildAndTestFn) ([]graph.Artifact, error) {
    41  	ctx, cancel := context.WithCancel(ctx)
    42  	defer cancel()
    43  
    44  	start := time.Now()
    45  
    46  	output.Default.Fprintln(out, "Checking cache...")
    47  	ctx, endTrace := instrumentation.StartTrace(ctx, "Build_CheckBuildCache")
    48  	defer endTrace()
    49  
    50  	lookup := make(chan []cacheDetails)
    51  	go func() { lookup <- c.lookupArtifacts(ctx, tags, platforms, artifacts) }()
    52  
    53  	var results []cacheDetails
    54  	select {
    55  	case <-ctx.Done():
    56  		return nil, context.Canceled
    57  	case results = <-lookup:
    58  	}
    59  
    60  	hashByName := make(map[string]string)
    61  	var needToBuild []*latest.Artifact
    62  	var alreadyBuilt []graph.Artifact
    63  	for i, artifact := range artifacts {
    64  		eventV2.CacheCheckInProgress(artifact.ImageName, platforms.GetPlatforms(artifact.ImageName).String())
    65  		out, ctx := output.WithEventContext(ctx, out, constants.Build, artifact.ImageName)
    66  		output.Default.Fprintf(out, " - %s: ", artifact.ImageName)
    67  
    68  		result := results[i]
    69  		switch result := result.(type) {
    70  		case failed:
    71  			output.Red.Fprintln(out, "Error checking cache.")
    72  			endTrace(instrumentation.TraceEndError(result.err))
    73  			return nil, result.err
    74  
    75  		case needsBuilding:
    76  			eventV2.CacheCheckMiss(artifact.ImageName, platforms.GetPlatforms(artifact.ImageName).String())
    77  			output.Yellow.Fprintln(out, "Not found. Building")
    78  			hashByName[artifact.ImageName] = result.Hash()
    79  			needToBuild = append(needToBuild, artifact)
    80  			continue
    81  
    82  		case needsTagging:
    83  			eventV2.CacheCheckHit(artifact.ImageName, platforms.GetPlatforms(artifact.ImageName).String())
    84  			output.Green.Fprintln(out, "Found. Tagging")
    85  			if err := result.Tag(ctx, c, platforms); err != nil {
    86  				endTrace(instrumentation.TraceEndError(err))
    87  				return nil, fmt.Errorf("tagging image: %w", err)
    88  			}
    89  
    90  		case needsPushing:
    91  			eventV2.CacheCheckHit(artifact.ImageName, platforms.GetPlatforms(artifact.ImageName).String())
    92  			output.Green.Fprintln(out, "Found. Pushing")
    93  			if err := result.Push(ctx, out, c); err != nil {
    94  				endTrace(instrumentation.TraceEndError(err))
    95  
    96  				return nil, fmt.Errorf("%s: %w", sErrors.PushImageErr, err)
    97  			}
    98  
    99  		default:
   100  			eventV2.CacheCheckHit(artifact.ImageName, platforms.GetPlatforms(artifact.ImageName).String())
   101  			isLocal, err := c.isLocalImage(artifact.ImageName)
   102  			if err != nil {
   103  				endTrace(instrumentation.TraceEndError(err))
   104  				return nil, err
   105  			}
   106  			if isLocal {
   107  				output.Green.Fprintln(out, "Found Locally")
   108  			} else {
   109  				output.Green.Fprintln(out, "Found Remotely")
   110  			}
   111  		}
   112  
   113  		// Image is already built
   114  		c.cacheMutex.RLock()
   115  		entry := c.artifactCache[result.Hash()]
   116  		c.cacheMutex.RUnlock()
   117  		tag := tags[artifact.ImageName]
   118  
   119  		var uniqueTag string
   120  		isLocal, err := c.isLocalImage(artifact.ImageName)
   121  		if err != nil {
   122  			endTrace(instrumentation.TraceEndError(err))
   123  			return nil, err
   124  		}
   125  		if isLocal {
   126  			var err error
   127  			uniqueTag, err = build.TagWithImageID(ctx, tag, entry.ID, c.client)
   128  			if err != nil {
   129  				endTrace(instrumentation.TraceEndError(err))
   130  				return nil, err
   131  			}
   132  		} else {
   133  			uniqueTag = build.TagWithDigest(tag, entry.Digest)
   134  		}
   135  		c.artifactStore.Record(artifact, uniqueTag)
   136  		alreadyBuilt = append(alreadyBuilt, graph.Artifact{
   137  			ImageName: artifact.ImageName,
   138  			Tag:       uniqueTag,
   139  		})
   140  	}
   141  
   142  	log.Entry(ctx).Infoln("Cache check completed in", timeutil.Humanize(time.Since(start)))
   143  
   144  	bRes, err := buildAndTest(ctx, out, tags, needToBuild, platforms)
   145  	if err != nil {
   146  		endTrace(instrumentation.TraceEndError(err))
   147  		return nil, err
   148  	}
   149  
   150  	if err := c.addArtifacts(ctx, bRes, hashByName); err != nil {
   151  		log.Entry(ctx).Warnf("error adding artifacts to cache; caching may not work as expected: %v", err)
   152  		return append(bRes, alreadyBuilt...), nil
   153  	}
   154  
   155  	if err := saveArtifactCache(c.cacheFile, c.artifactCache); err != nil {
   156  		log.Entry(ctx).Warnf("error saving cache file; caching may not work as expected: %v", err)
   157  		return append(bRes, alreadyBuilt...), nil
   158  	}
   159  
   160  	return maintainArtifactOrder(append(bRes, alreadyBuilt...), artifacts), err
   161  }
   162  
   163  func maintainArtifactOrder(built []graph.Artifact, artifacts []*latest.Artifact) []graph.Artifact {
   164  	byName := make(map[string]graph.Artifact)
   165  	for _, build := range built {
   166  		byName[build.ImageName] = build
   167  	}
   168  
   169  	var ordered []graph.Artifact
   170  
   171  	for _, artifact := range artifacts {
   172  		ordered = append(ordered, byName[artifact.ImageName])
   173  	}
   174  
   175  	return ordered
   176  }
   177  
   178  func (c *cache) addArtifacts(ctx context.Context, bRes []graph.Artifact, hashByName map[string]string) error {
   179  	for _, a := range bRes {
   180  		entry := ImageDetails{}
   181  		isLocal, err := c.isLocalImage(a.ImageName)
   182  		if err != nil {
   183  			return err
   184  		}
   185  		if isLocal {
   186  			imageID, err := c.client.ImageID(ctx, a.Tag)
   187  			if err != nil {
   188  				return err
   189  			}
   190  
   191  			if imageID != "" {
   192  				entry.ID = imageID
   193  			}
   194  		} else {
   195  			ref, err := docker.ParseReference(a.Tag)
   196  			if err != nil {
   197  				return fmt.Errorf("parsing reference %q: %w", a.Tag, err)
   198  			}
   199  			entry.Digest = ref.Digest
   200  		}
   201  		c.cacheMutex.Lock()
   202  		c.artifactCache[hashByName[a.ImageName]] = entry
   203  		c.cacheMutex.Unlock()
   204  	}
   205  	return nil
   206  }