go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/provenance/reporter/reporter.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package reporter
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"os"
    22  
    23  	"google.golang.org/grpc/codes"
    24  	"google.golang.org/grpc/status"
    25  	"google.golang.org/protobuf/types/known/timestamppb"
    26  
    27  	"go.chromium.org/luci/common/clock"
    28  	"go.chromium.org/luci/common/logging"
    29  	snooperpb "go.chromium.org/luci/provenance/api/snooperpb/v1"
    30  )
    31  
    32  var ErrServiceUnavailable = errors.New("local provenance service unavailable")
    33  
    34  // Report implements all provenance interfaces.
    35  //
    36  // This can be used as a caching opportunity by users for storing client for
    37  // longer use.
    38  // TODO(crbug/1269830): Implement a custom retry logic for transient errors.
    39  // Custom retry is needed because grpc status `Unavailable` is tagged as
    40  // transient. In this application, `ErrServiceUnavailable` is a permanent but
    41  // acceptable error code.
    42  type Report struct {
    43  	RClient snooperpb.SelfReportClient
    44  }
    45  
    46  // ReportCipdAdmission reports a local cipd admission to provenance.
    47  //
    48  // It returns a success status and annotated error. Status is to indicate user
    49  // whether to block further execution.
    50  // If local provenance service is unavailable, it will return an ok status and
    51  // annotated error. This is to indicate, the user should continue normal
    52  // execution.
    53  // All other errors are annotated to indicate permanent failures.
    54  func (r *Report) ReportCipdAdmission(ctx context.Context, pkgName, iid string) (bool, error) {
    55  	req := &snooperpb.ReportCipdRequest{
    56  		CipdReport: &snooperpb.CipdReport{
    57  			PackageName: pkgName,
    58  			Iid:         iid,
    59  			EventTs:     timestamppb.New(clock.Now(ctx)),
    60  		},
    61  	}
    62  
    63  	_, err := r.RClient.ReportCipd(ctx, req)
    64  	switch errS, _ := status.FromError(err); errS.Code() {
    65  	case codes.OK:
    66  		logging.Infof(ctx, "success to report cipd admission")
    67  		return true, nil
    68  	case codes.Unavailable:
    69  		logging.Errorf(ctx, "failed to report cipd admission: %v", ErrServiceUnavailable)
    70  		return true, ErrServiceUnavailable
    71  	default:
    72  		logging.Errorf(ctx, "failed to report cipd admission: %v", err)
    73  		return false, err
    74  	}
    75  }
    76  
    77  // ReportGitCheckout reports a local git checkout/fetch to provenance.
    78  //
    79  // It returns a success status and annotated error. Status is to indicate user
    80  // whether to block further execution.
    81  // If local provenance service is unavailable, it will return an ok status and
    82  // annotated error. This is to indicate, the user should continue normal
    83  // execution.
    84  // All other errors are annotated to indicate permanent failures.
    85  func (r *Report) ReportGitCheckout(ctx context.Context, repo, commit, ref string) (bool, error) {
    86  	req := &snooperpb.ReportGitRequest{
    87  		GitReport: &snooperpb.GitReport{
    88  			Repo:    repo,
    89  			Commit:  commit,
    90  			Refs:    ref,
    91  			EventTs: timestamppb.New(clock.Now(ctx)),
    92  		},
    93  	}
    94  
    95  	_, err := r.RClient.ReportGit(ctx, req)
    96  	switch errS, _ := status.FromError(err); errS.Code() {
    97  	case codes.OK:
    98  		logging.Infof(ctx, "success to report git checkout")
    99  		return true, nil
   100  	case codes.Unavailable:
   101  		logging.Errorf(ctx, "failed to report git checkout: %v", ErrServiceUnavailable)
   102  		return true, ErrServiceUnavailable
   103  	default:
   104  		logging.Errorf(ctx, "failed to report git checkout: %v", err)
   105  		return false, err
   106  	}
   107  }
   108  
   109  // ReportGcsDownload reports a local gcs download to provenance.
   110  //
   111  // It returns a success status and annotated error. Status is to indicate user
   112  // whether to block further execution.
   113  // If local provenance service is unavailable, it will return an ok status and
   114  // annotated error. This is to indicate, the user should continue normal
   115  // execution.
   116  // All other errors are annotated to indicate permanent failures.
   117  func (r *Report) ReportGcsDownload(ctx context.Context, uri, digest string) (bool, error) {
   118  	req := &snooperpb.ReportGcsRequest{
   119  		GcsReport: &snooperpb.GcsReport{
   120  			GcsUri:  uri,
   121  			Digest:  digest,
   122  			EventTs: timestamppb.New(clock.Now(ctx)),
   123  		},
   124  	}
   125  
   126  	_, err := r.RClient.ReportGcs(ctx, req)
   127  	switch errS, _ := status.FromError(err); errS.Code() {
   128  	case codes.OK:
   129  		logging.Infof(ctx, "success to report gcs download")
   130  		return true, nil
   131  	case codes.Unavailable:
   132  		logging.Errorf(ctx, "failed to report gcs download: %v", ErrServiceUnavailable)
   133  		return true, ErrServiceUnavailable
   134  	default:
   135  		logging.Errorf(ctx, "failed to report gcs download: %v", err)
   136  		return false, err
   137  	}
   138  }
   139  
   140  // ReportStage reports task stage via provenance local server.
   141  //
   142  // It returns a success status and annotated error. Status is to indicate user
   143  // whether to block further execution.
   144  // If local provenance service is unavailable, it will return an ok status and
   145  // annotated error. This is to indicate, the user should continue normal
   146  // execution.
   147  // All other errors are annotated to indicate permanent failures.
   148  // TODO: Use go struct when a new parameter is added.
   149  func (r *Report) ReportStage(ctx context.Context, stage snooperpb.TaskStage, recipe string, pid int64) (bool, error) {
   150  	// Must pass recipe name and pid when reporting task start.
   151  	if stage == snooperpb.TaskStage_STARTED && (recipe == "" || pid == 0) {
   152  		logging.Errorf(ctx, "failed to export task stage")
   153  		return false, fmt.Errorf("a recipe and pid must be provided when task starts")
   154  	}
   155  
   156  	req := &snooperpb.ReportTaskStageRequest{
   157  		TaskStage: stage,
   158  		Timestamp: timestamppb.New(clock.Now(ctx)),
   159  		// required when task starts
   160  		Recipe: recipe,
   161  		Pid:    pid,
   162  	}
   163  
   164  	_, err := r.RClient.ReportTaskStage(ctx, req)
   165  	switch errS, _ := status.FromError(err); errS.Code() {
   166  	case codes.OK:
   167  		logging.Infof(ctx, "success to report task stage")
   168  		return true, nil
   169  	case codes.Unavailable:
   170  		logging.Errorf(ctx, "failed to report task stage: %v", ErrServiceUnavailable)
   171  		return true, ErrServiceUnavailable
   172  	default:
   173  		logging.Errorf(ctx, "failed to report task stage: %v", err)
   174  		return false, err
   175  	}
   176  }
   177  
   178  // ReportPID reports process id to provenance local server for tracking.
   179  //
   180  // It returns a success status and annotated error. Status is to indicate user
   181  // whether to block further execution.
   182  //
   183  // If local provenance service is unavailable, it will return an ok status and
   184  // annotated error. This is to indicate that the user should continue normal
   185  // execution.
   186  // All other errors are annotated to indicate permanent failures.
   187  func (r *Report) ReportPID(ctx context.Context, pid int64) (bool, error) {
   188  	// Must pass all arguments when reporting pid.
   189  	if pid == 0 {
   190  		logging.Errorf(ctx, "failed to export pid")
   191  		return false, fmt.Errorf("pid must be present")
   192  	}
   193  
   194  	// Find who called this report and include in the report.
   195  	// This will be an absolute path of the executable that invoked this
   196  	// process.
   197  	reporter, err := os.Executable()
   198  	if err != nil {
   199  		logging.Errorf(ctx, "failed to export pid")
   200  		return false, err
   201  	}
   202  
   203  	req := &snooperpb.ReportPIDRequest{
   204  		Pid:      pid,
   205  		Reporter: reporter,
   206  	}
   207  
   208  	_, err = r.RClient.ReportPID(ctx, req)
   209  	switch errS, _ := status.FromError(err); errS.Code() {
   210  	case codes.OK:
   211  		logging.Infof(ctx, "succeeded to report task pid")
   212  		return true, nil
   213  	case codes.Unavailable:
   214  		logging.Errorf(ctx, "failed to report task pid: %v", ErrServiceUnavailable)
   215  		return true, ErrServiceUnavailable
   216  	default:
   217  		logging.Errorf(ctx, "failed to report task pid: %v", err)
   218  		return false, err
   219  	}
   220  }
   221  
   222  // ReportCipdDigest reports digest of built cipd package to provenance.
   223  //
   224  // It returns a success status and annotated error. Status is to indicate user
   225  // whether to block further execution.
   226  // If local provenance service is unavailable, it will return an ok status and
   227  // annotated error. This is to indicate, the user should continue normal
   228  // execution.
   229  // All other errors are annotated to indicate permanent failures.
   230  func (r *Report) ReportCipdDigest(ctx context.Context, digest, pkgName, iid string) (bool, error) {
   231  	req := &snooperpb.ReportArtifactDigestRequest{
   232  		Digest: digest,
   233  		Artifact: &snooperpb.Artifact{
   234  			Kind: &snooperpb.Artifact_Cipd{
   235  				Cipd: &snooperpb.Artifact_CIPD{
   236  					PackageName: pkgName,
   237  					InstanceId:  iid,
   238  				},
   239  			},
   240  		},
   241  	}
   242  
   243  	_, err := r.RClient.ReportArtifactDigest(ctx, req)
   244  	switch errS, _ := status.FromError(err); errS.Code() {
   245  	case codes.OK:
   246  		logging.Infof(ctx, "success to report cipd digest")
   247  		return true, nil
   248  	case codes.Unavailable:
   249  		logging.Errorf(ctx, "failed to report cipd digest: %v", ErrServiceUnavailable)
   250  		return true, ErrServiceUnavailable
   251  	default:
   252  		logging.Errorf(ctx, "failed to report cipd digest: %v", err)
   253  		return false, err
   254  	}
   255  }
   256  
   257  // ReportGcsDigest reports digest of a built gcs app to provenance.
   258  //
   259  // It returns a success status and annotated error. Status is to indicate user
   260  // whether to block further execution.
   261  // If local provenance service is unavailable, it will return an ok status and
   262  // annotated error. This is to indicate, the user should continue normal
   263  // execution.
   264  // All other errors are annotated to indicate permanent failures.
   265  func (r *Report) ReportGcsDigest(ctx context.Context, digest, gcsURI string) (bool, error) {
   266  	req := &snooperpb.ReportArtifactDigestRequest{
   267  		Digest: digest,
   268  		Artifact: &snooperpb.Artifact{
   269  			Kind: &snooperpb.Artifact_Gcs{
   270  				Gcs: gcsURI,
   271  			},
   272  		},
   273  	}
   274  
   275  	_, err := r.RClient.ReportArtifactDigest(ctx, req)
   276  	switch errS, _ := status.FromError(err); errS.Code() {
   277  	case codes.OK:
   278  		logging.Infof(ctx, "success to report gcs digest")
   279  		return true, nil
   280  	case codes.Unavailable:
   281  		logging.Errorf(ctx, "failed to report gcs digest: %v", ErrServiceUnavailable)
   282  		return true, ErrServiceUnavailable
   283  	default:
   284  		logging.Errorf(ctx, "failed to report gcs digest: %v", err)
   285  		return false, err
   286  	}
   287  }
   288  
   289  // ReportSbomDigest reports digest of a built gcs SBOM to provenance.
   290  //
   291  // It returns a success status and annotated error. Status is to indicate user
   292  // whether to block further execution.
   293  // If local provenance service is unavailable, it will return an ok status and
   294  // annotated error. This is to indicate, the user should continue normal
   295  // execution.
   296  // All other errors are annotated to indicate permanent failures.
   297  func (r *Report) ReportSbomDigest(ctx context.Context, digest, gcsURI string, subjectDigests []string) (bool, error) {
   298  	req := &snooperpb.ReportArtifactDigestRequest{
   299  		Digest: digest,
   300  		Artifact: &snooperpb.Artifact{
   301  			Kind: &snooperpb.Artifact_Gcs{
   302  				Gcs: gcsURI,
   303  			},
   304  		},
   305  		SbomSubjects: subjectDigests,
   306  	}
   307  
   308  	_, err := r.RClient.ReportArtifactDigest(ctx, req)
   309  	switch errS, _ := status.FromError(err); errS.Code() {
   310  	case codes.OK:
   311  		logging.Infof(ctx, "success to report sbom digest")
   312  		return true, nil
   313  	case codes.Unavailable:
   314  		logging.Errorf(ctx, "failed to report sbom digest: %v", ErrServiceUnavailable)
   315  		return true, ErrServiceUnavailable
   316  	default:
   317  		logging.Errorf(ctx, "failed to report sbom digest: %v", err)
   318  		return false, err
   319  	}
   320  }