github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/graph/dependencies.go (about) 1 /* 2 Copyright 2021 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 graph 18 19 import ( 20 "context" 21 "fmt" 22 23 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/bazel" 24 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/buildpacks" 25 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/custom" 26 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/jib" 27 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/ko" 28 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/misc" 29 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" 30 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/instrumentation" 31 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" 32 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" 33 ) 34 35 // for testing 36 var getDependenciesFunc = sourceDependenciesForArtifact 37 38 // SourceDependenciesCache provides an interface to evaluate and cache the source dependencies for artifacts. 39 type SourceDependenciesCache interface { 40 // TransitiveArtifactDependencies returns the source dependencies for the target artifact, including the source dependencies from all other artifacts that are in the transitive closure of its artifact dependencies. 41 // The result (even if an error) is cached so that the function is evaluated only once for every artifact. The cache is reset before the start of the next devloop. 42 TransitiveArtifactDependencies(ctx context.Context, a *latest.Artifact) ([]string, error) 43 // SingleArtifactDependencies returns the source dependencies for only the target artifact. 44 // The result (even if an error) is cached so that the function is evaluated only once for every artifact. The cache is reset before the start of the next devloop. 45 SingleArtifactDependencies(ctx context.Context, a *latest.Artifact) ([]string, error) 46 // Reset removes the cached source dependencies for all artifacts 47 Reset() 48 } 49 50 func NewSourceDependenciesCache(cfg docker.Config, r docker.ArtifactResolver, g ArtifactGraph) SourceDependenciesCache { 51 return &dependencyResolverImpl{cfg: cfg, artifactResolver: r, artifactGraph: g, cache: util.NewSyncStore()} 52 } 53 54 type dependencyResolverImpl struct { 55 cfg docker.Config 56 artifactResolver docker.ArtifactResolver 57 artifactGraph ArtifactGraph 58 cache *util.SyncStore 59 } 60 61 func (r *dependencyResolverImpl) TransitiveArtifactDependencies(ctx context.Context, a *latest.Artifact) ([]string, error) { 62 ctx, endTrace := instrumentation.StartTrace(ctx, "TransitiveArtifactDependencies", map[string]string{ 63 "ArtifactName": instrumentation.PII(a.ImageName), 64 }) 65 defer endTrace() 66 67 deps, err := r.SingleArtifactDependencies(ctx, a) 68 if err != nil { 69 endTrace(instrumentation.TraceEndError(err)) 70 return nil, err 71 } 72 for _, ad := range a.Dependencies { 73 d, err := r.TransitiveArtifactDependencies(ctx, r.artifactGraph[ad.ImageName]) 74 if err != nil { 75 endTrace(instrumentation.TraceEndError(err)) 76 return nil, err 77 } 78 deps = append(deps, d...) 79 } 80 return deps, nil 81 } 82 83 func (r *dependencyResolverImpl) SingleArtifactDependencies(ctx context.Context, a *latest.Artifact) ([]string, error) { 84 ctx, endTrace := instrumentation.StartTrace(ctx, "SingleArtifactDependencies", map[string]string{ 85 "ArtifactName": instrumentation.PII(a.ImageName), 86 }) 87 defer endTrace() 88 89 res := r.cache.Exec(a.ImageName, func() interface{} { 90 d, e := getDependenciesFunc(ctx, a, r.cfg, r.artifactResolver) 91 if e != nil { 92 return e 93 } 94 return d 95 }) 96 if err, ok := res.(error); ok { 97 endTrace(instrumentation.TraceEndError(err)) 98 return nil, err 99 } 100 return res.([]string), nil 101 } 102 103 func (r *dependencyResolverImpl) Reset() { 104 r.cache = util.NewSyncStore() 105 } 106 107 // sourceDependenciesForArtifact returns the build dependencies for the current artifact. 108 func sourceDependenciesForArtifact(ctx context.Context, a *latest.Artifact, cfg docker.Config, r docker.ArtifactResolver) ([]string, error) { 109 var ( 110 paths []string 111 err error 112 ) 113 114 switch { 115 case a.DockerArtifact != nil: 116 // Required artifacts cannot be resolved when `ResolveDependencyImages` runs prior to a completed build sequence (like `skaffold build` or the first iteration of `skaffold dev`). 117 // However it only affects the behavior for Dockerfiles with ONBUILD instructions, and there's no functional change even for those scenarios. 118 // For single build scenarios like `build` and `run`, it is called for the cache hash calculations which are already handled in `artifactHasher`. 119 // For `dev` it will succeed on the first dev loop and list any additional dependencies found from the base artifact's ONBUILD instructions as a file added instead of modified (see `filemon.Events`) 120 deps := docker.ResolveDependencyImages(a.Dependencies, r, false) 121 args, evalErr := docker.EvalBuildArgs(cfg.Mode(), a.Workspace, a.DockerArtifact.DockerfilePath, a.DockerArtifact.BuildArgs, deps) 122 if evalErr != nil { 123 return nil, fmt.Errorf("unable to evaluate build args: %w", evalErr) 124 } 125 paths, err = docker.GetDependencies(ctx, docker.NewBuildConfig(a.Workspace, a.ImageName, a.DockerArtifact.DockerfilePath, args), cfg) 126 127 case a.KanikoArtifact != nil: 128 deps := docker.ResolveDependencyImages(a.Dependencies, r, false) 129 args, evalErr := docker.EvalBuildArgs(cfg.Mode(), a.Workspace, a.KanikoArtifact.DockerfilePath, a.KanikoArtifact.BuildArgs, deps) 130 if evalErr != nil { 131 return nil, fmt.Errorf("unable to evaluate build args: %w", evalErr) 132 } 133 paths, err = docker.GetDependencies(ctx, docker.NewBuildConfig(a.Workspace, a.ImageName, a.KanikoArtifact.DockerfilePath, args), cfg) 134 135 case a.BazelArtifact != nil: 136 paths, err = bazel.GetDependencies(ctx, a.Workspace, a.BazelArtifact) 137 138 case a.JibArtifact != nil: 139 paths, err = jib.GetDependencies(ctx, a.Workspace, a.JibArtifact) 140 141 case a.CustomArtifact != nil: 142 paths, err = custom.GetDependencies(ctx, a.Workspace, a.ImageName, a.CustomArtifact, cfg) 143 144 case a.BuildpackArtifact != nil: 145 paths, err = buildpacks.GetDependencies(ctx, a.Workspace, a.BuildpackArtifact) 146 147 case a.KoArtifact != nil: 148 paths, err = ko.GetDependencies(ctx, a.Workspace, a.KoArtifact) 149 150 default: 151 return nil, fmt.Errorf("unexpected artifact type %q:\n%s", misc.ArtifactType(a), misc.FormatArtifact(a)) 152 } 153 154 if err != nil { 155 return nil, err 156 } 157 158 return util.AbsolutePaths(a.Workspace, paths), nil 159 }