go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/pbutil/invocation.go (about)

     1  // Copyright 2019 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 pbutil
    16  
    17  import (
    18  	"go.chromium.org/luci/common/errors"
    19  	pb "go.chromium.org/luci/resultdb/proto/v1"
    20  )
    21  
    22  const invocationIDPattern = `[a-z][a-z0-9_\-:.]{0,99}`
    23  
    24  var invocationIDRe = regexpf("^%s$", invocationIDPattern)
    25  var invocationNameRe = regexpf("^invocations/(%s)$", invocationIDPattern)
    26  
    27  // ValidateInvocationID returns a non-nil error if id is invalid.
    28  func ValidateInvocationID(id string) error {
    29  	return validateWithRe(invocationIDRe, id)
    30  }
    31  
    32  // ValidateInvocationName returns a non-nil error if name is invalid.
    33  func ValidateInvocationName(name string) error {
    34  	_, err := ParseInvocationName(name)
    35  	return err
    36  }
    37  
    38  // ParseInvocationName extracts the invocation id.
    39  func ParseInvocationName(name string) (id string, err error) {
    40  	if name == "" {
    41  		return "", unspecified()
    42  	}
    43  
    44  	m := invocationNameRe.FindStringSubmatch(name)
    45  	if m == nil {
    46  		return "", doesNotMatch(invocationNameRe)
    47  	}
    48  	return m[1], nil
    49  }
    50  
    51  // InvocationName synthesizes an invocation name from an id.
    52  // Does not validate id, use ValidateInvocationID.
    53  func InvocationName(id string) string {
    54  	return "invocations/" + id
    55  }
    56  
    57  // NormalizeInvocation converts inv to the canonical form.
    58  func NormalizeInvocation(inv *pb.Invocation) {
    59  	SortStringPairs(inv.Tags)
    60  
    61  	changelists := inv.SourceSpec.GetSources().GetChangelists()
    62  	SortGerritChanges(changelists)
    63  }
    64  
    65  // ValidateSourceSpec validates a source specification.
    66  func ValidateSourceSpec(sourceSpec *pb.SourceSpec) error {
    67  	// Treat nil sourceSpec message as empty message.
    68  	if sourceSpec.GetInherit() && sourceSpec.GetSources() != nil {
    69  		return errors.Reason("only one of inherit and sources may be set").Err()
    70  	}
    71  	if sourceSpec.GetSources() != nil {
    72  		if err := ValidateSources(sourceSpec.Sources); err != nil {
    73  			return errors.Annotate(err, "sources").Err()
    74  		}
    75  	}
    76  	return nil
    77  }
    78  
    79  // ValidateSources validates a set of sources.
    80  func ValidateSources(sources *pb.Sources) error {
    81  	if sources == nil {
    82  		return errors.Reason("unspecified").Err()
    83  	}
    84  	if err := ValidateGitilesCommit(sources.GetGitilesCommit()); err != nil {
    85  		return errors.Annotate(err, "gitiles_commit").Err()
    86  	}
    87  
    88  	if len(sources.Changelists) > 10 {
    89  		return errors.Reason("changelists: exceeds maximum of 10 changelists").Err()
    90  	}
    91  	type distinctChangelist struct {
    92  		host   string
    93  		change int64
    94  	}
    95  	clToIndex := make(map[distinctChangelist]int)
    96  
    97  	for i, cl := range sources.Changelists {
    98  		if err := ValidateGerritChange(cl); err != nil {
    99  			return errors.Annotate(err, "changelists[%v]", i).Err()
   100  		}
   101  		cl := distinctChangelist{
   102  			host:   cl.Host,
   103  			change: cl.Change,
   104  		}
   105  		if duplicateIndex, ok := clToIndex[cl]; ok {
   106  			return errors.Reason("changelists[%v]: duplicate change modulo patchset number; same change at changelists[%v]", i, duplicateIndex).Err()
   107  		}
   108  		clToIndex[cl] = i
   109  	}
   110  	return nil
   111  }