-
-
Notifications
You must be signed in to change notification settings - Fork 174
Expand file tree
/
Copy pathmain_hooks_and_keychain_store_integration_test.go
More file actions
101 lines (87 loc) · 4.24 KB
/
Copy pathmain_hooks_and_keychain_store_integration_test.go
File metadata and controls
101 lines (87 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package main
import (
"io/fs"
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"github.com/cloudposse/atmos/pkg/store/providers"
)
// TestMainHooksAndKeychainStoreIntegration proves end-to-end, with no cloud credentials and no
// running server, that an after-terraform-apply store hook writes a Terraform output into an
// encrypted-at-rest secrets store (the keychain `file` backend).
//
// It deploys a component whose hook stores the output `.random`, then asserts both that the value
// round-trips through the store API and that the on-disk keyring file is encrypted (the plaintext
// never appears). A keychain store is `secret: true` by default, so the written value is reachable
// only via the declarative `!secret` function, never `!store`; this test deliberately verifies the
// write path (the subject of the question) rather than a cross-component read-back.
//
// Isolation is purely env-driven: XDG_DATA_HOME points the keyring file at a temp dir, and
// ATMOS_KEYRING_PASSWORD drives the file backend non-interactively (see pkg/keyring/file.go).
func TestMainHooksAndKeychainStoreIntegration(t *testing.T) {
// Skip gracefully if no terraform binary. Atmos invokes `terraform` by default (the fixture
// does not override `components.terraform.command`), so that is the binary that must exist.
if _, err := exec.LookPath("terraform"); err != nil {
t.Skip("terraform not available: required for hook+store integration test")
}
// Credential-free, server-free encrypted secrets store.
xdgDataHome := t.TempDir()
t.Setenv("XDG_DATA_HOME", xdgDataHome) // isolates the keyring file.
t.Setenv("ATMOS_KEYRING_PASSWORD", "atmos-test-pass") // >= 8 chars, read non-interactively.
// Disable CI auto-detection so deploy hooks don't try to download planfiles from GitHub
// Artifacts during tests. t.Setenv restores the prior value automatically at test end.
t.Setenv("GITHUB_ACTIONS", "")
origDir, err := os.Getwd()
if err != nil {
t.Fatalf("failed to get current working directory: %v", err)
}
defer os.RemoveAll(filepath.Join(origDir, "tests", "fixtures", "scenarios", "hooks-keychain-test", ".terraform"))
t.Chdir("tests/fixtures/scenarios/hooks-keychain-test")
// This integration test calls run() (not main()) to avoid os.Exit() which panics in Go 1.25+.
// run() returns an exit code instead of calling os.Exit().
// We manipulate os.Args since run() uses cmd.Execute() which reads os.Args internally.
origArgs := os.Args
defer func() { os.Args = origArgs }()
// Deploy `component1`, whose after-terraform-apply hook writes the terraform output `.random`
// into the keychain file store under key `random_id`.
os.Args = []string{"atmos", "terraform", "deploy", "component1", "-s", "test"}
if exitCode := run(); exitCode != 0 {
t.Fatalf("component1 deploy returned non-zero exit code: %d", exitCode)
}
// Round-trip: read the value back through a keychain store constructed with the same options.
// This asserts the hook persisted the exact content, not merely that a deploy succeeded.
s, err := providers.NewKeychainStore(&providers.KeychainStoreOptions{Backend: "file"})
require.NoError(t, err)
got, err := s.Get("test", "component1", "random_id")
require.NoError(t, err)
require.Equal(t, "random1", got)
// Encrypted at rest: the secret value must not appear in plaintext anywhere in the keyring
// file the keychain `file` backend wrote under XDG_DATA_HOME.
requireEncryptedAtRest(t, xdgDataHome, "random1")
}
// requireEncryptedAtRest fails if the plaintext appears in any file under dir, and fails if no
// files were written at all (which would make the check vacuous).
func requireEncryptedAtRest(t *testing.T, dir, plaintext string) {
t.Helper()
fileCount := 0
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
fileCount++
data, readErr := os.ReadFile(path)
if readErr != nil {
return readErr
}
require.NotContains(t, string(data), plaintext,
"plaintext secret found in keyring file %q — value is not encrypted at rest", path)
return nil
})
require.NoError(t, err)
require.Positive(t, fileCount, "no keyring files were written under %q; encryption check would be vacuous", dir)
}