github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/cache/cache.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  	"path/filepath"
    24  	"sync"
    25  
    26  	"github.com/mitchellh/go-homedir"
    27  
    28  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
    31  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
    32  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
    33  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    34  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    35  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
    36  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/yaml"
    37  )
    38  
    39  // ImageDetails holds the Digest and ID of an image
    40  type ImageDetails struct {
    41  	Digest string `yaml:"digest,omitempty"`
    42  	ID     string `yaml:"id,omitempty"`
    43  }
    44  
    45  // ArtifactCache is a map of [artifact dependencies hash : ImageDetails]
    46  type ArtifactCache map[string]ImageDetails
    47  
    48  // cache holds any data necessary for accessing the cache
    49  type cache struct {
    50  	artifactCache      ArtifactCache
    51  	artifactGraph      graph.ArtifactGraph
    52  	artifactStore      build.ArtifactStore
    53  	cacheMutex         sync.RWMutex
    54  	client             docker.LocalDaemon
    55  	cfg                Config
    56  	cacheFile          string
    57  	isLocalImage       func(imageName string) (bool, error)
    58  	importMissingImage func(imageName string) (bool, error)
    59  	lister             DependencyLister
    60  }
    61  
    62  // DependencyLister fetches a list of dependencies for an artifact
    63  type DependencyLister func(ctx context.Context, artifact *latest.Artifact) ([]string, error)
    64  
    65  type Config interface {
    66  	docker.Config
    67  	PipelineForImage(imageName string) (latest.Pipeline, bool)
    68  	GetPipelines() []latest.Pipeline
    69  	DefaultPipeline() latest.Pipeline
    70  	GetCluster() config.Cluster
    71  	CacheArtifacts() bool
    72  	CacheFile() string
    73  	Mode() config.RunMode
    74  }
    75  
    76  // NewCache returns the current state of the cache
    77  func NewCache(ctx context.Context, cfg Config, isLocalImage func(imageName string) (bool, error), dependencies DependencyLister, graph graph.ArtifactGraph, store build.ArtifactStore) (Cache, error) {
    78  	if !cfg.CacheArtifacts() {
    79  		return &noCache{}, nil
    80  	}
    81  
    82  	cacheFile, err := resolveCacheFile(cfg.CacheFile())
    83  	if err != nil {
    84  		log.Entry(context.TODO()).Warnf("Error resolving cache file, not using skaffold cache: %v", err)
    85  		return &noCache{}, nil
    86  	}
    87  
    88  	artifactCache, err := retrieveArtifactCache(cacheFile)
    89  	if err != nil {
    90  		log.Entry(context.TODO()).Warnf("Error retrieving artifact cache, not using skaffold cache: %v", err)
    91  		return &noCache{}, nil
    92  	}
    93  
    94  	client, err := docker.NewAPIClient(ctx, cfg)
    95  	if err != nil {
    96  		// error only if any pipeline is local.
    97  		for _, p := range cfg.GetPipelines() {
    98  			for _, a := range p.Build.Artifacts {
    99  				if local, _ := isLocalImage(a.ImageName); local {
   100  					return nil, fmt.Errorf("getting local Docker client: %w", err)
   101  				}
   102  			}
   103  		}
   104  	}
   105  
   106  	importMissingImage := func(imageName string) (bool, error) {
   107  		pipeline, found := cfg.PipelineForImage(imageName)
   108  		if !found {
   109  			pipeline = cfg.DefaultPipeline()
   110  		}
   111  
   112  		if pipeline.Build.GoogleCloudBuild != nil || pipeline.Build.Cluster != nil {
   113  			return false, nil
   114  		}
   115  		return pipeline.Build.LocalBuild.TryImportMissing, nil
   116  	}
   117  
   118  	return &cache{
   119  		artifactCache:      artifactCache,
   120  		artifactGraph:      graph,
   121  		artifactStore:      store,
   122  		client:             client,
   123  		cfg:                cfg,
   124  		cacheFile:          cacheFile,
   125  		isLocalImage:       isLocalImage,
   126  		importMissingImage: importMissingImage,
   127  		lister:             dependencies,
   128  	}, nil
   129  }
   130  
   131  // resolveCacheFile makes sure that either a passed in cache file or the default cache file exists
   132  func resolveCacheFile(cacheFile string) (string, error) {
   133  	if cacheFile != "" {
   134  		return cacheFile, util.VerifyOrCreateFile(cacheFile)
   135  	}
   136  	home, err := homedir.Dir()
   137  	if err != nil {
   138  		return "", fmt.Errorf("retrieving home directory: %w", err)
   139  	}
   140  	defaultFile := filepath.Join(home, constants.DefaultSkaffoldDir, constants.DefaultCacheFile)
   141  	return defaultFile, util.VerifyOrCreateFile(defaultFile)
   142  }
   143  
   144  func retrieveArtifactCache(cacheFile string) (ArtifactCache, error) {
   145  	cache := ArtifactCache{}
   146  	contents, err := ioutil.ReadFile(cacheFile)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	if err := yaml.Unmarshal(contents, &cache); err != nil {
   151  		return nil, err
   152  	}
   153  	return cache, nil
   154  }
   155  
   156  func saveArtifactCache(cacheFile string, contents ArtifactCache) error {
   157  	data, err := yaml.Marshal(contents)
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	return ioutil.WriteFile(cacheFile, data, 0755)
   163  }