mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-01-04 04:07:16 +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
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/SAP/jenkins-library/pkg/command"
|
"github.com/SAP/jenkins-library/pkg/command"
|
||||||
"github.com/SAP/jenkins-library/pkg/log"
|
"github.com/SAP/jenkins-library/pkg/log"
|
||||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||||
|
"github.com/SAP/jenkins-library/pkg/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
type terraformExecuteUtils interface {
|
type terraformExecuteUtils interface {
|
||||||
@ -30,16 +32,16 @@ func newTerraformExecuteUtils() terraformExecuteUtils {
|
|||||||
return &utils
|
return &utils
|
||||||
}
|
}
|
||||||
|
|
||||||
func terraformExecute(config terraformExecuteOptions, telemetryData *telemetry.CustomData) {
|
func terraformExecute(config terraformExecuteOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *terraformExecuteCommonPipelineEnvironment) {
|
||||||
utils := newTerraformExecuteUtils()
|
utils := newTerraformExecuteUtils()
|
||||||
|
|
||||||
err := runTerraformExecute(&config, telemetryData, utils)
|
err := runTerraformExecute(&config, telemetryData, utils, commonPipelineEnvironment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Entry().WithError(err).Fatal("step execution failed")
|
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 {
|
if len(config.CliConfigFile) > 0 {
|
||||||
utils.AppendEnv([]string{fmt.Sprintf("TF_CLI_CONFIG_FILE=%s", config.CliConfigFile)})
|
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)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTerraform(utils terraformExecuteUtils, command string, args []string, globalOptions []string) error {
|
var outputBuffer bytes.Buffer
|
||||||
cliArgs := []string{}
|
utils.Stdout(&outputBuffer)
|
||||||
|
|
||||||
if globalOptions != nil {
|
err = runTerraform(utils, "output", []string{"-json"}, config.GlobalOptions)
|
||||||
cliArgs = append(cliArgs, globalOptions...)
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cliArgs = append(cliArgs, command)
|
commonPipelineEnvironment.custom.terraformOutputs, err = terraform.ReadOutputs(outputBuffer.String())
|
||||||
|
|
||||||
if args != nil {
|
return err
|
||||||
cliArgs = append(cliArgs, args...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils.RunExecutable("terraform", cliArgs...)
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SAP/jenkins-library/pkg/config"
|
"github.com/SAP/jenkins-library/pkg/config"
|
||||||
"github.com/SAP/jenkins-library/pkg/log"
|
"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/splunk"
|
||||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||||
"github.com/SAP/jenkins-library/pkg/validation"
|
"github.com/SAP/jenkins-library/pkg/validation"
|
||||||
@ -24,6 +26,34 @@ type terraformExecuteOptions struct {
|
|||||||
CliConfigFile string `json:"cliConfigFile,omitempty"`
|
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
|
// TerraformExecuteCommand Executes Terraform
|
||||||
func TerraformExecuteCommand() *cobra.Command {
|
func TerraformExecuteCommand() *cobra.Command {
|
||||||
const STEP_NAME = "terraformExecute"
|
const STEP_NAME = "terraformExecute"
|
||||||
@ -31,6 +61,7 @@ func TerraformExecuteCommand() *cobra.Command {
|
|||||||
metadata := terraformExecuteMetadata()
|
metadata := terraformExecuteMetadata()
|
||||||
var stepConfig terraformExecuteOptions
|
var stepConfig terraformExecuteOptions
|
||||||
var startTime time.Time
|
var startTime time.Time
|
||||||
|
var commonPipelineEnvironment terraformExecuteCommonPipelineEnvironment
|
||||||
var logCollector *log.CollectorHook
|
var logCollector *log.CollectorHook
|
||||||
|
|
||||||
var createTerraformExecuteCmd = &cobra.Command{
|
var createTerraformExecuteCmd = &cobra.Command{
|
||||||
@ -81,6 +112,7 @@ func TerraformExecuteCommand() *cobra.Command {
|
|||||||
telemetryData.ErrorCode = "1"
|
telemetryData.ErrorCode = "1"
|
||||||
handler := func() {
|
handler := func() {
|
||||||
config.RemoveVaultSecretFiles()
|
config.RemoveVaultSecretFiles()
|
||||||
|
commonPipelineEnvironment.persist(GeneralConfig.EnvRootPath, "commonPipelineEnvironment")
|
||||||
telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
|
telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
|
||||||
telemetryData.ErrorCategory = log.GetErrorCategory().String()
|
telemetryData.ErrorCategory = log.GetErrorCategory().String()
|
||||||
telemetry.Send(&telemetryData)
|
telemetry.Send(&telemetryData)
|
||||||
@ -98,7 +130,7 @@ func TerraformExecuteCommand() *cobra.Command {
|
|||||||
GeneralConfig.HookConfig.SplunkConfig.Index,
|
GeneralConfig.HookConfig.SplunkConfig.Index,
|
||||||
GeneralConfig.HookConfig.SplunkConfig.SendLogs)
|
GeneralConfig.HookConfig.SplunkConfig.SendLogs)
|
||||||
}
|
}
|
||||||
terraformExecute(stepConfig, &telemetryData)
|
terraformExecute(stepConfig, &telemetryData, &commonPipelineEnvironment)
|
||||||
telemetryData.ErrorCode = "0"
|
telemetryData.ErrorCode = "0"
|
||||||
log.Entry().Info("SUCCESS")
|
log.Entry().Info("SUCCESS")
|
||||||
},
|
},
|
||||||
@ -208,6 +240,17 @@ func terraformExecuteMetadata() config.StepData {
|
|||||||
Containers: []config.Container{
|
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: ""}}},
|
{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
|
return theMetaData
|
||||||
|
@ -94,15 +94,19 @@ func TestRunTerraformExecute(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tt {
|
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()
|
t.Parallel()
|
||||||
// init
|
// init
|
||||||
config := test.terraformExecuteOptions
|
config := test.terraformExecuteOptions
|
||||||
utils := newTerraformExecuteTestsUtils()
|
utils := newTerraformExecuteTestsUtils()
|
||||||
|
utils.StdoutReturn = map[string]string{}
|
||||||
|
utils.StdoutReturn["terraform output -json"] = "{}"
|
||||||
|
utils.StdoutReturn["terraform -chgdir=src output -json"] = "{}"
|
||||||
|
|
||||||
runner := utils.ExecMockRunner
|
runner := utils.ExecMockRunner
|
||||||
|
|
||||||
// test
|
// test
|
||||||
err := runTerraformExecute(&config, nil, utils)
|
err := runTerraformExecute(&config, nil, utils, &terraformExecuteCommonPipelineEnvironment{})
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -117,4 +121,32 @@ func TestRunTerraformExecute(t *testing.T) {
|
|||||||
assert.Subset(t, runner.Env, test.expectedEnvVars)
|
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:
|
env:
|
||||||
- name: TF_IN_AUTOMATION
|
- name: TF_IN_AUTOMATION
|
||||||
value: piper
|
value: piper
|
||||||
|
outputs:
|
||||||
|
resources:
|
||||||
|
- name: commonPipelineEnvironment
|
||||||
|
type: piperEnvironment
|
||||||
|
params:
|
||||||
|
- name: custom/terraformOutputs
|
||||||
|
type: 'map[string]interface{}'
|
||||||
|
Loading…
Reference in New Issue
Block a user