Files
atlantis/server/core/config/raw/global_cfg.go
2022-03-18 15:18:13 -07:00

269 lines
8.0 KiB
Go

package raw
import (
"fmt"
"regexp"
"strings"
validation "github.com/go-ozzo/ozzo-validation"
"github.com/pkg/errors"
"github.com/runatlantis/atlantis/server/core/config/valid"
)
// GlobalCfg is the raw schema for server-side repo config.
type GlobalCfg struct {
Repos []Repo `yaml:"repos" json:"repos"`
Workflows map[string]Workflow `yaml:"workflows" json:"workflows"`
PolicySets PolicySets `yaml:"policies" json:"policies"`
Metrics Metrics `yaml:"metrics" json:"metrics"`
}
// Repo is the raw schema for repos in the server-side repo config.
type Repo struct {
ID string `yaml:"id" json:"id"`
Branch string `yaml:"branch" json:"branch"`
ApplyRequirements []string `yaml:"apply_requirements" json:"apply_requirements"`
PreWorkflowHooks []WorkflowHook `yaml:"pre_workflow_hooks" json:"pre_workflow_hooks"`
Workflow *string `yaml:"workflow,omitempty" json:"workflow,omitempty"`
PostWorkflowHooks []WorkflowHook `yaml:"post_workflow_hooks" json:"post_workflow_hooks"`
AllowedWorkflows []string `yaml:"allowed_workflows,omitempty" json:"allowed_workflows,omitempty"`
AllowedOverrides []string `yaml:"allowed_overrides" json:"allowed_overrides"`
AllowCustomWorkflows *bool `yaml:"allow_custom_workflows,omitempty" json:"allow_custom_workflows,omitempty"`
DeleteSourceBranchOnMerge *bool `yaml:"delete_source_branch_on_merge,omitempty" json:"delete_source_branch_on_merge,omitempty"`
}
func (g GlobalCfg) Validate() error {
err := validation.ValidateStruct(&g,
validation.Field(&g.Repos),
validation.Field(&g.Workflows),
validation.Field(&g.Metrics),
)
if err != nil {
return err
}
// Check that all workflows referenced by repos are actually defined.
for _, repo := range g.Repos {
if repo.Workflow == nil {
continue
}
name := *repo.Workflow
if name == valid.DefaultWorkflowName {
// The 'default' workflow will always be defined.
continue
}
found := false
for w := range g.Workflows {
if w == name {
found = true
break
}
}
if !found {
return fmt.Errorf("workflow %q is not defined", name)
}
}
// Check that all allowed workflows are defined
for _, repo := range g.Repos {
if repo.AllowedWorkflows == nil {
continue
}
for _, name := range repo.AllowedWorkflows {
if name == valid.DefaultWorkflowName {
// The 'default' workflow will always be defined.
continue
}
found := false
for w := range g.Workflows {
if w == name {
found = true
break
}
}
if !found {
return fmt.Errorf("workflow %q is not defined", name)
}
}
}
return nil
}
func (g GlobalCfg) ToValid(defaultCfg valid.GlobalCfg) valid.GlobalCfg {
workflows := make(map[string]valid.Workflow)
// assumes: globalcfg is always initialized with one repo .*
applyReqs := defaultCfg.Repos[0].ApplyRequirements
var globalApplyReqs []string
for _, req := range applyReqs {
for _, nonOverrideableReq := range valid.NonOverrideableApplyReqs {
if req == nonOverrideableReq {
globalApplyReqs = append(globalApplyReqs, req)
}
}
}
for k, v := range g.Workflows {
validatedWorkflow := v.ToValid(k)
workflows[k] = validatedWorkflow
if k == valid.DefaultWorkflowName {
// Handle the special case where they're redefining the default
// workflow. In this case, our default repo config references
// the "old" default workflow and so needs to be redefined.
defaultCfg.Repos[0].Workflow = &validatedWorkflow
}
}
// Merge in defaults without overriding.
for k, v := range defaultCfg.Workflows {
if _, ok := workflows[k]; !ok {
workflows[k] = v
}
}
var repos []valid.Repo
for _, r := range g.Repos {
repos = append(repos, r.ToValid(workflows, globalApplyReqs))
}
repos = append(defaultCfg.Repos, repos...)
return valid.GlobalCfg{
Repos: repos,
Workflows: workflows,
PolicySets: g.PolicySets.ToValid(),
Metrics: g.Metrics.ToValid(),
}
}
// HasRegexID returns true if r is configured with a regex id instead of an
// exact match id.
func (r Repo) HasRegexID() bool {
return strings.HasPrefix(r.ID, "/") && strings.HasSuffix(r.ID, "/")
}
// HasRegexBranch returns true if a branch regex was set.
func (r Repo) HasRegexBranch() bool {
return strings.HasPrefix(r.Branch, "/") && strings.HasSuffix(r.Branch, "/")
}
func (r Repo) Validate() error {
idValid := func(value interface{}) error {
id := value.(string)
if !r.HasRegexID() {
return nil
}
_, err := regexp.Compile(id[1 : len(id)-1])
return errors.Wrapf(err, "parsing: %s", id)
}
branchValid := func(value interface{}) error {
branch := value.(string)
if !r.HasRegexBranch() {
return nil
}
_, err := regexp.Compile(branch[1 : len(branch)-1])
return errors.Wrapf(err, "parsing: %s", branch)
}
overridesValid := func(value interface{}) error {
overrides := value.([]string)
for _, o := range overrides {
if o != valid.ApplyRequirementsKey && o != valid.WorkflowKey && o != valid.DeleteSourceBranchOnMergeKey {
return fmt.Errorf("%q is not a valid override, only %q, %q and %q are supported", o, valid.ApplyRequirementsKey, valid.WorkflowKey, valid.DeleteSourceBranchOnMergeKey)
}
}
return nil
}
workflowExists := func(value interface{}) error {
// We validate workflows in ParserValidator.validateRepoWorkflows
// because we need the list of workflows to validate.
return nil
}
deleteSourceBranchOnMergeValid := func(value interface{}) error {
//TOBE IMPLEMENTED
return nil
}
return validation.ValidateStruct(&r,
validation.Field(&r.ID, validation.Required, validation.By(idValid)),
validation.Field(&r.Branch, validation.By(branchValid)),
validation.Field(&r.AllowedOverrides, validation.By(overridesValid)),
validation.Field(&r.ApplyRequirements, validation.By(validApplyReq)),
validation.Field(&r.Workflow, validation.By(workflowExists)),
validation.Field(&r.DeleteSourceBranchOnMerge, validation.By(deleteSourceBranchOnMergeValid)),
)
}
func (r Repo) ToValid(workflows map[string]valid.Workflow, globalApplyReqs []string) valid.Repo {
var id string
var idRegex *regexp.Regexp
if r.HasRegexID() {
withoutSlashes := r.ID[1 : len(r.ID)-1]
// Safe to use MustCompile because we test it in Validate().
idRegex = regexp.MustCompile(withoutSlashes)
} else {
id = r.ID
}
var branchRegex *regexp.Regexp
if r.HasRegexBranch() {
withoutSlashes := r.Branch[1 : len(r.Branch)-1]
// Safe to use MustCompile because we test it in Validate().
branchRegex = regexp.MustCompile(withoutSlashes)
}
var workflow *valid.Workflow
if r.Workflow != nil {
// This key is guaranteed to exist because we test for it in
// ParserValidator.validateRepoWorkflows.
ptr := workflows[*r.Workflow]
workflow = &ptr
}
var preWorkflowHooks []*valid.WorkflowHook
if len(r.PreWorkflowHooks) > 0 {
for _, hook := range r.PreWorkflowHooks {
preWorkflowHooks = append(preWorkflowHooks, hook.ToValid())
}
}
var postWorkflowHooks []*valid.WorkflowHook
if len(r.PostWorkflowHooks) > 0 {
for _, hook := range r.PostWorkflowHooks {
postWorkflowHooks = append(postWorkflowHooks, hook.ToValid())
}
}
var mergedApplyReqs []string
mergedApplyReqs = append(mergedApplyReqs, r.ApplyRequirements...)
// only add global reqs if they don't exist already.
OUTER:
for _, globalReq := range globalApplyReqs {
for _, currReq := range r.ApplyRequirements {
if globalReq == currReq {
continue OUTER
}
}
mergedApplyReqs = append(mergedApplyReqs, globalReq)
}
return valid.Repo{
ID: id,
IDRegex: idRegex,
BranchRegex: branchRegex,
ApplyRequirements: mergedApplyReqs,
PreWorkflowHooks: preWorkflowHooks,
Workflow: workflow,
PostWorkflowHooks: postWorkflowHooks,
AllowedWorkflows: r.AllowedWorkflows,
AllowedOverrides: r.AllowedOverrides,
AllowCustomWorkflows: r.AllowCustomWorkflows,
DeleteSourceBranchOnMerge: r.DeleteSourceBranchOnMerge,
}
}