mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-08-07 11:28:34 +00:00
399 lines
13 KiB
Go
399 lines
13 KiB
Go
package raw
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
validation "github.com/go-ozzo/ozzo-validation"
|
|
"github.com/runatlantis/atlantis/server/core/config/valid"
|
|
"github.com/runatlantis/atlantis/server/utils"
|
|
)
|
|
|
|
// 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"`
|
|
TeamAuthz TeamAuthz `yaml:"team_authz" json:"team_authz"`
|
|
}
|
|
|
|
// 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"`
|
|
RepoConfigFile string `yaml:"repo_config_file" json:"repo_config_file"`
|
|
PlanRequirements []string `yaml:"plan_requirements" json:"plan_requirements"`
|
|
ApplyRequirements []string `yaml:"apply_requirements" json:"apply_requirements"`
|
|
ImportRequirements []string `yaml:"import_requirements" json:"import_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"`
|
|
RepoLocking *bool `yaml:"repo_locking,omitempty" json:"repo_locking,omitempty"`
|
|
RepoLocks *RepoLocks `yaml:"repo_locks,omitempty" json:"repo_locks,omitempty"`
|
|
PolicyCheck *bool `yaml:"policy_check,omitempty" json:"policy_check,omitempty"`
|
|
CustomPolicyCheck *bool `yaml:"custom_policy_check,omitempty" json:"custom_policy_check,omitempty"`
|
|
AutoDiscover *AutoDiscover `yaml:"autodiscover,omitempty" json:"autodiscover,omitempty"`
|
|
SilencePRComments []string `yaml:"silence_pr_comments,omitempty" json:"silence_pr_comments,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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate supported SilencePRComments values.
|
|
for _, repo := range g.Repos {
|
|
if repo.SilencePRComments == nil {
|
|
continue
|
|
}
|
|
for _, silenceStage := range repo.SilencePRComments {
|
|
if !utils.SlicesContains(valid.AllowedSilencePRComments, silenceStage) {
|
|
return fmt.Errorf(
|
|
"server-side repo config '%s' key value of '%s' is not supported, supported values are [%s]",
|
|
valid.SilencePRCommentsKey,
|
|
silenceStage,
|
|
strings.Join(valid.AllowedSilencePRComments, ", "),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
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 .*
|
|
globalPlanReqs := defaultCfg.Repos[0].PlanRequirements
|
|
applyReqs := defaultCfg.Repos[0].ApplyRequirements
|
|
var globalApplyReqs []string
|
|
for _, req := range applyReqs {
|
|
for _, nonOverridableReq := range valid.NonOverridableApplyReqs {
|
|
if req == nonOverridableReq {
|
|
globalApplyReqs = append(globalApplyReqs, req)
|
|
}
|
|
}
|
|
}
|
|
globalImportReqs := defaultCfg.Repos[0].ImportRequirements
|
|
|
|
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, globalPlanReqs, globalApplyReqs, globalImportReqs))
|
|
}
|
|
repos = append(defaultCfg.Repos, repos...)
|
|
|
|
return valid.GlobalCfg{
|
|
Repos: repos,
|
|
Workflows: workflows,
|
|
PolicySets: g.PolicySets.ToValid(),
|
|
Metrics: g.Metrics.ToValid(),
|
|
TeamAuthz: g.TeamAuthz.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])
|
|
if err != nil {
|
|
return fmt.Errorf("parsing: %s: %w", id, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
branchValid := func(value interface{}) error {
|
|
branch := value.(string)
|
|
if branch == "" {
|
|
return nil
|
|
}
|
|
if !strings.HasPrefix(branch, "/") || !strings.HasSuffix(branch, "/") {
|
|
return errors.New("regex must begin and end with a slash '/'")
|
|
}
|
|
withoutSlashes := branch[1 : len(branch)-1]
|
|
_, err := regexp.Compile(withoutSlashes)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing: %s: %w", branch, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
repoConfigFileValid := func(value interface{}) error {
|
|
repoConfigFile := value.(string)
|
|
if repoConfigFile == "" {
|
|
return nil
|
|
}
|
|
if strings.HasPrefix(repoConfigFile, "/") {
|
|
return errors.New("must not starts with a slash '/'")
|
|
}
|
|
if strings.Contains(repoConfigFile, "../") || strings.Contains(repoConfigFile, "..\\") {
|
|
return errors.New("must not contains parent directory path like '../'")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
overridesValid := func(value interface{}) error {
|
|
overrides := value.([]string)
|
|
for _, o := range overrides {
|
|
if o != valid.PlanRequirementsKey && o != valid.ApplyRequirementsKey && o != valid.ImportRequirementsKey && o != valid.WorkflowKey && o != valid.DeleteSourceBranchOnMergeKey && o != valid.RepoLockingKey && o != valid.RepoLocksKey && o != valid.PolicyCheckKey && o != valid.CustomPolicyCheckKey && o != valid.SilencePRCommentsKey {
|
|
return fmt.Errorf("%q is not a valid override, only %q, %q, %q, %q, %q, %q, %q, %q, %q, and %q are supported", o, valid.PlanRequirementsKey, valid.ApplyRequirementsKey, valid.ImportRequirementsKey, valid.WorkflowKey, valid.DeleteSourceBranchOnMergeKey, valid.RepoLockingKey, valid.RepoLocksKey, valid.PolicyCheckKey, valid.CustomPolicyCheckKey, valid.SilencePRCommentsKey)
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
autoDiscoverValid := func(value interface{}) error {
|
|
autoDiscover := value.(*AutoDiscover)
|
|
if autoDiscover != nil {
|
|
return autoDiscover.Validate()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
repoLocksValid := func(value interface{}) error {
|
|
repoLocks := value.(*RepoLocks)
|
|
if repoLocks != nil {
|
|
return repoLocks.Validate()
|
|
}
|
|
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.RepoConfigFile, validation.By(repoConfigFileValid)),
|
|
validation.Field(&r.AllowedOverrides, validation.By(overridesValid)),
|
|
validation.Field(&r.PlanRequirements, validation.By(validPlanReq)),
|
|
validation.Field(&r.ApplyRequirements, validation.By(validApplyReq)),
|
|
validation.Field(&r.ImportRequirements, validation.By(validImportReq)),
|
|
validation.Field(&r.Workflow, validation.By(workflowExists)),
|
|
validation.Field(&r.DeleteSourceBranchOnMerge, validation.By(deleteSourceBranchOnMergeValid)),
|
|
validation.Field(&r.AutoDiscover, validation.By(autoDiscoverValid)),
|
|
validation.Field(&r.RepoLocks, validation.By(repoLocksValid)),
|
|
)
|
|
}
|
|
|
|
func (r Repo) ToValid(workflows map[string]valid.Workflow, globalPlanReqs []string, globalApplyReqs []string, globalImportReqs []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 mergedPlanReqs []string
|
|
mergedPlanReqs = append(mergedPlanReqs, r.PlanRequirements...)
|
|
var mergedApplyReqs []string
|
|
mergedApplyReqs = append(mergedApplyReqs, r.ApplyRequirements...)
|
|
var mergedImportReqs []string
|
|
mergedImportReqs = append(mergedImportReqs, r.ImportRequirements...)
|
|
|
|
// only add global reqs if they don't exist already.
|
|
OuterGlobalPlanReqs:
|
|
for _, globalReq := range globalPlanReqs {
|
|
for _, currReq := range r.PlanRequirements {
|
|
if globalReq == currReq {
|
|
continue OuterGlobalPlanReqs
|
|
}
|
|
}
|
|
|
|
// dont add policy_check step if repo have it explicitly disabled
|
|
if globalReq == valid.PoliciesPassedCommandReq && r.PolicyCheck != nil && !*r.PolicyCheck {
|
|
continue
|
|
}
|
|
mergedPlanReqs = append(mergedPlanReqs, globalReq)
|
|
}
|
|
OuterGlobalApplyReqs:
|
|
for _, globalReq := range globalApplyReqs {
|
|
for _, currReq := range r.ApplyRequirements {
|
|
if globalReq == currReq {
|
|
continue OuterGlobalApplyReqs
|
|
}
|
|
}
|
|
|
|
// dont add policy_check step if repo have it explicitly disabled
|
|
if globalReq == valid.PoliciesPassedCommandReq && r.PolicyCheck != nil && !*r.PolicyCheck {
|
|
continue
|
|
}
|
|
mergedApplyReqs = append(mergedApplyReqs, globalReq)
|
|
}
|
|
OuterGlobalImportReqs:
|
|
for _, globalReq := range globalImportReqs {
|
|
for _, currReq := range r.ImportRequirements {
|
|
if globalReq == currReq {
|
|
continue OuterGlobalImportReqs
|
|
}
|
|
}
|
|
|
|
// dont add policy_check step if repo have it explicitly disabled
|
|
if globalReq == valid.PoliciesPassedCommandReq && r.PolicyCheck != nil && !*r.PolicyCheck {
|
|
continue
|
|
}
|
|
mergedImportReqs = append(mergedImportReqs, globalReq)
|
|
}
|
|
|
|
var autoDiscover *valid.AutoDiscover
|
|
if r.AutoDiscover != nil {
|
|
autoDiscover = r.AutoDiscover.ToValid()
|
|
}
|
|
|
|
var repoLocks *valid.RepoLocks
|
|
if r.RepoLocks != nil {
|
|
repoLocks = r.RepoLocks.ToValid()
|
|
}
|
|
|
|
return valid.Repo{
|
|
ID: id,
|
|
IDRegex: idRegex,
|
|
BranchRegex: branchRegex,
|
|
RepoConfigFile: r.RepoConfigFile,
|
|
PlanRequirements: mergedPlanReqs,
|
|
ApplyRequirements: mergedApplyReqs,
|
|
ImportRequirements: mergedImportReqs,
|
|
PreWorkflowHooks: preWorkflowHooks,
|
|
Workflow: workflow,
|
|
PostWorkflowHooks: postWorkflowHooks,
|
|
AllowedWorkflows: r.AllowedWorkflows,
|
|
AllowedOverrides: r.AllowedOverrides,
|
|
AllowCustomWorkflows: r.AllowCustomWorkflows,
|
|
DeleteSourceBranchOnMerge: r.DeleteSourceBranchOnMerge,
|
|
RepoLocking: r.RepoLocking,
|
|
RepoLocks: repoLocks,
|
|
PolicyCheck: r.PolicyCheck,
|
|
CustomPolicyCheck: r.CustomPolicyCheck,
|
|
AutoDiscover: autoDiscover,
|
|
SilencePRComments: r.SilencePRComments,
|
|
}
|
|
}
|