github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/result.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 "bufio" 21 "context" 22 "fmt" 23 "io" 24 "sync" 25 26 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph" 27 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output" 28 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log" 29 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" 30 ) 31 32 const bufferedLinesPerArtifact = 10000 33 34 // For testing 35 var ( 36 buffSize = bufferedLinesPerArtifact 37 ) 38 39 // logAggregator provides an interface to create an output writer for each artifact build and later aggregate the logs in build order. 40 // The order of output is not guaranteed between multiple builds running concurrently. 41 type logAggregator interface { 42 // GetWriter returns an output writer tracked by the logAggregator 43 GetWriter(ctx context.Context) (w io.Writer, close func(), err error) 44 // PrintInOrder prints the output from each allotted writer in build order. 45 // It blocks until the instantiated capacity of io writers have been all allotted and closed, or the context is cancelled. 46 PrintInOrder(ctx context.Context) 47 } 48 49 type logAggregatorImpl struct { 50 out io.Writer 51 messages chan chan string 52 size int 53 capacity int 54 countMutex sync.Mutex 55 } 56 57 func (l *logAggregatorImpl) GetWriter(ctx context.Context) (io.Writer, func(), error) { 58 if err := l.checkCapacity(); err != nil { 59 return nil, nil, err 60 } 61 r, w := io.Pipe() 62 63 writer := io.Writer(w) 64 if output.IsColorable(l.out) { 65 writer = output.GetWriter(ctx, writer, output.DefaultColorCode, false, false) 66 } 67 ch := make(chan string, buffSize) 68 l.messages <- ch 69 // write the build output to a buffered channel. 70 go l.writeToChannel(r, ch) 71 return writer, func() { w.Close() }, nil 72 } 73 74 func (l *logAggregatorImpl) PrintInOrder(ctx context.Context) { 75 go func() { 76 <-ctx.Done() 77 // we handle cancellation by passing a nil struct instead of closing the channel. 78 // This makes it easier to flush all pending messages on the buffered channel before returning and avoid any race with pending requests for new writers. 79 l.messages <- nil 80 }() 81 for i := 0; i < l.capacity; i++ { 82 ch := <-l.messages 83 if ch == nil { 84 return 85 } 86 // read from each build's message channel and write to the given output. 87 printResult(l.out, ch) 88 } 89 } 90 91 func (l *logAggregatorImpl) checkCapacity() error { 92 l.countMutex.Lock() 93 defer l.countMutex.Unlock() 94 if l.size == l.capacity { 95 return fmt.Errorf("failed to create writer: capacity exceeded") 96 } 97 l.size++ 98 return nil 99 } 100 101 func printResult(out io.Writer, output chan string) { 102 for line := range output { 103 fmt.Fprintln(out, line) 104 } 105 } 106 107 func (l *logAggregatorImpl) writeToChannel(r io.Reader, lines chan string) { 108 scanner := bufio.NewScanner(r) 109 for scanner.Scan() { 110 lines <- scanner.Text() 111 } 112 if scanner.Err() != nil { 113 log.Entry(context.TODO()).Errorf("error occurred retrieving build logs: %v", scanner.Err()) 114 } 115 close(lines) 116 } 117 118 // noopLogAggregatorImpl simply returns a single stored io.Writer, usually `os.Stdout` for every request. 119 // This is useful when builds are sequential and logs can be outputted to standard output with color formatting. 120 type noopLogAggregatorImpl struct { 121 out io.Writer 122 } 123 124 func (n *noopLogAggregatorImpl) GetWriter(context.Context) (io.Writer, func(), error) { 125 return n.out, func() {}, nil 126 } 127 128 func (n *noopLogAggregatorImpl) PrintInOrder(context.Context) {} 129 130 func newLogAggregator(out io.Writer, capacity int, concurrency int) logAggregator { 131 if concurrency == 1 { 132 return &noopLogAggregatorImpl{out: out} 133 } 134 return &logAggregatorImpl{out: out, capacity: capacity, messages: make(chan chan string, capacity)} 135 } 136 137 // ArtifactStore stores the results of each artifact build. 138 type ArtifactStore interface { 139 Record(a *latest.Artifact, tag string) 140 GetImageTag(imageName string) (tag string, found bool) 141 GetArtifacts(s []*latest.Artifact) ([]graph.Artifact, error) 142 } 143 144 func NewArtifactStore() ArtifactStore { 145 return &artifactStoreImpl{m: new(sync.Map)} 146 } 147 148 type artifactStoreImpl struct { 149 m *sync.Map 150 } 151 152 func (ba *artifactStoreImpl) Record(a *latest.Artifact, tag string) { 153 ba.m.Store(a.ImageName, tag) 154 } 155 156 func (ba *artifactStoreImpl) GetImageTag(imageName string) (string, bool) { 157 v, ok := ba.m.Load(imageName) 158 if !ok { 159 return "", false 160 } 161 t, ok := v.(string) 162 if !ok { 163 log.Entry(context.TODO()).Fatalf("invalid build output recorded for image %s", imageName) 164 } 165 return t, true 166 } 167 168 func (ba *artifactStoreImpl) GetArtifacts(s []*latest.Artifact) ([]graph.Artifact, error) { 169 var builds []graph.Artifact 170 for _, a := range s { 171 t, found := ba.GetImageTag(a.ImageName) 172 if !found { 173 return nil, fmt.Errorf("failed to retrieve build result for image %s", a.ImageName) 174 } 175 builds = append(builds, graph.Artifact{ImageName: a.ImageName, Tag: t}) 176 } 177 return builds, nil 178 }