blob: 8feea31a569b8bcdb5d2ee6d9599ca50753f3a5d [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Functions in this file parse a yaml string that lists all Cobalt customers
// and their associated projects. It is used in order to find where the project
// configs are stored.
package config_parser
import (
"config"
"fmt"
"math"
"regexp"
"yamlpb"
"github.com/golang/glog"
)
var validNameRegexp = regexp.MustCompile("^[a-zA-Z][_a-zA-Z0-9]{1,81}$")
// Parse a list of customers appending all their projects to the ProjectConfigData
// list that was passed in.
func parseCustomerList(content string, l *[]ProjectConfigData) (err error) {
var registry config.CobaltRegistry
if err := yamlpb.UnmarshalString(content, &registry); err != nil {
return fmt.Errorf("Error while parsing the yaml for a list of Cobalt customer definitions: %v", err)
}
customerNames := map[string]bool{}
customerIds := map[uint32]bool{}
deletedCustomerIds := map[uint32]bool{}
for _, id := range registry.DeletedCustomerIds {
deletedCustomerIds[id] = true
}
for i, customer := range registry.Customers {
if customer.CustomerName == "" {
return fmt.Errorf("customer_name field is missing in entry %v of the customer list.", i)
}
if !validNameRegexp.MatchString(customer.CustomerName) {
return fmt.Errorf("Customer name '%v' is invalid. Customer names must match the regular expression '%v'", customer.CustomerName, validNameRegexp)
}
if customerNames[customer.CustomerName] {
return fmt.Errorf("Customer name '%v' repeated. Customer names must be unique.", customer.CustomerName)
}
customerNames[customer.CustomerName] = true
if customer.CustomerId == 0 {
return fmt.Errorf("Missing customer id for '%v'.", customer.CustomerName)
}
if customer.CustomerId <= 0 {
return fmt.Errorf("Customer id for '%v' is negative. Customer ids must be positive.", customer.CustomerName)
}
if customer.CustomerId == math.MaxInt32 && customer.CustomerName != "cobalt_internal" {
return fmt.Errorf("Customer '%v' tried to register id %d, which is reserved for the internal customer 'cobalt_internal'.", customer.CustomerName, math.MaxInt32)
}
if customerIds[customer.CustomerId] {
return fmt.Errorf("Customer id %v for customer '%v' repeated. Customer IDs must be unique.", customerId, customer.CustomerName)
}
if deletedCustomerIds[customer.CustomerId] {
return fmt.Errorf("Customer id %v for customer '%v' was previously used and deleted. Customer IDs must be unique and not be reused.", customerId, customer.CustomerName)
}
customerIds[customer.CustomerId] = true
if len(customer.Projects) == 0 {
glog.Warningf("No projects found for customer '%v'.", customer.CustomerName)
continue
}
c := []ProjectConfigData{}
if err := populateProjectList(customer.Projects, &c); err != nil {
return fmt.Errorf("Project list for customer %v is invalid: %v", customer.CustomerName, err)
}
for i := range c {
c[i].CustomerId = uint32(customer.CustomerId)
c[i].CustomerName = customer.CustomerName
if len(customer.ExperimentsNamespaces) > 0 {
for _, exp := range customer.ExperimentsNamespaces {
c[i].CustomerExperimentsNamespaces = append(c[i].CustomerExperimentsNamespaces, exp)
}
} else {
c[i].CustomerExperimentsNamespaces = []interface{}{}
}
for _, deletedProjectId := range customer.DeletedProjectIds {
c[i].DeletedProjectIds = append(c[i].DeletedProjectIds, uint32(deletedProjectId))
}
}
*l = append(*l, c...)
}
for _, deletedCustomerId := range registry.DeletedCustomerIds {
c := ProjectConfigData{}
c.CustomerId = uint32(deletedCustomerId)
c.IsDeletedCustomer = true
*l = append(*l, c)
}
return nil
}
// populateProjectList populates a list of cobalt projects given in the form of
// an array of ProjectConfig proto objects. For more details, see
// populateProjectConfigData. This function also validates that project names and
// ids are unique.
func populateProjectList(projects []*config.ProjectConfig, l *[]ProjectConfigData) (err error) {
projectNames := map[string]bool{}
projectIds := map[uint32]bool{}
for i, v := range projects {
c := ProjectConfigData{}
if err := populateProjectConfigData(v, &c); err != nil {
return fmt.Errorf("Error in entry %v in project list: %v", i, err)
}
if projectNames[c.ProjectName] {
return fmt.Errorf("Project name '%v' repeated. Project names must be unique.", c.ProjectName)
}
projectNames[c.ProjectName] = true
if projectIds[c.ProjectId] {
return fmt.Errorf("Project id %v for project %v is repeated. Project ids must be unique.", c.ProjectId, c.ProjectName)
}
projectIds[c.ProjectId] = true
*l = append(*l, c)
}
return
}
// populateProjectConfigData populates a cobalt project given in the form of a
// ProjectConfig proto object. It populates the name, projectId and contact
// fields of the ProjectConfigData it returns. It also validates those values.
// The project id must be a positive integer. The project must have name, id
// and contact fields.
func populateProjectConfigData(p *config.ProjectConfig, c *ProjectConfigData) (err error) {
if p.ProjectName == "" {
return fmt.Errorf("Missing name in project list: %v", p)
}
c.ProjectName = p.ProjectName
if !validNameRegexp.MatchString(c.ProjectName) {
return fmt.Errorf("Project name '%v' is invalid. Project names must match the regular expression '%v'", c.ProjectName, validNameRegexp)
}
if p.ProjectId <= 0 {
return fmt.Errorf("ID for project %v is missing or not a positive integer.", c.ProjectName)
}
c.ProjectId = uint32(p.ProjectId)
if p.ProjectContact == "" {
return fmt.Errorf("Missing contact for project %v.", c.ProjectName)
}
c.Contact = p.ProjectContact
if p.AppPackageIdentifier != "" {
c.AppPackageIdentifier = p.AppPackageIdentifier
}
c.ProjectExperimentsNamespaces = []interface{}{}
if len(p.ExperimentsNamespaces) > 0 {
for _, en := range p.ExperimentsNamespaces {
c.ProjectExperimentsNamespaces = append(c.ProjectExperimentsNamespaces, en)
}
}
return nil
}
func toStrMap(i map[interface{}]interface{}) (o map[string]interface{}, err error) {
o = make(map[string]interface{})
for k, v := range i {
s, ok := k.(string)
if !ok {
return nil, fmt.Errorf("Expected a yaml map with string keys. '%v' is not a string.", k)
}
o[s] = v
}
return o, nil
}