| // 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, ®istry); 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 |
| } |