tfvars-atlantis-config/repocfg/workflow.go
2024-02-26 12:44:50 +00:00

176 lines
4.2 KiB
Go

package repocfg
import (
"errors"
"fmt"
"path/filepath"
"github.com/runatlantis/atlantis/server/core/config/raw"
)
// ExtRawWorkflow extends the raw.Workflow type to add additional methods
// and fields
type ExtRawWorkflow struct {
Name string
Args *ExtraArgs
Workspace string
raw.Workflow
}
// ExtraArgs is a type aliased map of extra arguments to be passed within a workflow
type ExtraArgs map[string][]string
// hasArgs returns true if the component has a target (i.e. a Terraform variable file)
func hasArgs(c Component) bool {
return len(c.VarFiles) > 0
}
// Add generates the extra_args for the stage and adds them to the stage's Args
func (args *ExtraArgs) Add(v []string) {
(*args)["extra_args"] = v
}
// relativeVarFile returns the path of the variable file v relative to
// the component's path.
func relativeVarFile(c, v string) (*string, error) {
absComponent, err := filepath.Abs(c)
if err != nil {
return nil, fmt.Errorf("absolute path for component %s: %s", c, err)
}
absVarFile, err := filepath.Abs(v)
if err != nil {
return nil, fmt.Errorf("absolute path for %s: %s", v, err)
}
rel, err := filepath.Rel(absComponent, absVarFile)
if err != nil {
return nil, fmt.Errorf("relative path for %s: %s", v, err)
}
return &rel, nil
}
// WorkflowsFrom creates new Atlantis workflows from a Terraform component
// and calls the stage methods to generate all of its fields.
func WorkflowsFrom(c Component, opts Options) ([]ExtRawWorkflow, error) {
var workflows []ExtRawWorkflow
for _, v := range c.VarFiles {
wf := ExtRawWorkflow{
Name: friendlyName(c.Path, v),
Workflow: raw.Workflow{
Plan: &raw.Stage{},
Apply: &raw.Stage{},
},
Args: &ExtraArgs{},
Workspace: pathWithoutExtension(v),
}
if hasArgs(c) {
rel, err := relativeVarFile(c.Path, v)
if err != nil {
return nil, fmt.Errorf("relative variable file for %s: %s", v, err)
}
wf.Args.Add([]string{"-var-file=" + *rel})
}
if opts.MultiEnv {
err := wf.AddMultiEnv(v)
if err != nil {
return nil, fmt.Errorf("adding multienv for %s: %s", v, err)
}
}
if opts.UseWorkspaces {
wf.AddWorkspace()
}
wf.PlanStage(opts)
wf.ApplyStage(opts)
err := wf.Validate()
if err != nil {
return nil, fmt.Errorf("workflow validation failed for %s: %s", wf.Name, err)
}
workflows = append(workflows, wf)
}
return workflows, nil
}
// PlanStage generates the plan stage for the workflow
func (wf *ExtRawWorkflow) PlanStage(opts Options) {
if wf.Plan == nil {
wf.Plan = new(raw.Stage)
}
init := raw.Step{
Key: ptr("init"),
}
wf.Plan.Steps = append(wf.Plan.Steps, init)
if len(*wf.Args) > 0 {
wf.Plan.Steps = append(wf.Plan.Steps, raw.Step{
Map: map[string]map[string][]string{
"plan": *wf.Args,
},
})
} else {
wf.Plan.Steps = append(wf.Plan.Steps, raw.Step{
Key: ptr("plan"),
})
}
}
// ApplyStage generates the apply stage for the workflow
//
// Unlike the plan stage, the apply stage doesn't need extra args for -var-file,
// as it uses the planfile generated by the plan stage.
func (wf *ExtRawWorkflow) ApplyStage(opts Options) {
if wf.Apply == nil {
wf.Apply = new(raw.Stage)
}
wf.Apply.Steps = append(wf.Apply.Steps, raw.Step{
Key: ptr("apply"),
})
}
// AddWorkspace sets TF_WORKSPACE in the plan and apply stages
// to the value of the workspace from the project configuration.
//
// This is needed because Atlantis will use the default workspace
// which doesn't exist.
func (wf *ExtRawWorkflow) AddWorkspace() {
for _, stg := range []*raw.Stage{wf.Plan, wf.Apply} {
stg.Steps = append(stg.Steps, raw.Step{
EnvOrRun: map[string]map[string]string{
"env": {
"name": "TF_WORKSPACE",
"value": wf.Workspace,
},
},
})
}
}
// AddMultiEnv adds the multienv step to the plan and apply stages
func (wf *ExtRawWorkflow) AddMultiEnv(env string) error {
environ, err := prefixedEnviron(pathWithoutExtension(env))
if errors.Is(err, ErrNoEnvVars) {
return nil
}
if err != nil {
return fmt.Errorf("adding multienv for environment %s: %w", env, err)
}
for _, stg := range []*raw.Stage{wf.Plan, wf.Apply} {
stg.Steps = append(stg.Steps, raw.Step{
StringVal: map[string]string{
"multienv": *environ,
},
})
}
return nil
}