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:
parent
31cd2df1bd
commit
90d5ab7ca2
@ -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...)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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"])
|
||||
})
|
||||
}
|
||||
|
28
pkg/terraform/terraform.go
Normal file
28
pkg/terraform/terraform.go
Normal 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
|
||||
}
|
117
pkg/terraform/terraform_test.go
Normal file
117
pkg/terraform/terraform_test.go
Normal 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"])
|
||||
}
|
@ -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{}'
|
||||
|
Loading…
Reference in New Issue
Block a user