blob: aef5fde7dcb91a282f1e03a7f9db9008046885ee [file] [log] [blame]
// Copyright 2022 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rpc
import (
"fmt"
"regexp"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/analysis/internal/clustering"
"go.chromium.org/luci/analysis/internal/clustering/rules"
"go.chromium.org/luci/analysis/internal/config"
)
// Regular expressions for matching resource names used in APIs.
var (
GenericKeyPattern = "[a-z0-9\\-]+"
RuleNameRe = regexp.MustCompile(`^projects/(` + config.ProjectRePattern + `)/rules/(` + rules.RuleIDRePattern + `)$`)
// ClusterNameRe performs partial validation of a cluster resource name.
// Cluster algorithm and ID must be further validated by
// ClusterID.Validate().
ClusterNameRe = regexp.MustCompile(`^projects/(` + config.ProjectRePattern + `)/clusters/(` + GenericKeyPattern + `)/(` + GenericKeyPattern + `)$`)
// ClusterFailuresNameRe performs a partial validation of the resource
// name for a cluster's failures.
// Cluster algorithm and ID must be further validated by
// ClusterID.Validate().
ClusterFailuresNameRe = regexp.MustCompile(`^projects/(` + config.ProjectRePattern + `)/clusters/(` + GenericKeyPattern + `)/(` + GenericKeyPattern + `)/failures$`)
// ClusterExoneratedTestVariantsNameRe performs a partial validation of
// the resource name for a cluster's exonerated test variants.
// Cluster algorithm and ID must be further validated by
// ClusterID.Validate().
ClusterExoneratedTestVariantsNameRe = regexp.MustCompile(`^projects/(` + config.ProjectRePattern + `)/clusters/(` + GenericKeyPattern + `)/(` + GenericKeyPattern + `)/exoneratedTestVariants$`)
ProjectNameRe = regexp.MustCompile(`^projects/(` + config.ProjectRePattern + `)$`)
ProjectConfigNameRe = regexp.MustCompile(`^projects/(` + config.ProjectRePattern + `)/config$`)
ReclusteringProgressNameRe = regexp.MustCompile(`^projects/(` + config.ProjectRePattern + `)/reclusteringProgress$`)
)
// parseRuleName parses a rule resource name into its constituent ID parts.
func parseRuleName(name string) (project, ruleID string, err error) {
match := RuleNameRe.FindStringSubmatch(name)
if match == nil {
return "", "", errors.New("invalid rule name, expected format: projects/{project}/rules/{rule_id}")
}
return match[1], match[2], nil
}
// parseProjectName parses a project resource name into a project ID.
func parseProjectName(name string) (project string, err error) {
match := ProjectNameRe.FindStringSubmatch(name)
if match == nil {
return "", errors.New("invalid project name, expected format: projects/{project}")
}
return match[1], nil
}
// parseProjectConfigName parses a project config resource name into a project ID.
func parseProjectConfigName(name string) (project string, err error) {
match := ProjectConfigNameRe.FindStringSubmatch(name)
if match == nil {
return "", errors.New("invalid project config name, expected format: projects/{project}/config")
}
return match[1], nil
}
// parseReclusteringProgressName parses a reclustering progress resource name
// into its constituent project ID part.
func parseReclusteringProgressName(name string) (project string, err error) {
match := ReclusteringProgressNameRe.FindStringSubmatch(name)
if match == nil {
return "", errors.New("invalid reclustering progress name, expected format: projects/{project}/reclusteringProgress")
}
return match[1], nil
}
// parseClusterName parses a cluster resource name into its constituent ID
// parts. Algorithm aliases are resolved to concrete algorithm names.
func parseClusterName(name string) (project string, clusterID clustering.ClusterID, err error) {
match := ClusterNameRe.FindStringSubmatch(name)
if match == nil {
return "", clustering.ClusterID{}, errors.New("invalid cluster name, expected format: projects/{project}/clusters/{cluster_alg}/{cluster_id}")
}
algorithm := resolveAlgorithm(match[2])
id := match[3]
cID := clustering.ClusterID{Algorithm: algorithm, ID: id}
if err := cID.Validate(); err != nil {
return "", clustering.ClusterID{}, errors.Annotate(err, "invalid cluster identity").Err()
}
return match[1], cID, nil
}
// parseClusterFailuresName parses the resource name for a cluster's failures
// into its constituent ID parts. Algorithm aliases are resolved to
// concrete algorithm names.
func parseClusterFailuresName(name string) (project string, clusterID clustering.ClusterID, err error) {
match := ClusterFailuresNameRe.FindStringSubmatch(name)
if match == nil {
return "", clustering.ClusterID{}, errors.New("invalid cluster failures name, expected format: projects/{project}/clusters/{cluster_alg}/{cluster_id}/failures")
}
algorithm := resolveAlgorithm(match[2])
id := match[3]
cID := clustering.ClusterID{Algorithm: algorithm, ID: id}
if err := cID.Validate(); err != nil {
return "", clustering.ClusterID{}, errors.Annotate(err, "invalid cluster identity").Err()
}
return match[1], cID, nil
}
// parseClusterExoneratedTestVariantsName parses the resource name for a cluster's
// exonerated test variants into its constituent ID parts. Algorithm aliases are
// resolved to concrete algorithm names.
func parseClusterExoneratedTestVariantsName(name string) (project string, clusterID clustering.ClusterID, err error) {
match := ClusterExoneratedTestVariantsNameRe.FindStringSubmatch(name)
if match == nil {
return "", clustering.ClusterID{}, errors.New("invalid cluster failures name, expected format: projects/{project}/clusters/{cluster_alg}/{cluster_id}/exoneratedTestVariants")
}
algorithm := resolveAlgorithm(match[2])
id := match[3]
cID := clustering.ClusterID{Algorithm: algorithm, ID: id}
if err := cID.Validate(); err != nil {
return "", clustering.ClusterID{}, errors.Annotate(err, "invalid cluster identity").Err()
}
return match[1], cID, nil
}
func ruleName(project, ruleID string) string {
return fmt.Sprintf("projects/%s/rules/%s", project, ruleID)
}