go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/protoutil/builder.go (about)

     1  // Copyright 2018 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 protoutil
    16  
    17  import (
    18  	"fmt"
    19  	"regexp"
    20  	"strings"
    21  
    22  	pb "go.chromium.org/luci/buildbucket/proto"
    23  	"go.chromium.org/luci/common/errors"
    24  )
    25  
    26  var (
    27  	projRegex    = regexp.MustCompile(`^[a-z0-9\-_]+$`)
    28  	bucketRegex  = regexp.MustCompile(`^[a-z0-9\-_.]{1,100}$`)
    29  	builderRegex = regexp.MustCompile(`^[a-zA-Z0-9\-_.\(\) ]{1,128}$`)
    30  )
    31  
    32  // ValidateBuilderID validates the given builder ID.
    33  // Bucket and Builder are optional and only validated if specified.
    34  func ValidateBuilderID(b *pb.BuilderID) error {
    35  	switch parts := strings.Split(b.GetBucket(), "."); {
    36  	case !projRegex.MatchString(b.GetProject()):
    37  		return errors.Reason("project must match %q", projRegex).Err()
    38  	case b.GetBucket() != "" && !bucketRegex.MatchString(b.Bucket):
    39  		return errors.Reason("bucket must match %q", bucketRegex).Err()
    40  	case b.GetBuilder() != "" && !builderRegex.MatchString(b.Builder):
    41  		return errors.Reason("builder must match %q", builderRegex).Err()
    42  	case b.GetBucket() != "" && parts[0] == "luci" && len(parts) > 2:
    43  		return errors.Reason("invalid use of v1 bucket in v2 API (hint: try %q)", parts[2]).Err()
    44  	default:
    45  		return nil
    46  	}
    47  }
    48  
    49  // ValidateRequiredBuilderID validates the given builder ID, requiring Bucket
    50  // and Builder.
    51  func ValidateRequiredBuilderID(b *pb.BuilderID) error {
    52  	switch err := ValidateBuilderID(b); {
    53  	case err != nil:
    54  		return err
    55  	case b.Bucket == "":
    56  		return errors.Reason("bucket is required").Err()
    57  	case b.Builder == "":
    58  		return errors.Reason("builder is required").Err()
    59  	default:
    60  		return nil
    61  	}
    62  }
    63  
    64  // FormatBuilderID returns "{project}/{bucket}/{builder}" string.
    65  func FormatBuilderID(id *pb.BuilderID) string {
    66  	return fmt.Sprintf("%s/%s/%s", id.Project, id.Bucket, id.Builder)
    67  }
    68  
    69  // ParseBuilderID parses a "{project}/{bucket}/{builder}" string.
    70  // Opposite of FormatBuilderID.
    71  func ParseBuilderID(s string) (*pb.BuilderID, error) {
    72  	parts := strings.Split(s, "/")
    73  	if len(parts) != 3 {
    74  		return nil, fmt.Errorf("invalid builder id; must have 2 slashes")
    75  	}
    76  	return &pb.BuilderID{
    77  		Project: parts[0],
    78  		Bucket:  parts[1],
    79  		Builder: parts[2],
    80  	}, nil
    81  }
    82  
    83  // FormatBucketID returns "{project}/{bucket}" string.
    84  func FormatBucketID(project, bucket string) string {
    85  	return fmt.Sprintf("%s/%s", project, bucket)
    86  }
    87  
    88  // ParseBucketID parses a "{project}/{bucket}" string.
    89  // Opposite of FormatBucketID.
    90  func ParseBucketID(s string) (string, string, error) {
    91  	switch parts := strings.Split(s, "/"); {
    92  	case len(parts) != 2:
    93  		return "", "", errors.New("invalid bucket id; must have 1 slash")
    94  	case strings.TrimSpace(parts[0]) == "":
    95  		return "", "", errors.New("invalid bucket id; project is empty")
    96  	case strings.TrimSpace(parts[1]) == "":
    97  		return "", "", errors.New("invalid bucket id; bucket is empty")
    98  	default:
    99  		return parts[0], parts[1], nil
   100  	}
   101  }