blob: 153822ce271f69c8b26cc88d8f11d38d8c9bafc9 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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.
// 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 (
"fmt"
"math"
"regexp"
yaml "github.com/go-yaml/yaml"
"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 ProjectConfig
// list that was passed in.
func parseCustomerList(content string, l *[]ProjectConfig) (err error) {
var y []map[string]interface{}
if err := yaml.Unmarshal([]byte(content), &y); 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[int]bool{}
for i, customer := range y {
v, ok := customer["customer_name"]
if !ok {
return fmt.Errorf("customer_name field is missing in entry %v of the customer list.", i)
}
customerName, ok := v.(string)
if !ok {
return fmt.Errorf("Customer name '%v' is not a string.", v)
}
if !validNameRegexp.MatchString(customerName) {
return fmt.Errorf("Customer name '%v' is invalid. Customer names must match the regular expression '%v'", customerName, validNameRegexp)
}
if customerNames[customerName] {
return fmt.Errorf("Customer name '%v' repeated. Customer names must be unique.", customerName)
}
customerNames[customerName] = true
v, ok = customer["customer_id"]
if !ok {
return fmt.Errorf("Missing customer id for '%v'.", customerName)
}
customerId, ok := v.(int)
if !ok {
return fmt.Errorf("Customer id '%v' for '%v' is not numeric.", customerId, customerName)
}
if customerId < 0 {
return fmt.Errorf("Customer id for '%v' is negative. Customer ids must be positive.", customerName)
}
if customerId == math.MaxInt32 && customerName != "cobalt_internal" {
return fmt.Errorf("Customer '%v' tried to register id %d, which is reserved for the internal customer 'cobalt_internal'.", customerName, math.MaxInt32)
}
if customerIds[customerId] {
return fmt.Errorf("Customer id %v for customer '%v' repeated. Customer names must be unique.", customerId, customerName)
}
customerIds[customerId] = true
projectsAsI, ok := customer["projects"]
if !ok {
glog.Warningf("No projects found for customer '%v'.", customerName)
continue
}
projectsAsList, ok := projectsAsI.([]interface{})
if !ok {
fmt.Errorf("Project list for customer %v is invalid. It should be a yaml list.", customerName)
}
c := []ProjectConfig{}
if err := populateProjectList(projectsAsList, &c); err != nil {
return fmt.Errorf("Project list for customer %v is invalid: %v", customerName, err)
}
for i := range c {
c[i].CustomerId = uint32(customerId)
c[i].CustomerName = customerName
}
*l = append(*l, c...)
}
return nil
}
// populateProjectList populates a list of cobalt projects given in the form of
// a map as returned by a call to yaml.Unmarshal. For more details, see
// populateProjectConfig. This function also validates that project names and
// ids are unique.
func populateProjectList(y []interface{}, l *[]ProjectConfig) (err error) {
projectNames := map[string]bool{}
projectIds := map[uint32]bool{}
for i, v := range y {
m, ok := v.(map[interface{}]interface{})
if !ok {
return fmt.Errorf("Entry %v in project list is not a yaml map.", i)
}
p, err := toStrMap(m)
if err != nil {
return fmt.Errorf("Entry %v in project list is not valid: %v", i, err)
}
c := ProjectConfig{}
if err := populateProjectConfig(p, &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
}
// populateProjectConfig populates a cobalt project given in the form of a map
// as returned by a call to yaml.Unmarshal. It populates the name, projectId and
// contact fields of the ProjectConfig 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 populateProjectConfig(p map[string]interface{}, c *ProjectConfig) (err error) {
v, ok := p["project_name"]
if !ok {
return fmt.Errorf("Missing name in project list.")
}
c.ProjectName, ok = v.(string)
if !ok {
return fmt.Errorf("Project name '%v' is not a string.", v)
}
if !validNameRegexp.MatchString(c.ProjectName) {
return fmt.Errorf("Project name '%v' is invalid. Project names must match the regular expression '%v'", c.ProjectName, validNameRegexp)
}
c.CobaltVersion = CobaltVersion1
v, ok = p["cobalt_version"]
if ok {
version, ok := v.(int)
if !ok {
return fmt.Errorf("Cobalt version '%v' for project %v is not an integer.", v, c.ProjectName)
}
if version == 0 {
c.CobaltVersion = CobaltVersion0
} else if version == 1 {
c.CobaltVersion = CobaltVersion1
} else {
return fmt.Errorf("Version '%v' for project %v is not '1' or '0'.", version, c.ProjectName)
}
}
if c.CobaltVersion == CobaltVersion0 {
return fmt.Errorf("Project %v is declared to use config version 0.1, which is no longer supported.", c.ProjectName)
}
if c.CobaltVersion == CobaltVersion1 {
v, ok := p["project_id"]
if !ok {
return fmt.Errorf("Missing project_id for project %v.", c.ProjectName)
}
projectId, ok := v.(int)
if !ok {
return fmt.Errorf("ID '%v' for project %v is not an integer.", v, c.ProjectName)
}
if projectId <= 0 {
return fmt.Errorf("ID for project %v is not a positive integer.", c.ProjectName)
}
c.ProjectId = uint32(projectId)
}
v, ok = p["project_contact"]
if !ok {
return fmt.Errorf("Missing contact for project %v.", c.ProjectName)
}
c.Contact, ok = v.(string)
if !ok {
return fmt.Errorf("Contact '%v' for project %v is not a string.", v, c.ProjectName)
}
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
}