go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/config_service/rpc/common.go (about)

     1  // Copyright 2023 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 rpc
    16  
    17  import (
    18  	"path"
    19  	"strings"
    20  
    21  	"google.golang.org/protobuf/types/known/fieldmaskpb"
    22  	"google.golang.org/protobuf/types/known/timestamppb"
    23  
    24  	"go.chromium.org/luci/common/data/stringset"
    25  	"go.chromium.org/luci/common/errors"
    26  	"go.chromium.org/luci/common/proto/mask"
    27  
    28  	"go.chromium.org/luci/config_service/internal/common"
    29  	"go.chromium.org/luci/config_service/internal/model"
    30  	pb "go.chromium.org/luci/config_service/proto"
    31  )
    32  
    33  // maxRawContentSize is the max allowed raw content size per config in rpc
    34  // responses. Any size larger than it will be responded with a GCS signed url.
    35  const maxRawContentSize = 30 * 1024 * 1024
    36  
    37  // defaultConfigSetMask is the default mask used for ConfigSet related RPCs.
    38  var defaultConfigSetMask = mask.MustFromReadMask(&pb.ConfigSet{}, "name", "url", "revision")
    39  
    40  // validatePath validates path in rpc requests.
    41  func validatePath(p string) error {
    42  	if p == "" {
    43  		return errors.New("not specified")
    44  	}
    45  	if path.IsAbs(p) {
    46  		return errors.Reason("must not be absolute").Err()
    47  	}
    48  	if strings.HasPrefix(p, "./") || strings.HasPrefix(p, "../") {
    49  		return errors.Reason("should not start with './' or '../'").Err()
    50  	}
    51  	return nil
    52  }
    53  
    54  // toConfigMask converts the given field mask for Config proto to a config mask.
    55  func toConfigMask(fields *fieldmaskpb.FieldMask) (*mask.Mask, error) {
    56  	// Convert "content" path to "raw_content" and "signed_url", as "content" is
    57  	// 'oneof' field type and the mask lib hasn't supported to parse it yet.
    58  	if fieldSet := stringset.NewFromSlice(fields.GetPaths()...); fieldSet.Has("content") {
    59  		fieldSet.Del("content")
    60  		fieldSet.Add("raw_content")
    61  		fieldSet.Add("signed_url")
    62  		fields.Paths = fieldSet.ToSlice()
    63  	}
    64  	return mask.FromFieldMask(fields, &pb.Config{}, false, false)
    65  }
    66  
    67  // toConfigSetMask converts the given field mask for ConfigSet proto to a
    68  // ConfigSet mask.
    69  func toConfigSetMask(fields *fieldmaskpb.FieldMask) (*mask.Mask, error) {
    70  	if len(fields.GetPaths()) == 0 {
    71  		return defaultConfigSetMask, nil
    72  	}
    73  	return mask.FromFieldMask(fields, &pb.ConfigSet{}, false, false)
    74  }
    75  
    76  // toConfigPb converts *model.File to Config proto, excluding its content.
    77  func toConfigPb(cs string, f *model.File) *pb.Config {
    78  	return &pb.Config{
    79  		ConfigSet:     cs,
    80  		Path:          f.Path,
    81  		ContentSha256: f.ContentSHA256,
    82  		Size:          f.Size,
    83  		Revision:      f.Revision.StringID(),
    84  		Url:           common.GitilesURL(f.Location.GetGitilesLocation()),
    85  	}
    86  }
    87  
    88  // toConfigSetPb converts *model.ConfigSet to ConfigSet proto.
    89  func toConfigSetPb(cs *model.ConfigSet) *pb.ConfigSet {
    90  	if cs == nil {
    91  		return nil
    92  	}
    93  	return &pb.ConfigSet{
    94  		Name: string(cs.ID),
    95  		Url:  common.GitilesURL(cs.Location.GetGitilesLocation()),
    96  		Revision: &pb.ConfigSet_Revision{
    97  			Id:             cs.LatestRevision.ID,
    98  			Url:            common.GitilesURL(cs.LatestRevision.Location.GetGitilesLocation()),
    99  			CommitterEmail: cs.LatestRevision.CommitterEmail,
   100  			AuthorEmail:    cs.LatestRevision.AuthorEmail,
   101  			Timestamp:      timestamppb.New(cs.LatestRevision.CommitTime),
   102  		},
   103  	}
   104  }
   105  
   106  // toImportAttempt converts *model.ImportAttempt to ConfigSet_Attempt proto.
   107  func toImportAttempt(attempt *model.ImportAttempt) *pb.ConfigSet_Attempt {
   108  	if attempt == nil {
   109  		return nil
   110  	}
   111  	return &pb.ConfigSet_Attempt{
   112  		Message: attempt.Message,
   113  		Success: attempt.Success,
   114  		Revision: &pb.ConfigSet_Revision{
   115  			Id:             attempt.Revision.ID,
   116  			Url:            common.GitilesURL(attempt.Revision.Location.GetGitilesLocation()),
   117  			CommitterEmail: attempt.Revision.CommitterEmail,
   118  			AuthorEmail:    attempt.Revision.AuthorEmail,
   119  			Timestamp:      timestamppb.New(attempt.Revision.CommitTime),
   120  		},
   121  		ValidationResult: attempt.ValidationResult,
   122  	}
   123  }