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 }