github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/scheduler.go (about) 1 /* 2 Copyright 2020 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 build 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "io" 24 "strconv" 25 26 "golang.org/x/sync/errgroup" 27 28 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" 29 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" 30 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/event" 31 eventV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/event/v2" 32 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph" 33 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/instrumentation" 34 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output" 35 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform" 36 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" 37 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/tag" 38 ) 39 40 type ArtifactBuilder func(ctx context.Context, out io.Writer, artifact *latest.Artifact, tag string, platforms platform.Matcher) (string, error) 41 42 type scheduler struct { 43 artifacts []*latest.Artifact 44 nodes []node // size len(artifacts) 45 artifactBuilder ArtifactBuilder 46 logger logAggregator 47 results ArtifactStore 48 concurrencySem countingSemaphore 49 reportFailure bool 50 } 51 52 func newScheduler(artifacts []*latest.Artifact, artifactBuilder ArtifactBuilder, concurrency int, out io.Writer, store ArtifactStore) *scheduler { 53 s := scheduler{ 54 artifacts: artifacts, 55 nodes: createNodes(artifacts), 56 artifactBuilder: artifactBuilder, 57 logger: newLogAggregator(out, len(artifacts), concurrency), 58 results: store, 59 concurrencySem: newCountingSemaphore(concurrency), 60 61 // avoid visual stutters from reporting failures inline and Skaffold's final command output 62 reportFailure: concurrency != 1 && len(artifacts) > 1, 63 } 64 return &s 65 } 66 67 func (s *scheduler) run(ctx context.Context, tags tag.ImageTags, platforms platform.Resolver) ([]graph.Artifact, error) { 68 g, gCtx := errgroup.WithContext(ctx) 69 70 for i := range s.artifacts { 71 i := i 72 73 // Create a goroutine for each element in dag. Each goroutine waits on its dependencies to finish building. 74 // Because our artifacts form a DAG, at least one of the goroutines should be able to start building. 75 // Wrap in an error group so that all other builds are cancelled as soon as any one fails. 76 g.Go(func() error { 77 return s.build(gCtx, tags, platforms, i) 78 }) 79 } 80 // print output for all artifact builds in order 81 s.logger.PrintInOrder(gCtx) 82 if err := g.Wait(); err != nil { 83 event.BuildSequenceFailed(err) 84 return nil, err 85 } 86 return s.results.GetArtifacts(s.artifacts) 87 } 88 89 func (s *scheduler) build(ctx context.Context, tags tag.ImageTags, platforms platform.Resolver, i int) error { 90 n := s.nodes[i] 91 a := s.artifacts[i] 92 err := n.waitForDependencies(ctx) 93 if err != nil { 94 // `waitForDependencies` only returns `context.Canceled` error 95 event.BuildCanceled(a.ImageName, platforms.GetPlatforms(a.ImageName).String()) 96 eventV2.BuildCanceled(a.ImageName, platforms.GetPlatforms(a.ImageName).String(), err) 97 return err 98 } 99 release := s.concurrencySem.acquire() 100 defer release() 101 102 event.BuildInProgress(a.ImageName, platforms.GetPlatforms(a.ImageName).String()) 103 eventV2.BuildInProgress(a.ImageName, platforms.GetPlatforms(a.ImageName).String()) 104 ctx, endTrace := instrumentation.StartTrace(ctx, "build_BuildInProgress", map[string]string{ 105 "ArtifactNumber": strconv.Itoa(i), 106 "ImageName": instrumentation.PII(a.ImageName), 107 }) 108 defer endTrace() 109 110 w, closeFn, err := s.logger.GetWriter(ctx) 111 if err != nil { 112 event.BuildFailed(a.ImageName, platforms.GetPlatforms(a.ImageName).String(), err) 113 eventV2.BuildFailed(a.ImageName, platforms.GetPlatforms(a.ImageName).String(), err) 114 endTrace(instrumentation.TraceEndError(err)) 115 return err 116 } 117 defer closeFn() 118 119 w, ctx = output.WithEventContext(ctx, w, constants.Build, a.ImageName) 120 output.Default.Fprintf(w, "Building [%s]...\n", a.ImageName) 121 finalTag, err := performBuild(ctx, w, tags, platforms, a, s.artifactBuilder) 122 if err != nil { 123 event.BuildFailed(a.ImageName, platforms.GetPlatforms(a.ImageName).String(), err) 124 endTrace(instrumentation.TraceEndError(err)) 125 if errors.Is(ctx.Err(), context.Canceled) { 126 output.Yellow.Fprintf(w, "Build [%s] was canceled\n", a.ImageName) 127 eventV2.BuildCanceled(a.ImageName, platforms.GetPlatforms(a.ImageName).String(), err) 128 return err 129 } 130 if s.reportFailure { 131 output.Red.Fprintf(w, "Build [%s] failed: %v\n", a.ImageName, err) 132 } 133 eventV2.BuildFailed(a.ImageName, platforms.GetPlatforms(a.ImageName).String(), err) 134 return fmt.Errorf("build [%s] failed: %w", a.ImageName, err) 135 } 136 137 output.Default.Fprintf(w, "Build [%s] succeeded\n", a.ImageName) 138 s.results.Record(a, finalTag) 139 n.markComplete() 140 event.BuildComplete(a.ImageName, platforms.GetPlatforms(a.ImageName).String()) 141 eventV2.BuildSucceeded(a.ImageName, platforms.GetPlatforms(a.ImageName).String()) 142 return nil 143 } 144 145 // InOrder builds a list of artifacts in dependency order. 146 func InOrder(ctx context.Context, out io.Writer, tags tag.ImageTags, platforms platform.Resolver, artifacts []*latest.Artifact, artifactBuilder ArtifactBuilder, concurrency int, store ArtifactStore) ([]graph.Artifact, error) { 147 // `concurrency` specifies the max number of builds that can run at any one time. If concurrency is 0, then all builds can run in parallel. 148 if concurrency == 0 { 149 concurrency = len(artifacts) 150 } 151 if concurrency > 1 { 152 output.Default.Fprintf(out, "Building %d artifacts in parallel\n", concurrency) 153 } 154 s := newScheduler(artifacts, artifactBuilder, concurrency, out, store) 155 ctx, cancel := context.WithCancel(ctx) 156 defer cancel() 157 return s.run(ctx, tags, platforms) 158 } 159 160 func performBuild(ctx context.Context, cw io.Writer, tags tag.ImageTags, platforms platform.Resolver, artifact *latest.Artifact, build ArtifactBuilder) (string, error) { 161 tag, present := tags[artifact.ImageName] 162 if !present { 163 return "", fmt.Errorf("unable to find tag for image %s", artifact.ImageName) 164 } 165 pl := platforms.GetPlatforms(artifact.ImageName) 166 if pl.IsNotEmpty() { 167 output.Default.Fprintf(cw, "Target platforms: [%s]\n", pl) 168 } 169 tag = docker.SanitizeImageName(tag) 170 return build(ctx, cw, artifact, tag, pl) 171 }