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  }