Skip to content

Commit

Permalink
feat: MultiEnv step added (runatlantis#1793)
Browse files Browse the repository at this point in the history
* MultiEnv step added

* Values removed from the output

* Fixing tests

* multienv_step_runner_test added

* Documentation of new feature added

* Documentatation of new feature  modified

* multienv_step test file extension changed

* Fixed multinev_step_runner test

* Enhanced debug logging in multienv_step_runner

* Enhanced errorhandling

* Test command modified

* Test command modified

* Test command modified

* Test command modified

* Errorhandling modified

* Test command modified

* Test command modified

* Create empty map in test

* Fixing test ExpOut

* Testing, refactoring, eliminating extra debug log

* MultiEnv result modified from json to plain string

* MultiEnv step Run parameter updated

* Import added

* project_command_runner updated

* project_command_runner updated

* multienv_step_runner_test updated

* multienv doc modified

* Update runatlantis.io/docs/custom-workflows.md

Co-authored-by: PePe Amengual <[email protected]>

Co-authored-by: Istvan Tapaszto <[email protected]>
Co-authored-by: PePe Amengual <[email protected]>
  • Loading branch information
3 people authored and krrrr38 committed Dec 16, 2022
1 parent 358c7dc commit e010803
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 3 deletions.
21 changes: 21 additions & 0 deletions runatlantis.io/docs/custom-workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,24 @@ as the environment variable value.
* `env` `command`'s can use any of the built-in environment variables available
to `run` commands.
:::

#### Multiple Environment Variables `multienv` Command
The `multienv` command allows you to set dynamic number of multiple environment variables that will be available
to all steps defined **below** the `multienv` step.
```yaml
- multienv: custom-command
```
| Key | Type | Default | Required | Description |
|----------|--------|---------|----------|-----------------------------------------------|
| multienv | string | none | no | Run a custom command and add set |
| | | | | environment variables according to the result |
The result of the executed command must have a fixed format:
EnvVar1Name=value1,EnvVar2Name=value2,EnvVar3Name=value3

The name-value pairs in the result are added as environment variables if success is true otherwise the workflow execution stops with error and the errorMessage is getting displayed.

::: tip Notes
* `multienv` `command`'s can use any of the built-in environment variables available
to `run` commands.
:::

8 changes: 5 additions & 3 deletions server/core/config/raw/step.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
ApplyStepName = "apply"
InitStepName = "init"
EnvStepName = "env"
MultiEnvStepName = "multienv"
)

// Step represents a single action/command to perform. In YAML, it can be set as
Expand Down Expand Up @@ -81,6 +82,7 @@ func (s Step) validStepName(stepName string) bool {
stepName == PlanStepName ||
stepName == ApplyStepName ||
stepName == EnvStepName ||
stepName == MultiEnvStepName ||
stepName == ShowStepName ||
stepName == PolicyCheckStepName
}
Expand Down Expand Up @@ -191,7 +193,7 @@ func (s Step) Validate() error {
len(keys), strings.Join(keys, ","))
}
for stepName := range elem {
if stepName != RunStepName {
if stepName != RunStepName && stepName != MultiEnvStepName {
return fmt.Errorf("%q is not a valid step type", stepName)
}
}
Expand Down Expand Up @@ -251,9 +253,9 @@ func (s Step) ToValid() valid.Step {
if len(s.StringVal) > 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 _, v := range s.StringVal {
for stepName, v := range s.StringVal {
return valid.Step{
StepName: RunStepName,
StepName: stepName,
RunCommand: v,
}
}
Expand Down
39 changes: 39 additions & 0 deletions server/core/runtime/multienv_step_runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package runtime

import (
"fmt"
"strings"

"github.com/runatlantis/atlantis/server/events/command"
)

// EnvStepRunner set environment variables.
type MultiEnvStepRunner struct {
RunStepRunner *RunStepRunner
}

// 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)
if err == nil {
envVars := strings.Split(res, ",")
if len(envVars) > 0 {
var sb strings.Builder
sb.WriteString("Dynamic environment variables added:\n")
for _, item := range envVars {
nameValue := strings.Split(item, "=")
if len(nameValue) == 2 {
envs[nameValue[0]] = nameValue[1]
sb.WriteString(nameValue[0])
sb.WriteString("\n")
} else {
return "", fmt.Errorf("Invalid environment variable definition: %s", item)
}
}
return sb.String(), nil
}
return "No dynamic environment variable added", nil
}
return "", err
}
79 changes: 79 additions & 0 deletions server/core/runtime/multienv_step_runner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package runtime_test

import (
"testing"

version "github.com/hashicorp/go-version"
. "github.com/petergtz/pegomock"
"github.com/runatlantis/atlantis/server/core/runtime"
"github.com/runatlantis/atlantis/server/core/terraform/mocks"
"github.com/runatlantis/atlantis/server/events/command"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/logging"
. "github.com/runatlantis/atlantis/testing"
)

func TestMultiEnvStepRunner_Run(t *testing.T) {
cases := []struct {
Command string
ProjectName string
ExpOut string
ExpErr string
Version string
}{
{
Command: `echo 'TF_VAR_REPODEFINEDVARIABLE_ONE=value1'`,
ExpOut: "Dynamic environment variables added:\nTF_VAR_REPODEFINEDVARIABLE_ONE\n",
Version: "v1.2.3",
},
}
RegisterMockTestingT(t)
tfClient := mocks.NewMockClient()
tfVersion, err := version.NewVersion("0.12.0")
Ok(t, err)
runStepRunner := runtime.RunStepRunner{
TerraformExecutor: tfClient,
DefaultTFVersion: tfVersion,
}
multiEnvStepRunner := runtime.MultiEnvStepRunner{
RunStepRunner: &runStepRunner,
}
for _, c := range cases {
t.Run(c.Command, func(t *testing.T) {
tmpDir, cleanup := TempDir(t)
defer cleanup()
ctx := command.ProjectContext{
BaseRepo: models.Repo{
Name: "basename",
Owner: "baseowner",
},
HeadRepo: models.Repo{
Name: "headname",
Owner: "headowner",
},
Pull: models.PullRequest{
Num: 2,
HeadBranch: "add-feat",
BaseBranch: "master",
Author: "acme",
},
User: models.User{
Username: "acme-user",
},
Log: logging.NewNoopLogger(t),
Workspace: "myworkspace",
RepoRelDir: "mydir",
TerraformVersion: tfVersion,
ProjectName: c.ProjectName,
}
envMap := make(map[string]string)
value, err := multiEnvStepRunner.Run(ctx, c.Command, tmpDir, envMap)
if c.ExpErr != "" {
ErrContains(t, c.ExpErr, err)
return
}
Ok(t, err)
Equals(t, c.ExpOut, value)
})
}
}
9 changes: 9 additions & 0 deletions server/events/project_command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ type EnvStepRunner interface {
Run(ctx command.ProjectContext, cmd string, value string, path string, envs map[string]string) (string, error)
}

// 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)
}

//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_webhooks_sender.go WebhooksSender

// WebhooksSender sends webhook.
Expand Down Expand Up @@ -189,6 +195,7 @@ type DefaultProjectCommandRunner struct {
VersionStepRunner StepRunner
RunStepRunner CustomStepRunner
EnvStepRunner EnvStepRunner
MultiEnvStepRunner MultiEnvStepRunner
PullApprovedChecker runtime.PullApprovedChecker
WorkingDir WorkingDir
Webhooks WebhooksSender
Expand Down Expand Up @@ -493,6 +500,8 @@ func (p *DefaultProjectCommandRunner) runSteps(steps []valid.Step, ctx command.P
// We reset out to the empty string because we don't want it to
// 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)
}

if out != "" {
Expand Down
3 changes: 3 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,9 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
EnvStepRunner: &runtime.EnvStepRunner{
RunStepRunner: runStepRunner,
},
MultiEnvStepRunner: &runtime.MultiEnvStepRunner{
RunStepRunner: runStepRunner,
},
VersionStepRunner: &runtime.VersionStepRunner{
TerraformExecutor: terraformClient,
DefaultTFVersion: defaultTfVersion,
Expand Down

0 comments on commit e010803

Please sign in to comment.