Skip to content

Commit

Permalink
Improved errors printing returned from Sops (#3174)
Browse files Browse the repository at this point in the history
* Add fixture for sops errors

* Add checking for sops errors
  • Loading branch information
denis256 authored May 31, 2024
1 parent d4f855e commit 11ab667
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 12 deletions.
35 changes: 34 additions & 1 deletion config/config_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import (
"fmt"
"os"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strings"
"unicode/utf8"

"github.com/hashicorp/go-multierror"

"github.com/getsops/sops/v3/cmd/sops/formats"
"github.com/getsops/sops/v3/decrypt"
"github.com/hashicorp/go-getter"
Expand Down Expand Up @@ -753,7 +756,7 @@ func sopsDecryptFile(ctx *ParsingContext, params []string) (string, error) {

rawData, err := decrypt.File(sourceFile, format)
if err != nil {
return "", errors.WithStackTrace(err)
return "", errors.WithStackTrace(extractSopsErrors(err))
}

if utf8.Valid(rawData) {
Expand Down Expand Up @@ -984,3 +987,33 @@ func ParseAndDecodeVarFile(varFile string, fileContents []byte, out interface{})
}
return gocty.FromCtyValue(ctyVal, out)
}

// extractSopsErrors extracts the original errors from the sops library and returns them as a multierror.Error
func extractSopsErrors(err error) *multierror.Error {
var errs = &multierror.Error{}

// workaround to extract original errors from sops library
// using reflection extract GroupResults from getDataKeyError
// may not be compatible with future versions
errValue := reflect.ValueOf(err)
if errValue.Kind() == reflect.Ptr {
errValue = errValue.Elem()
}
if errValue.Type().Name() == "getDataKeyError" {
groupResultsField := errValue.FieldByName("GroupResults")
if groupResultsField.IsValid() && groupResultsField.Kind() == reflect.Slice {
for i := 0; i < groupResultsField.Len(); i++ {
groupErr := groupResultsField.Index(i)
if groupErr.CanInterface() {
errs = multierror.Append(errs, groupErr.Interface().(error))
}
}
}
}

// append the original error if no group results were found
if errs.Len() == 0 {
errs = multierror.Append(errs, err)
}
return errs
}
22 changes: 22 additions & 0 deletions test/fixture-sops-errors/file.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
sops:
kms:
- arn: arn:aws:kms:us-east-1:123456789012:key/abcd1234-a123-456a-a12b-a123b4cd56ef
created_at: '2024-05-31T12:00:00Z'
enc: AQICAHh/wUk47iRfX5z0YYrj2L8dXbIf3+m0XL7B3BQmXU1F3wG8O5k3AoUmK9AekxyAmNndAAAAcTB2BgkqhkiG9w0BBwagZTBjAgEAMGkGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMzjt8jKK8XHdR+NTbAgEQgDtLGz9iI4HhXJTh3x/aRb9nsJhEALwJhHMRUnYtQ==
gcp_kms: []
azure_kv: []
hc_vault: []
age: []
lastmodified: '2024-05-31T12:00:00Z'
mac: ENC[AES256_GCM,data:crypteddata,iv:ivvalue,tag:tagvalue]
pgp: []
encrypted_regex: ^(data|stringData)$
version: 3.7.1

apiVersion: v1
kind: Secret
metadata:
name: example-secret
namespace: default
data:
example-key: ENC[AES256_GCM,data:crypteddata,iv:ivvalue,tag:tagvalue]
Empty file.
3 changes: 3 additions & 0 deletions test/fixture-sops-errors/terragrunt.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
locals {
secret_vars = yamldecode(sops_decrypt_file("${get_terragrunt_dir()}/file.yaml"))
}
39 changes: 28 additions & 11 deletions test/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ const (
TEST_FIXTURE_INFO_ERROR = "fixture-terragrunt-info-error"
TEST_FIXTURE_DEPENDENCY_OUTPUT = "fixture-dependency-output"
TEST_FIXTURE_OUT_DIR = "fixture-out-dir"
TEST_FIXTURE_SOPS_ERRORS = "fixture-sops-errors"
TERRAFORM_BINARY = "terraform"
TOFU_BINARY = "tofu"
TERRAFORM_FOLDER = ".terraform"
Expand Down Expand Up @@ -6867,7 +6868,7 @@ func TestStorePlanFilesRunAllPlanApply(t *testing.T) {
testPath := util.JoinPath(tmpEnvPath, TEST_FIXTURE_OUT_DIR)

// run plan with output directory
_, output, err := runTerragruntCommandWithOutput(t, fmt.Sprintf("terraform run-all plan --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
_, output, err := runTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all plan --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
require.NoError(t, err)

assert.Contains(t, output, fmt.Sprintf("Using output file %s", tmpDir))
Expand All @@ -6880,7 +6881,7 @@ func TestStorePlanFilesRunAllPlanApply(t *testing.T) {
assert.Equal(t, "tfplan.tfplan", filepath.Base(file))
}

_, _, err = runTerragruntCommandWithOutput(t, fmt.Sprintf("terraform run-all apply --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
_, _, err = runTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all apply --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
require.NoError(t, err)
}

Expand All @@ -6893,10 +6894,10 @@ func TestStorePlanFilesRunAllDestroy(t *testing.T) {
testPath := util.JoinPath(tmpEnvPath, TEST_FIXTURE_OUT_DIR)

// plan and apply
_, _, err := runTerragruntCommandWithOutput(t, fmt.Sprintf("terraform run-all plan --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
_, _, err := runTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all plan --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
require.NoError(t, err)

_, _, err = runTerragruntCommandWithOutput(t, fmt.Sprintf("terraform run-all apply --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
_, _, err = runTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all apply --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
require.NoError(t, err)

// remove all tfstate files from temp directory to prepare destroy
Expand All @@ -6908,7 +6909,7 @@ func TestStorePlanFilesRunAllDestroy(t *testing.T) {
}

// prepare destroy plan
_, output, err := runTerragruntCommandWithOutput(t, fmt.Sprintf("terraform run-all plan -destroy --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
_, output, err := runTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all plan -destroy --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
require.NoError(t, err)

assert.Contains(t, output, fmt.Sprintf("Using output file %s", tmpDir))
Expand All @@ -6920,7 +6921,7 @@ func TestStorePlanFilesRunAllDestroy(t *testing.T) {
assert.Equal(t, "tfplan.tfplan", filepath.Base(file))
}

_, _, err = runTerragruntCommandWithOutput(t, fmt.Sprintf("terraform run-all apply --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
_, _, err = runTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all apply --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
require.NoError(t, err)
}

Expand Down Expand Up @@ -6962,7 +6963,7 @@ func TestPlanJsonPlanBinaryRunAll(t *testing.T) {
testPath := util.JoinPath(tmpEnvPath, TEST_FIXTURE_OUT_DIR)

// run plan with output directory
_, _, err := runTerragruntCommandWithOutput(t, fmt.Sprintf("terraform run-all plan --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-json-out-dir %s --terragrunt-out-dir %s", testPath, tmpDir, tmpDir))
_, _, err := runTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all plan --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-json-out-dir %s --terragrunt-out-dir %s", testPath, tmpDir, tmpDir))
require.NoError(t, err)

// verify that was generated json files with plan data
Expand Down Expand Up @@ -6997,23 +6998,39 @@ func TestTerragruntRunAllPlanAndShow(t *testing.T) {
testPath := util.JoinPath(tmpEnvPath, TEST_FIXTURE_OUT_DIR)

// run plan and apply
_, _, err := runTerragruntCommandWithOutput(t, fmt.Sprintf("terraform run-all plan --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
_, _, err := runTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all plan --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
require.NoError(t, err)

_, _, err = runTerragruntCommandWithOutput(t, fmt.Sprintf("terraform run-all apply --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
_, _, err = runTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all apply --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
require.NoError(t, err)

// run new plan and show
_, _, err = runTerragruntCommandWithOutput(t, fmt.Sprintf("terraform run-all plan --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
_, _, err = runTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all plan --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
require.NoError(t, err)

stdout, _, err := runTerragruntCommandWithOutput(t, fmt.Sprintf("terraform run-all show --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s -no-color", testPath, tmpDir))
stdout, _, err := runTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all show --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s --terragrunt-out-dir %s -no-color", testPath, tmpDir))
require.NoError(t, err)

// Verify that output contains the plan and not just the actual state output
assert.Contains(t, stdout, "No changes. Your infrastructure matches the configuration.")
}

func TestTerragruntLogSopsErrors(t *testing.T) {
t.Parallel()

// create temporary directory for plan files
tmpEnvPath := copyEnvironment(t, TEST_FIXTURE_SOPS_ERRORS)
cleanupTerraformFolder(t, tmpEnvPath)
testPath := util.JoinPath(tmpEnvPath, TEST_FIXTURE_SOPS_ERRORS)

// apply and check for errors
_, errorOut, err := runTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt apply --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s", testPath))
require.Error(t, err)

require.Contains(t, errorOut, "error decrypting key: [error decrypting key")
require.Contains(t, errorOut, "error base64-decoding encrypted data key: illegal base64 data at input byte")
}

func validateOutput(t *testing.T, outputs map[string]TerraformOutput, key string, value interface{}) {
t.Helper()
output, hasPlatform := outputs[key]
Expand Down

0 comments on commit 11ab667

Please sign in to comment.