mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-07-28 22:28:25 +00:00
feat: optionally hide multienv output (#4422)
Co-authored-by: PePe Amengual <jose.amengual@gmail.com> Co-authored-by: Rui Chen <rui@chenrui.dev>
This commit is contained in:
@@ -40,6 +40,9 @@ const (
|
||||
// name: test
|
||||
// command: echo 312
|
||||
// value: value
|
||||
// - multienv:
|
||||
// command: envs.sh
|
||||
// outpiut: hide
|
||||
// - run:
|
||||
// command: my custom command
|
||||
// output: hide
|
||||
@@ -57,8 +60,8 @@ type Step struct {
|
||||
// Key will be set in case #1 and #3 above to the key. In case #2, there
|
||||
// could be multiple keys (since the element is a map) so we don't set Key.
|
||||
Key *string
|
||||
// EnvOrRun will be set in case #2 above.
|
||||
EnvOrRun map[string]map[string]string
|
||||
// CommandMap will be set in case #2 above.
|
||||
CommandMap map[string]map[string]string
|
||||
// Map will be set in case #3 above.
|
||||
Map map[string]map[string][]string
|
||||
// StringVal will be set in case #4 above.
|
||||
@@ -146,7 +149,7 @@ func (s Step) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
envOrRunStep := func(value interface{}) error {
|
||||
envOrRunOrMultiEnvStep := func(value interface{}) error {
|
||||
elem := value.(map[string]map[string]string)
|
||||
var keys []string
|
||||
for k := range elem {
|
||||
@@ -192,19 +195,21 @@ func (s Step) Validate() error {
|
||||
return fmt.Errorf("env steps only support one of the %q or %q keys, found both",
|
||||
ValueArgKey, CommandArgKey)
|
||||
}
|
||||
case RunStepName:
|
||||
case RunStepName, MultiEnvStepName:
|
||||
argsCopy := make(map[string]string)
|
||||
for k, v := range args {
|
||||
argsCopy[k] = v
|
||||
}
|
||||
args = argsCopy
|
||||
if _, ok := args[CommandArgKey]; !ok {
|
||||
return fmt.Errorf("run step must have a %q key set", CommandArgKey)
|
||||
return fmt.Errorf("%q step must have a %q key set", stepName, CommandArgKey)
|
||||
}
|
||||
delete(args, CommandArgKey)
|
||||
if v, ok := args[OutputArgKey]; ok {
|
||||
if !(v == valid.PostProcessRunOutputShow || v == valid.PostProcessRunOutputHide || v == valid.PostProcessRunOutputStripRefreshing) {
|
||||
if stepName == RunStepName && !(v == valid.PostProcessRunOutputShow || v == valid.PostProcessRunOutputHide || v == valid.PostProcessRunOutputStripRefreshing) {
|
||||
return fmt.Errorf("run step %q option must be one of %q, %q, or %q", OutputArgKey, valid.PostProcessRunOutputShow, valid.PostProcessRunOutputHide, valid.PostProcessRunOutputStripRefreshing)
|
||||
} else if stepName == MultiEnvStepName && !(v == valid.PostProcessRunOutputShow || v == valid.PostProcessRunOutputHide) {
|
||||
return fmt.Errorf("multienv step %q option must be %q or %q", OutputArgKey, valid.PostProcessRunOutputShow, valid.PostProcessRunOutputHide)
|
||||
}
|
||||
}
|
||||
delete(args, OutputArgKey)
|
||||
@@ -215,7 +220,7 @@ func (s Step) Validate() error {
|
||||
}
|
||||
// Sort so tests can be deterministic.
|
||||
sort.Strings(argKeys)
|
||||
return fmt.Errorf("run steps only support keys %q, %q and %q, found extra keys %q", RunStepName, CommandArgKey, OutputArgKey, strings.Join(argKeys, ","))
|
||||
return fmt.Errorf("%q steps only support keys %q and %q, found extra keys %q", stepName, CommandArgKey, OutputArgKey, strings.Join(argKeys, ","))
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%q is not a valid step type", stepName)
|
||||
@@ -224,7 +229,7 @@ func (s Step) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
runStep := func(value interface{}) error {
|
||||
runOrMultiEnvStep := func(value interface{}) error {
|
||||
elem := value.(map[string]string)
|
||||
var keys []string
|
||||
for k := range elem {
|
||||
@@ -238,7 +243,7 @@ func (s Step) Validate() error {
|
||||
len(keys), strings.Join(keys, ","))
|
||||
}
|
||||
for stepName := range elem {
|
||||
if stepName != RunStepName && stepName != MultiEnvStepName {
|
||||
if !(stepName == RunStepName || stepName == MultiEnvStepName) {
|
||||
return fmt.Errorf("%q is not a valid step type", stepName)
|
||||
}
|
||||
}
|
||||
@@ -251,11 +256,11 @@ func (s Step) Validate() error {
|
||||
if len(s.Map) > 0 {
|
||||
return validation.Validate(s.Map, validation.By(extraArgs))
|
||||
}
|
||||
if len(s.EnvOrRun) > 0 {
|
||||
return validation.Validate(s.EnvOrRun, validation.By(envOrRunStep))
|
||||
if len(s.CommandMap) > 0 {
|
||||
return validation.Validate(s.CommandMap, validation.By(envOrRunOrMultiEnvStep))
|
||||
}
|
||||
if len(s.StringVal) > 0 {
|
||||
return validation.Validate(s.StringVal, validation.By(runStep))
|
||||
return validation.Validate(s.StringVal, validation.By(runOrMultiEnvStep))
|
||||
}
|
||||
return errors.New("step element is empty")
|
||||
}
|
||||
@@ -269,10 +274,10 @@ func (s Step) ToValid() valid.Step {
|
||||
}
|
||||
|
||||
// This will trigger in case #2 (see Step docs).
|
||||
if len(s.EnvOrRun) > 0 {
|
||||
if len(s.CommandMap) > 0 {
|
||||
// After validation we assume there's only one key and it's a valid
|
||||
// step name so we just use the first one.
|
||||
for stepName, stepArgs := range s.EnvOrRun {
|
||||
for stepName, stepArgs := range s.CommandMap {
|
||||
step := valid.Step{
|
||||
StepName: stepName,
|
||||
EnvVarName: stepArgs[NameArgKey],
|
||||
@@ -356,7 +361,7 @@ func (s *Step) unmarshalGeneric(unmarshal func(interface{}) error) error {
|
||||
var envStep map[string]map[string]string
|
||||
err = unmarshal(&envStep)
|
||||
if err == nil {
|
||||
s.EnvOrRun = envStep
|
||||
s.CommandMap = envStep
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -379,8 +384,8 @@ func (s Step) marshalGeneric() (interface{}, error) {
|
||||
return s.StringVal, nil
|
||||
} else if len(s.Map) != 0 {
|
||||
return s.Map, nil
|
||||
} else if len(s.EnvOrRun) != 0 {
|
||||
return s.EnvOrRun, nil
|
||||
} else if len(s.CommandMap) != 0 {
|
||||
return s.CommandMap, nil
|
||||
} else if s.Key != nil {
|
||||
return s.Key, nil
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ env:
|
||||
value: direct_value
|
||||
name: test`,
|
||||
exp: raw.Step{
|
||||
EnvOrRun: EnvOrRunType{
|
||||
CommandMap: EnvType{
|
||||
"env": {
|
||||
"value": "direct_value",
|
||||
"name": "test",
|
||||
@@ -96,7 +96,7 @@ env:
|
||||
command: echo 123
|
||||
name: test`,
|
||||
exp: raw.Step{
|
||||
EnvOrRun: EnvOrRunType{
|
||||
CommandMap: EnvType{
|
||||
"env": {
|
||||
"command": "echo 123",
|
||||
"name": "test",
|
||||
@@ -134,10 +134,10 @@ key: value`,
|
||||
description: "empty",
|
||||
input: "",
|
||||
exp: raw.Step{
|
||||
Key: nil,
|
||||
Map: nil,
|
||||
StringVal: nil,
|
||||
EnvOrRun: nil,
|
||||
Key: nil,
|
||||
Map: nil,
|
||||
StringVal: nil,
|
||||
CommandMap: nil,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -227,7 +227,7 @@ func TestStep_Validate(t *testing.T) {
|
||||
{
|
||||
description: "env",
|
||||
input: raw.Step{
|
||||
EnvOrRun: EnvOrRunType{
|
||||
CommandMap: EnvType{
|
||||
"env": {
|
||||
"name": "test",
|
||||
"command": "echo 123",
|
||||
@@ -283,7 +283,7 @@ func TestStep_Validate(t *testing.T) {
|
||||
{
|
||||
description: "multiple keys in env",
|
||||
input: raw.Step{
|
||||
EnvOrRun: EnvOrRunType{
|
||||
CommandMap: EnvType{
|
||||
"key1": nil,
|
||||
"key2": nil,
|
||||
},
|
||||
@@ -312,7 +312,7 @@ func TestStep_Validate(t *testing.T) {
|
||||
{
|
||||
description: "invalid key in env",
|
||||
input: raw.Step{
|
||||
EnvOrRun: EnvOrRunType{
|
||||
CommandMap: EnvType{
|
||||
"invalid": nil,
|
||||
},
|
||||
},
|
||||
@@ -353,7 +353,7 @@ func TestStep_Validate(t *testing.T) {
|
||||
{
|
||||
description: "env step with no name key set",
|
||||
input: raw.Step{
|
||||
EnvOrRun: EnvOrRunType{
|
||||
CommandMap: EnvType{
|
||||
"env": {
|
||||
"value": "value",
|
||||
},
|
||||
@@ -364,7 +364,7 @@ func TestStep_Validate(t *testing.T) {
|
||||
{
|
||||
description: "env step with invalid key",
|
||||
input: raw.Step{
|
||||
EnvOrRun: EnvOrRunType{
|
||||
CommandMap: EnvType{
|
||||
"env": {
|
||||
"abc": "",
|
||||
"invalid2": "",
|
||||
@@ -376,7 +376,7 @@ func TestStep_Validate(t *testing.T) {
|
||||
{
|
||||
description: "env step with both command and value set",
|
||||
input: raw.Step{
|
||||
EnvOrRun: EnvOrRunType{
|
||||
CommandMap: EnvType{
|
||||
"env": {
|
||||
"name": "name",
|
||||
"command": "command",
|
||||
@@ -454,7 +454,7 @@ func TestStep_ToValid(t *testing.T) {
|
||||
{
|
||||
description: "env step",
|
||||
input: raw.Step{
|
||||
EnvOrRun: EnvOrRunType{
|
||||
CommandMap: EnvType{
|
||||
"env": {
|
||||
"name": "test",
|
||||
"command": "echo 123",
|
||||
@@ -561,7 +561,7 @@ func TestStep_ToValid(t *testing.T) {
|
||||
{
|
||||
description: "run step with output",
|
||||
input: raw.Step{
|
||||
EnvOrRun: EnvOrRunType{
|
||||
CommandMap: RunType{
|
||||
"run": {
|
||||
"command": "my 'run command'",
|
||||
"output": "hide",
|
||||
@@ -574,6 +574,34 @@ func TestStep_ToValid(t *testing.T) {
|
||||
Output: "hide",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "multienv step",
|
||||
input: raw.Step{
|
||||
StringVal: map[string]string{
|
||||
"multienv": "envs.sh",
|
||||
},
|
||||
},
|
||||
exp: valid.Step{
|
||||
StepName: "multienv",
|
||||
RunCommand: "envs.sh",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "multienv step with output",
|
||||
input: raw.Step{
|
||||
CommandMap: MultiEnvType{
|
||||
"multienv": {
|
||||
"command": "envs.sh",
|
||||
"output": "hide",
|
||||
},
|
||||
},
|
||||
},
|
||||
exp: valid.Step{
|
||||
StepName: "multienv",
|
||||
RunCommand: "envs.sh",
|
||||
Output: "hide",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.description, func(t *testing.T) {
|
||||
@@ -583,4 +611,6 @@ func TestStep_ToValid(t *testing.T) {
|
||||
}
|
||||
|
||||
type MapType map[string]map[string][]string
|
||||
type EnvOrRunType map[string]map[string]string
|
||||
type EnvType map[string]map[string]string
|
||||
type RunType map[string]map[string]string
|
||||
type MultiEnvType map[string]map[string]string
|
||||
|
||||
@@ -16,32 +16,39 @@ type MultiEnvStepRunner struct {
|
||||
|
||||
// Run runs the multienv step command.
|
||||
// The command must return a json string containing the array of name-value pairs that are being added as extra environment variables
|
||||
func (r *MultiEnvStepRunner) Run(ctx command.ProjectContext, command string, path string, envs map[string]string) (string, error) {
|
||||
res, err := r.RunStepRunner.Run(ctx, command, path, envs, false, valid.PostProcessRunOutputShow)
|
||||
func (r *MultiEnvStepRunner) Run(ctx command.ProjectContext, command string, path string, envs map[string]string, postProcessOutput valid.PostProcessRunOutputOption) (string, error) {
|
||||
res, err := r.RunStepRunner.Run(ctx, command, path, envs, false, postProcessOutput)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(res) == 0 {
|
||||
return "No dynamic environment variable added", nil
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString("Dynamic environment variables added:\n")
|
||||
if len(res) == 0 {
|
||||
sb.WriteString("No dynamic environment variable added")
|
||||
} else {
|
||||
sb.WriteString("Dynamic environment variables added:\n")
|
||||
|
||||
vars, err := parseMultienvLine(res)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Invalid environment variable definition: %s (%w)", res, err)
|
||||
vars, err := parseMultienvLine(res)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Invalid environment variable definition: %s (%w)", res, err)
|
||||
}
|
||||
|
||||
for i := 0; i < len(vars); i += 2 {
|
||||
key := vars[i]
|
||||
envs[key] = vars[i+1]
|
||||
sb.WriteString(key)
|
||||
sb.WriteRune('\n')
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(vars); i += 2 {
|
||||
key := vars[i]
|
||||
envs[key] = vars[i+1]
|
||||
sb.WriteString(key)
|
||||
sb.WriteRune('\n')
|
||||
switch postProcessOutput {
|
||||
case valid.PostProcessRunOutputHide:
|
||||
return "", nil
|
||||
case valid.PostProcessRunOutputShow:
|
||||
return sb.String(), nil
|
||||
default:
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func parseMultienvLine(in string) ([]string, error) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
. "github.com/petergtz/pegomock/v4"
|
||||
"github.com/runatlantis/atlantis/server/core/config/valid"
|
||||
"github.com/runatlantis/atlantis/server/core/runtime"
|
||||
"github.com/runatlantis/atlantis/server/core/terraform/mocks"
|
||||
"github.com/runatlantis/atlantis/server/events/command"
|
||||
@@ -84,7 +85,7 @@ func TestMultiEnvStepRunner_Run(t *testing.T) {
|
||||
ProjectName: c.ProjectName,
|
||||
}
|
||||
envMap := make(map[string]string)
|
||||
value, err := multiEnvStepRunner.Run(ctx, c.Command, tmpDir, envMap)
|
||||
value, err := multiEnvStepRunner.Run(ctx, c.Command, tmpDir, envMap, valid.PostProcessRunOutputShow)
|
||||
if c.ExpErr != "" {
|
||||
ErrContains(t, c.ExpErr, err)
|
||||
return
|
||||
|
||||
@@ -78,7 +78,7 @@ type EnvStepRunner interface {
|
||||
// MultiEnvStepRunner runs multienv steps.
|
||||
type MultiEnvStepRunner interface {
|
||||
// Run cmd in path.
|
||||
Run(ctx command.ProjectContext, cmd string, path string, envs map[string]string) (string, error)
|
||||
Run(ctx command.ProjectContext, cmd string, path string, envs map[string]string, postProcessOutput valid.PostProcessRunOutputOption) (string, error)
|
||||
}
|
||||
|
||||
//go:generate pegomock generate --package mocks -o mocks/mock_webhooks_sender.go WebhooksSender
|
||||
@@ -795,7 +795,7 @@ func (p *DefaultProjectCommandRunner) runSteps(steps []valid.Step, ctx command.P
|
||||
// be printed to the PR, it's solely to set the environment variable.
|
||||
out = ""
|
||||
case "multienv":
|
||||
out, err = p.MultiEnvStepRunner.Run(ctx, step.RunCommand, absPath, envs)
|
||||
out, err = p.MultiEnvStepRunner.Run(ctx, step.RunCommand, absPath, envs, step.Output)
|
||||
}
|
||||
|
||||
if out != "" {
|
||||
|
||||
Reference in New Issue
Block a user