1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00

feat(terraformExecute): pass tf outputs to cpe (#3241)

* feat(terraformExecute): pass tf outputs to cpe

* cleanup

Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
This commit is contained in:
Christian Volk 2021-11-04 10:28:41 +01:00 committed by GitHub
parent 31cd2df1bd
commit 90d5ab7ca2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 263 additions and 17 deletions

View File

@ -1,11 +1,13 @@
package cmd
import (
"bytes"
"fmt"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/SAP/jenkins-library/pkg/terraform"
)
type terraformExecuteUtils interface {
@ -30,16 +32,16 @@ func newTerraformExecuteUtils() terraformExecuteUtils {
return &utils
}
func terraformExecute(config terraformExecuteOptions, telemetryData *telemetry.CustomData) {
func terraformExecute(config terraformExecuteOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *terraformExecuteCommonPipelineEnvironment) {
utils := newTerraformExecuteUtils()
err := runTerraformExecute(&config, telemetryData, utils)
err := runTerraformExecute(&config, telemetryData, utils, commonPipelineEnvironment)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func runTerraformExecute(config *terraformExecuteOptions, telemetryData *telemetry.CustomData, utils terraformExecuteUtils) error {
func runTerraformExecute(config *terraformExecuteOptions, telemetryData *telemetry.CustomData, utils terraformExecuteUtils, commonPipelineEnvironment *terraformExecuteCommonPipelineEnvironment) error {
if len(config.CliConfigFile) > 0 {
utils.AppendEnv([]string{fmt.Sprintf("TF_CLI_CONFIG_FILE=%s", config.CliConfigFile)})
}
@ -66,21 +68,38 @@ func runTerraformExecute(config *terraformExecuteOptions, telemetryData *telemet
}
}
return runTerraform(utils, config.Command, args, config.GlobalOptions)
}
err := runTerraform(utils, config.Command, args, config.GlobalOptions)
func runTerraform(utils terraformExecuteUtils, command string, args []string, globalOptions []string) error {
cliArgs := []string{}
if globalOptions != nil {
cliArgs = append(cliArgs, globalOptions...)
if err != nil {
return err
}
cliArgs = append(cliArgs, command)
var outputBuffer bytes.Buffer
utils.Stdout(&outputBuffer)
if args != nil {
cliArgs = append(cliArgs, args...)
err = runTerraform(utils, "output", []string{"-json"}, config.GlobalOptions)
if err != nil {
return err
}
return utils.RunExecutable("terraform", cliArgs...)
commonPipelineEnvironment.custom.terraformOutputs, err = terraform.ReadOutputs(outputBuffer.String())
return err
}
func runTerraform(utils terraformExecuteUtils, command string, additionalArgs []string, globalOptions []string) error {
args := []string{}
if len(globalOptions) > 0 {
args = append(args, globalOptions...)
}
args = append(args, command)
if len(additionalArgs) > 0 {
args = append(args, additionalArgs...)
}
return utils.RunExecutable("terraform", args...)
}

View File

@ -5,10 +5,12 @@ package cmd
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperenv"
"github.com/SAP/jenkins-library/pkg/splunk"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/SAP/jenkins-library/pkg/validation"
@ -24,6 +26,34 @@ type terraformExecuteOptions struct {
CliConfigFile string `json:"cliConfigFile,omitempty"`
}
type terraformExecuteCommonPipelineEnvironment struct {
custom struct {
terraformOutputs map[string]interface{}
}
}
func (p *terraformExecuteCommonPipelineEnvironment) persist(path, resourceName string) {
content := []struct {
category string
name string
value interface{}
}{
{category: "custom", name: "terraformOutputs", value: p.custom.terraformOutputs},
}
errCount := 0
for _, param := range content {
err := piperenv.SetResourceParameter(path, resourceName, filepath.Join(param.category, param.name), param.value)
if err != nil {
log.Entry().WithError(err).Error("Error persisting piper environment.")
errCount++
}
}
if errCount > 0 {
log.Entry().Fatal("failed to persist Piper environment")
}
}
// TerraformExecuteCommand Executes Terraform
func TerraformExecuteCommand() *cobra.Command {
const STEP_NAME = "terraformExecute"
@ -31,6 +61,7 @@ func TerraformExecuteCommand() *cobra.Command {
metadata := terraformExecuteMetadata()
var stepConfig terraformExecuteOptions
var startTime time.Time
var commonPipelineEnvironment terraformExecuteCommonPipelineEnvironment
var logCollector *log.CollectorHook
var createTerraformExecuteCmd = &cobra.Command{
@ -81,6 +112,7 @@ func TerraformExecuteCommand() *cobra.Command {
telemetryData.ErrorCode = "1"
handler := func() {
config.RemoveVaultSecretFiles()
commonPipelineEnvironment.persist(GeneralConfig.EnvRootPath, "commonPipelineEnvironment")
telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
telemetryData.ErrorCategory = log.GetErrorCategory().String()
telemetry.Send(&telemetryData)
@ -98,7 +130,7 @@ func TerraformExecuteCommand() *cobra.Command {
GeneralConfig.HookConfig.SplunkConfig.Index,
GeneralConfig.HookConfig.SplunkConfig.SendLogs)
}
terraformExecute(stepConfig, &telemetryData)
terraformExecute(stepConfig, &telemetryData, &commonPipelineEnvironment)
telemetryData.ErrorCode = "0"
log.Entry().Info("SUCCESS")
},
@ -208,6 +240,17 @@ func terraformExecuteMetadata() config.StepData {
Containers: []config.Container{
{Name: "terraform", Image: "hashicorp/terraform:0.14.7", EnvVars: []config.EnvVar{{Name: "TF_IN_AUTOMATION", Value: "piper"}}, Options: []config.Option{{Name: "--entrypoint", Value: ""}}},
},
Outputs: config.StepOutputs{
Resources: []config.StepResources{
{
Name: "commonPipelineEnvironment",
Type: "piperEnvironment",
Parameters: []map[string]interface{}{
{"Name": "custom/terraformOutputs"},
},
},
},
},
},
}
return theMetaData

View File

@ -94,15 +94,19 @@ func TestRunTerraformExecute(t *testing.T) {
}
for i, test := range tt {
t.Run(fmt.Sprintf("That arguemtns are correct %d", i), func(t *testing.T) {
t.Run(fmt.Sprintf("That arguments are correct %d", i), func(t *testing.T) {
t.Parallel()
// init
config := test.terraformExecuteOptions
utils := newTerraformExecuteTestsUtils()
utils.StdoutReturn = map[string]string{}
utils.StdoutReturn["terraform output -json"] = "{}"
utils.StdoutReturn["terraform -chgdir=src output -json"] = "{}"
runner := utils.ExecMockRunner
// test
err := runTerraformExecute(&config, nil, utils)
err := runTerraformExecute(&config, nil, utils, &terraformExecuteCommonPipelineEnvironment{})
// assert
assert.NoError(t, err)
@ -117,4 +121,32 @@ func TestRunTerraformExecute(t *testing.T) {
assert.Subset(t, runner.Env, test.expectedEnvVars)
})
}
t.Run("Outputs get injected into CPE", func(t *testing.T) {
t.Parallel()
cpe := terraformExecuteCommonPipelineEnvironment{}
config := terraformExecuteOptions{
Command: "plan",
}
utils := newTerraformExecuteTestsUtils()
utils.StdoutReturn = map[string]string{}
utils.StdoutReturn["terraform output -json"] = `{
"sample_var": {
"sensitive": true,
"value": "a secret value",
"type": "string"
}
}
`
// test
err := runTerraformExecute(&config, nil, utils, &cpe)
// assert
assert.NoError(t, err)
assert.Equal(t, 1, len(cpe.custom.terraformOutputs))
assert.Equal(t, "a secret value", cpe.custom.terraformOutputs["sample_var"])
})
}

View File

@ -0,0 +1,28 @@
package terraform
import (
"encoding/json"
)
type TerraformOutput struct {
Sensitive bool `json:"sensitive"`
ObjType interface{} `json:"type"`
Value interface{} `json:"value"`
}
func ReadOutputs(tfOutputJson string) (map[string]interface{}, error) {
var objmap map[string]TerraformOutput
err := json.Unmarshal([]byte(tfOutputJson), &objmap)
if err != nil {
return nil, err
}
retmap := make(map[string]interface{})
for tfoutvarname, tfoutvar := range objmap {
retmap[tfoutvarname] = tfoutvar.Value
}
return retmap, nil
}

View File

@ -0,0 +1,117 @@
package terraform
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestReadOutputs(t *testing.T) {
terraformOutputsJson := `{
"boolean": {
"sensitive": false,
"type": "bool",
"value": true
},
"list_any": {
"sensitive": false,
"type": [
"tuple",
[
"bool",
"string",
"number",
[
"tuple",
[]
]
]
],
"value": [
true,
"2",
3,
[]
]
},
"list_numbers": {
"sensitive": false,
"type": [
"tuple",
[
"number",
"number",
"number"
]
],
"value": [
1,
2,
3
]
},
"list_string": {
"sensitive": false,
"type": [
"tuple",
[
"string",
"string",
"string"
]
],
"value": [
"1",
"2",
"3"
]
},
"map": {
"sensitive": false,
"type": [
"object",
{
"ATTR1": "string",
"ATTR2": [
"object",
{
"ATTR3": [
"tuple",
[]
]
}
]
}
],
"value": {
"ATTR1": "",
"ATTR2": {
"ATTR3": []
}
}
},
"secret": {
"sensitive": true,
"type": "string",
"value": "this-could-be-a-password"
},
"string": {
"sensitive": false,
"type": "string",
"value": "string"
},
"number": {
"sensitive": false,
"type": "number",
"value": 1
}
}
`
outputs, err := ReadOutputs(terraformOutputsJson)
assert.NoError(t, err)
assert.Equal(t, 8, len(outputs))
assert.Equal(t, true, outputs["boolean"])
assert.Equal(t, "string", outputs["string"])
assert.Equal(t, float64(1), outputs["number"])
}

View File

@ -70,3 +70,10 @@ spec:
env:
- name: TF_IN_AUTOMATION
value: piper
outputs:
resources:
- name: commonPipelineEnvironment
type: piperEnvironment
params:
- name: custom/terraformOutputs
type: 'map[string]interface{}'