Skip to content

Commit 994635c

Browse files
aknyshclaudeautofix-ci[bot]
authored
feat: Packer directory-based template support and improvements (#1982)
* updates * updates * updates * updates * updates * updates * docs: Add Packer directory-based templates milestone to roadmap Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Address CodeRabbit feedback on Packer directory-based templates PR - Remove unused Terminal import from troubleshooting.mdx - Add language tag to PRD code block - Improve packer build tests (run init first, capture stderr, skip if plugins missing) - Fix Windows path quoting in packer_output_test.go (use single quotes) - Improve captureStdout helper (restore logger output, close pipes properly, handle panics) - Add fallback for network timeout in fetch-latest-release plugin - Fix broken documentation link (packer-build → build) - Refactor nested if blocks to reduce complexity (nestif linter) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * address comments, add tests * address comments, add tests * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * [autofix.ci] apply automated fixes (attempt 3/3) * address comments, add tests * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * [autofix.ci] apply automated fixes (attempt 3/3) * update NOTICE * [autofix.ci] apply automated fixes * address comments * [autofix.ci] apply automated fixes * address comments --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent bda1aab commit 994635c

File tree

26 files changed

+2035
-72
lines changed

26 files changed

+2035
-72
lines changed

cmd/packer_build_test.go

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"os"
6+
"strings"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
11+
log "github.com/cloudposse/atmos/pkg/logger"
12+
)
13+
14+
// TestPackerBuildCmd tests the packer build command execution.
15+
// This test verifies that packer build executes correctly.
16+
// The actual packer execution may fail due to missing AWS credentials.
17+
// The test verifies command arguments are parsed correctly, component and stack are resolved,
18+
// the variable file is generated, and packer is invoked with correct arguments.
19+
func TestPackerBuildCmd(t *testing.T) {
20+
_ = NewTestKit(t)
21+
22+
skipIfPackerNotInstalled(t)
23+
24+
workDir := "../tests/fixtures/scenarios/packer"
25+
t.Setenv("ATMOS_CLI_CONFIG_PATH", workDir)
26+
t.Setenv("ATMOS_LOGS_LEVEL", "Warning")
27+
log.SetLevel(log.WarnLevel)
28+
29+
// Ensure plugins are installed so build errors are about credentials, not init.
30+
RootCmd.SetArgs([]string{"packer", "init", "aws/bastion", "-s", "nonprod"})
31+
if initErr := Execute(); initErr != nil {
32+
t.Skipf("Skipping test: packer init failed (may require network access): %v", initErr)
33+
}
34+
35+
// Reset for actual test.
36+
_ = NewTestKit(t)
37+
38+
oldStdout := os.Stdout
39+
oldStderr := os.Stderr
40+
r, w, err := os.Pipe()
41+
if err != nil {
42+
t.Fatalf("failed to create pipe for stdout capture: %v", err)
43+
}
44+
os.Stdout = w
45+
os.Stderr = w
46+
log.SetOutput(w)
47+
48+
// Ensure cleanup happens before any reads.
49+
defer func() {
50+
os.Stdout = oldStdout
51+
os.Stderr = oldStderr
52+
log.SetOutput(os.Stderr)
53+
}()
54+
55+
// Run packer build. It will fail due to missing AWS credentials.
56+
// We verify that Atmos correctly processes the command.
57+
RootCmd.SetArgs([]string{"packer", "build", "aws/bastion", "-s", "nonprod"})
58+
err = Execute()
59+
60+
// Close write end after Execute.
61+
_ = w.Close()
62+
63+
// Read the captured output.
64+
var buf bytes.Buffer
65+
_, _ = buf.ReadFrom(r)
66+
output := buf.String()
67+
68+
// The command may fail due to AWS credentials.
69+
// The output should contain packer-specific content, indicating that Atmos invoked packer correctly.
70+
if err == nil {
71+
t.Logf("TestPackerBuildCmd completed successfully (unexpected in test environment)")
72+
return
73+
}
74+
75+
// Skip if plugins are still missing despite init attempt.
76+
if strings.Contains(output, "Missing plugins") {
77+
t.Skipf("Skipping test: packer plugins missing (run packer init): %v", err)
78+
}
79+
80+
// If packer ran and failed due to credentials, that's expected.
81+
// Check that packer actually ran (output contains packer-specific content).
82+
packerRan := strings.Contains(output, "amazon-ebs") ||
83+
strings.Contains(output, "Build") ||
84+
strings.Contains(output, "credential") ||
85+
strings.Contains(output, "Packer")
86+
87+
if packerRan {
88+
t.Logf("Packer build executed but failed (likely due to missing credentials): %v", err)
89+
// Test passes - packer was correctly invoked.
90+
return
91+
}
92+
93+
// If the error is from Atmos (not packer), that's a real failure.
94+
t.Logf("TestPackerBuildCmd output: %s", output)
95+
t.Errorf("Packer build failed unexpectedly: %v", err)
96+
}
97+
98+
func TestPackerBuildCmdInvalidComponent(t *testing.T) {
99+
_ = NewTestKit(t)
100+
101+
skipIfPackerNotInstalled(t)
102+
103+
workDir := "../tests/fixtures/scenarios/packer"
104+
t.Setenv("ATMOS_CLI_CONFIG_PATH", workDir)
105+
t.Setenv("ATMOS_LOGS_LEVEL", "Warning")
106+
log.SetLevel(log.WarnLevel)
107+
108+
// Capture stderr for error messages.
109+
oldStderr := os.Stderr
110+
r, w, err := os.Pipe()
111+
if err != nil {
112+
t.Fatalf("failed to create pipe for stderr capture: %v", err)
113+
}
114+
os.Stderr = w
115+
log.SetOutput(w)
116+
117+
defer func() {
118+
os.Stderr = oldStderr
119+
log.SetOutput(os.Stderr)
120+
}()
121+
122+
RootCmd.SetArgs([]string{"packer", "build", "invalid/component", "-s", "nonprod"})
123+
err = Execute()
124+
125+
// Close write end after Execute.
126+
_ = w.Close()
127+
128+
// Read the captured output.
129+
var buf bytes.Buffer
130+
_, _ = buf.ReadFrom(r)
131+
output := buf.String()
132+
133+
// Should fail with invalid component error.
134+
assert.Error(t, err, "'TestPackerBuildCmdInvalidComponent' should fail for invalid component")
135+
136+
// Log the error for debugging.
137+
t.Logf("TestPackerBuildCmdInvalidComponent error: %v", err)
138+
t.Logf("TestPackerBuildCmdInvalidComponent output: %s", output)
139+
}
140+
141+
func TestPackerBuildCmdMissingStack(t *testing.T) {
142+
_ = NewTestKit(t)
143+
144+
skipIfPackerNotInstalled(t)
145+
146+
workDir := "../tests/fixtures/scenarios/packer"
147+
t.Setenv("ATMOS_CLI_CONFIG_PATH", workDir)
148+
t.Setenv("ATMOS_LOGS_LEVEL", "Warning")
149+
log.SetLevel(log.WarnLevel)
150+
151+
RootCmd.SetArgs([]string{"packer", "build", "aws/bastion"})
152+
err := Execute()
153+
154+
// The command should fail either with "stack is required" or with a packer execution error.
155+
// Both indicate the command was processed.
156+
assert.Error(t, err, "'TestPackerBuildCmdMissingStack' should fail when stack is not specified")
157+
t.Logf("TestPackerBuildCmdMissingStack error: %v", err)
158+
}
159+
160+
func TestPackerBuildCmdWithDirectoryTemplate(t *testing.T) {
161+
_ = NewTestKit(t)
162+
163+
skipIfPackerNotInstalled(t)
164+
165+
workDir := "../tests/fixtures/scenarios/packer"
166+
t.Setenv("ATMOS_CLI_CONFIG_PATH", workDir)
167+
t.Setenv("ATMOS_LOGS_LEVEL", "Warning")
168+
log.SetLevel(log.WarnLevel)
169+
170+
// Ensure plugins are installed so build errors are about credentials, not init.
171+
RootCmd.SetArgs([]string{"packer", "init", "aws/multi-file", "-s", "nonprod"})
172+
if initErr := Execute(); initErr != nil {
173+
t.Skipf("Skipping test: packer init failed (may require network access): %v", initErr)
174+
}
175+
176+
// Reset for actual test.
177+
_ = NewTestKit(t)
178+
179+
oldStdout := os.Stdout
180+
oldStderr := os.Stderr
181+
r, w, err := os.Pipe()
182+
if err != nil {
183+
t.Fatalf("failed to create pipe for stdout capture: %v", err)
184+
}
185+
os.Stdout = w
186+
os.Stderr = w
187+
log.SetOutput(w)
188+
189+
defer func() {
190+
os.Stdout = oldStdout
191+
os.Stderr = oldStderr
192+
log.SetOutput(os.Stderr)
193+
}()
194+
195+
// Test with explicit directory template flag (directory mode).
196+
// This uses "." to load all *.pkr.hcl files from the component directory.
197+
RootCmd.SetArgs([]string{"packer", "build", "aws/multi-file", "-s", "nonprod", "--template", "."})
198+
err = Execute()
199+
200+
// Close write end after Execute.
201+
_ = w.Close()
202+
203+
// Read the captured output.
204+
var buf bytes.Buffer
205+
_, _ = buf.ReadFrom(r)
206+
output := buf.String()
207+
208+
// The command may fail due to AWS credentials, but verify packer was invoked.
209+
if err == nil {
210+
return
211+
}
212+
213+
// Skip if plugins are still missing despite init attempt.
214+
if strings.Contains(output, "Missing plugins") {
215+
t.Skipf("Skipping test: packer plugins missing (run packer init): %v", err)
216+
}
217+
218+
packerRan := strings.Contains(output, "amazon-ebs") ||
219+
strings.Contains(output, "Build") ||
220+
strings.Contains(output, "credential") ||
221+
strings.Contains(output, "Packer")
222+
223+
if packerRan {
224+
t.Logf("Packer build with directory template executed (failed due to credentials): %v", err)
225+
// Test passes.
226+
return
227+
}
228+
229+
t.Logf("TestPackerBuildCmdWithDirectoryTemplate output: %s", output)
230+
t.Errorf("Packer build with directory template failed unexpectedly: %v", err)
231+
}

0 commit comments

Comments
 (0)