sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/resultstore/uploader.go (about) 1 /* 2 Copyright 2023 The Kubernetes 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 resultstore 18 19 import ( 20 "bytes" 21 "context" 22 "crypto/sha256" 23 "sync" 24 25 "github.com/google/uuid" 26 "github.com/sirupsen/logrus" 27 "sigs.k8s.io/prow/pkg/resultstore/writer" 28 ) 29 30 type Uploader struct { 31 client writer.ResultStoreBatchClient 32 } 33 34 func NewUploader(client *Client) *Uploader { 35 return &Uploader{ 36 client: client.UploadClient(), 37 } 38 } 39 40 // onlyTransientError returns err only if it is transient. This ensures the 41 // caller retries the Upload at a later time only for non-permanent errors. 42 func onlyTransientError(err error) error { 43 if writer.IsPermanentError(err) { 44 return nil 45 } 46 return err 47 } 48 49 // Upload uploads a completed Prow job's results to ResultStore's API: 50 // https://github.com/googleapis/googleapis/blob/master/google/devtools/resultstore/v2/resultstore_upload.proto 51 // This function distinguishes between transient and permanent errors; only 52 // transient errors from ResultStore are returned, in which case the call 53 // should be retried later. 54 func (u *Uploader) Upload(ctx context.Context, log *logrus.Entry, p *Payload) error { 55 invID, err := p.InvocationID() 56 if err != nil { 57 log.Errorf("p.InvocationID: %v", err) 58 } 59 inv, err := p.Invocation() 60 if err != nil { 61 log.Errorf("p.Invocation: %v", err) 62 return nil 63 } 64 w, err := writer.New(ctx, log, u.client, inv, invID, authToken.From(invID)) 65 if err != nil { 66 return onlyTransientError(err) 67 } 68 // While the resource proto write methods could theoretically return error, 69 // because we presently create fewer than writer.batchSize updates, they 70 // are all batched and no I/O occurs until the Finalize() call. 71 w.WriteConfiguration(ctx, p.DefaultConfiguration()) 72 w.WriteTarget(ctx, p.OverallTarget()) 73 w.WriteConfiguredTarget(ctx, p.ConfiguredTarget()) 74 w.WriteAction(ctx, p.OverallAction()) 75 76 if err := w.Finalize(ctx); err != nil { 77 return onlyTransientError(err) 78 } 79 return nil 80 } 81 82 type tokenGenerator struct { 83 mu sync.Mutex 84 seed []byte 85 } 86 87 func (g *tokenGenerator) From(id string) string { 88 g.mu.Lock() 89 seed := g.seed 90 g.mu.Unlock() 91 92 digest := sha256.New() 93 digest.Write(seed) 94 digest.Write([]byte(id)) 95 r := bytes.NewReader(digest.Sum(nil)) 96 // error cannot occur since we read from a 256-bit digest. 97 u, _ := uuid.NewRandomFromReader(r) 98 return u.String() 99 } 100 101 func (g *tokenGenerator) Reseed(seed string) { 102 digest := sha256.New() 103 digest.Write([]byte(seed)) 104 105 g.mu.Lock() 106 g.seed = digest.Sum(nil) 107 g.mu.Unlock() 108 } 109 110 var authToken *tokenGenerator 111 112 func init() { 113 authToken = &tokenGenerator{} 114 SeedAuthToken("Avast ye, Matey!") 115 } 116 117 // SeedAuthToken sets the seed for computing AuthenticationToken values for 118 // ResultStore uploads. This is just one of many layers of protection, but if 119 // ever needed, a Crier secret string could be used to rule out unintended 120 // writers from interfering with in-progress uploads. 121 func SeedAuthToken(seed string) { 122 authToken.Reseed(seed) 123 }