go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/config/set.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 config
    16  
    17  import (
    18  	"fmt"
    19  	"regexp"
    20  	"strings"
    21  )
    22  
    23  // ServiceNamePattern is the regexp pattern string that matches valid service
    24  // name.
    25  const ServiceNamePattern = `[a-z0-9\-_]+`
    26  
    27  // serviceNameRegexp matches valid service name
    28  var serviceNameRegexp = regexp.MustCompile(fmt.Sprintf(`^%s$`, ServiceNamePattern))
    29  
    30  // Set is a name of a configuration set: a bunch of config files versioned and
    31  // stored as a single unit in a same repository.
    32  //
    33  // A config set name consists of a domain and a target.
    34  //
    35  //   - Service config sets are config sets in the "services" domain, with the
    36  //     service name as the target.
    37  //   - Project config sets are config sets in the "projects" domain. The target
    38  //     is the project name.
    39  type Set string
    40  
    41  // Domain describes the domain of the config set.
    42  type Domain string
    43  
    44  const (
    45  	// ProjectDomain is the domain of project config set(e.g. projects/chromium).
    46  	ProjectDomain Domain = "projects"
    47  	// ServiceDomain is the domain of service config set (e.g.
    48  	// services/luci-config).
    49  	ServiceDomain Domain = "services"
    50  )
    51  
    52  // ServiceSet returns the name of a config set for the specified service.
    53  //
    54  // Returns error if the service name doesn't match `ServiceNamePattern`.
    55  func ServiceSet(service string) (Set, error) {
    56  	if !serviceNameRegexp.MatchString(service) {
    57  		return "", fmt.Errorf("invalid service name %q, expected to match %q", service, ServiceNamePattern)
    58  	}
    59  	return Set(fmt.Sprintf("%s/%s", ServiceDomain, service)), nil
    60  }
    61  
    62  // MustServiceSet is like `ServiceSet` but panic on invalid service name.
    63  func MustServiceSet(service string) Set {
    64  	cs, err := ServiceSet(service)
    65  	if err != nil {
    66  		panic(err)
    67  	}
    68  	return cs
    69  }
    70  
    71  // ProjectSet returns the config set for the specified project.
    72  //
    73  // Returns error if the project name is invalid. See `ValidateProjectName`.
    74  func ProjectSet(project string) (Set, error) {
    75  	if err := ValidateProjectName(project); err != nil {
    76  		return "", fmt.Errorf("invalid project name: %w", err)
    77  	}
    78  	return Set(fmt.Sprintf("%s/%s", ProjectDomain, project)), nil
    79  }
    80  
    81  // MustProjectSet is like `ProjectSet` but panic on invalid project name.
    82  func MustProjectSet(project string) Set {
    83  	cs, err := ProjectSet(project)
    84  	if err != nil {
    85  		panic(err)
    86  	}
    87  	return cs
    88  }
    89  
    90  // Split splits a Set into its domain, target components.
    91  func (cs Set) Split() (domain Domain, target string) {
    92  	p := strings.SplitN(string(cs), "/", 2)
    93  	if len(p) == 1 {
    94  		return Domain(p[0]), ""
    95  	}
    96  	return Domain(p[0]), p[1]
    97  }
    98  
    99  // Service returns a service name for a service config set or empty string for
   100  // all other sets.
   101  func (cs Set) Service() string {
   102  	domain, target := cs.Split()
   103  	if domain == ServiceDomain {
   104  		return target
   105  	}
   106  	return ""
   107  }
   108  
   109  // Project returns a project name for a project config set or empty string for
   110  // all other sets.
   111  func (cs Set) Project() string {
   112  	domain, target := cs.Split()
   113  	if domain == ProjectDomain {
   114  		return target
   115  	}
   116  	return ""
   117  }
   118  
   119  // Domain returns the domain of the config set.
   120  func (cs Set) Domain() Domain {
   121  	domain, _ := cs.Split()
   122  	return domain
   123  }
   124  
   125  // Validate checks that the config set is well-formed.
   126  func (cs Set) Validate() error {
   127  	switch domain, target := cs.Split(); domain {
   128  	case "":
   129  		return fmt.Errorf("can not extract domain from config set %q. expected syntax \"domain/target\"", cs)
   130  	case ProjectDomain:
   131  		_, err := ProjectSet(target)
   132  		return err
   133  	case ServiceDomain:
   134  		_, err := ServiceSet(target)
   135  		return err
   136  	default:
   137  		return fmt.Errorf("unknown domain %q for config set %q; currently supported domains [%s, %s]", domain, cs, ProjectDomain, ServiceDomain)
   138  	}
   139  }