go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/config_service/internal/rules/project.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 rules
    16  
    17  import (
    18  	"google.golang.org/protobuf/encoding/prototext"
    19  
    20  	"go.chromium.org/luci/common/data/stringset"
    21  	cfgcommonpb "go.chromium.org/luci/common/proto/config"
    22  	"go.chromium.org/luci/config"
    23  	"go.chromium.org/luci/config/validation"
    24  
    25  	"go.chromium.org/luci/config_service/internal/common"
    26  )
    27  
    28  func validateProjectsCfg(vctx *validation.Context, configSet, path string, content []byte) error {
    29  	vctx.SetFile(path)
    30  	cfg := &cfgcommonpb.ProjectsCfg{}
    31  	if err := prototext.Unmarshal(content, cfg); err != nil {
    32  		vctx.Errorf("invalid projects proto: %s", err)
    33  		return nil
    34  	}
    35  	seenTeams := stringset.New(len(cfg.GetTeams()))
    36  	for i, team := range cfg.GetTeams() {
    37  		vctx.Enter("teams #%d", i)
    38  		vctx.Enter("name")
    39  		validateUniqueID(vctx, team.GetName(), seenTeams, nil)
    40  		vctx.Exit()
    41  		if len(team.GetMaintenanceContact()) == 0 {
    42  			vctx.Errorf("maintenance_contact is required")
    43  		}
    44  		for i, contact := range team.GetMaintenanceContact() {
    45  			vctx.Enter("maintenance_contact #%d", i)
    46  			validateEmail(vctx, contact)
    47  			vctx.Exit()
    48  		}
    49  		if len(team.GetEscalationContact()) == 0 {
    50  			vctx.Warningf("escalation_contact is recommended")
    51  		}
    52  		for i, contact := range team.GetEscalationContact() {
    53  			vctx.Enter("escalation_contact #%d", i)
    54  			validateEmail(vctx, contact)
    55  			vctx.Exit()
    56  		}
    57  		vctx.Exit()
    58  	}
    59  	validateSorted[*cfgcommonpb.Team](vctx, cfg.GetTeams(), "teams", func(t *cfgcommonpb.Team) string {
    60  		return t.GetName()
    61  	})
    62  
    63  	seenProjects := stringset.New(len(cfg.GetProjects()))
    64  	for i, project := range cfg.GetProjects() {
    65  		vctx.Enter("projects #%d", i)
    66  		vctx.Enter("id")
    67  		validateUniqueID(vctx, project.GetId(), seenProjects, func(vctx *validation.Context, id string) {
    68  			if _, err := config.ProjectSet(id); err != nil {
    69  				vctx.Errorf("invalid id: %s", err)
    70  			}
    71  		})
    72  		vctx.Exit()
    73  
    74  		vctx.Enter("gitiles_location")
    75  		if err := common.ValidateGitilesLocation(project.GetGitilesLocation()); err != nil {
    76  			vctx.Error(err)
    77  		}
    78  		vctx.Exit()
    79  
    80  		vctx.Enter("owned_by")
    81  		switch ownedBy := project.GetOwnedBy(); {
    82  		case ownedBy == "":
    83  			vctx.Errorf("not specified")
    84  		case !seenTeams.Has(ownedBy):
    85  			vctx.Errorf(`unknown team %q`, ownedBy)
    86  		}
    87  		vctx.Exit()
    88  
    89  		vctx.Exit()
    90  	}
    91  	return nil
    92  }
    93  
    94  func validateProjectMetadata(vctx *validation.Context, configSet, path string, content []byte) error {
    95  	vctx.SetFile(path)
    96  	cfg := &cfgcommonpb.ProjectCfg{}
    97  	if err := prototext.Unmarshal(content, cfg); err != nil {
    98  		vctx.Errorf("invalid project proto: %s", err)
    99  		return nil
   100  	}
   101  	if cfg.GetName() == "" {
   102  		vctx.Errorf("name is not specified")
   103  	}
   104  	for i, access := range cfg.GetAccess() {
   105  		vctx.Enter("access #%d", i)
   106  		validateAccess(vctx, access)
   107  		vctx.Exit()
   108  	}
   109  	return nil
   110  }