Skip to content

Commit cfdc230

Browse files
authored
Fix TestStacksGenerateRemote (#3865)
* Updated compression level * Setting compression 0 * Add depth url * Config update * git commands debug * space check test * Updated pack threads * Window memory update * global update * Updated git clone timeout * Git ssh path * Memory limit update * Usage of https for repo clone * Post buffer update * HTTP config update * Config cleanup * Add http low speed * Clone update * Config update * Disable GC * Size limit update * https git clone * Switch to go getter v2 * Usage of go-getter v2, added remote review * Git config cleanup * Cleanup * stack generate improvements
1 parent 3f47a90 commit cfdc230

File tree

5 files changed

+61
-148
lines changed

5 files changed

+61
-148
lines changed

.circleci/config.yml

+3
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ setup_test_environment: &setup_test_environment
7878
# Import test / dev key for SOPS
7979
gpg --import --no-tty --batch --yes ./test/fixtures/sops/test_pgp_key.asc
8080
mkdir -p logs
81+
# configure git to avoid periodic failures
82+
git config --global core.compression 0
83+
git config --global gc.auto 0
8184
no_output_timeout: 30m
8285

8386
run_integration_test: &run_integration_test

_ci/install-golang.ps1

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ choco install golang --version 1.23.1 -y
44
Get-Command go
55
go version
66

7+
# configure git compression
8+
git config --global core.compression 0
9+
git config --global gc.auto 0
10+
711
try {
812
# Enable Developer Mode
913
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"

cli/commands/stack/generate.go

+36-146
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@ package stack
33
import (
44
"context"
55
"fmt"
6-
"io"
7-
"net/url"
86
"os"
97
"path/filepath"
108
"strings"
119

10+
"github.com/gruntwork-io/terragrunt/util"
11+
1212
"github.com/gruntwork-io/terragrunt/config"
13-
"github.com/hashicorp/go-getter"
13+
"github.com/hashicorp/go-getter/v2"
1414

1515
"github.com/gruntwork-io/terragrunt/internal/errors"
1616
"github.com/gruntwork-io/terragrunt/options"
1717
)
1818

19+
const (
20+
StackManifestName = ".terragrunt-stack-manifest"
21+
)
22+
1923
func generateStack(ctx context.Context, opts *options.TerragruntOptions) error {
2024
opts.TerragruntStackConfigPath = filepath.Join(opts.WorkingDir, defaultStackFile)
2125
opts.Logger.Infof("Generating stack from %s", opts.TerragruntStackConfigPath)
@@ -48,172 +52,58 @@ func processStackFile(ctx context.Context, opts *options.TerragruntOptions, stac
4852
return errors.New(fmt.Errorf("failed to get absolute path for destination '%s': %w", dest, err))
4953
}
5054

51-
client := &getter.Client{
52-
Dst: dest,
53-
Mode: getter.ClientModeAny,
54-
Dir: true,
55-
DisableSymlinks: true,
56-
Options: []getter.ClientOption{
57-
getter.WithContext(ctx),
58-
},
59-
}
60-
61-
// setting custom getters
62-
client.Getters = map[string]getter.Getter{}
63-
64-
for getterName, getterValue := range getter.Getters {
65-
// setting custom getter for file to not use symlinks
66-
if getterName == "file" {
67-
client.Getters[getterName] = &stacksFileProvider{}
68-
} else {
69-
client.Getters[getterName] = getterValue
70-
}
71-
}
72-
73-
// fetching unit source
7455
src := unit.Source
56+
opts.Logger.Debugf("Processing unit: %s (%s) to %s", unit.Name, src, dest)
7557

76-
// set absolute path for source if it's not an absolute path or URL
77-
if !filepath.IsAbs(unit.Source) && !isURL(client, unit.Source) {
58+
if isLocal(opts, src) {
7859
src = filepath.Join(opts.WorkingDir, unit.Source)
7960
src, err = filepath.Abs(src)
8061

8162
if err != nil {
8263
opts.Logger.Warnf("failed to get absolute path for source '%s': %v", unit.Source, err)
8364
src = unit.Source
8465
}
85-
}
8666

87-
opts.Logger.Debugf("Processing unit: %s (%s) to %s", unit.Name, src, dest)
88-
89-
client.Src = src
67+
if err := util.CopyFolderContentsWithFilter(opts.Logger, src, dest, StackManifestName, func(absolutePath string) bool {
68+
return true
69+
}); err != nil {
70+
return errors.New(err)
71+
}
72+
} else {
73+
if err := os.MkdirAll(dest, dirPerm); err != nil {
74+
return errors.New(err)
75+
}
9076

91-
if err := client.Get(); err != nil {
92-
return errors.New(err)
77+
if _, err := getter.GetAny(ctx, dest, src); err != nil {
78+
return errors.New(err)
79+
}
9380
}
9481
}
9582

9683
return nil
9784
}
9885

99-
func isURL(client *getter.Client, str string) bool {
100-
value, err := getter.Detect(str, client.Dst, getter.Detectors)
101-
if err != nil {
102-
return false
103-
}
104-
// check if starts with file://
105-
if strings.HasPrefix(value, "file://") {
106-
return false
107-
}
108-
109-
return true
110-
}
111-
112-
// stacksFileProvider is a custom getter for file:// protocol.
113-
type stacksFileProvider struct {
114-
client *getter.Client
115-
}
116-
117-
// Get implements downloading functionality.
118-
func (p *stacksFileProvider) Get(dst string, u *url.URL) error {
119-
src := u.Path
120-
file, err := os.Stat(src)
121-
122-
if err != nil {
123-
return errors.New(fmt.Errorf("source path error: %w", err))
124-
}
125-
126-
if file.IsDir() {
127-
return p.copyDir(src, dst)
128-
}
129-
130-
return p.copyFile(src, dst)
131-
}
132-
133-
// GetFile implements single file download.
134-
func (p *stacksFileProvider) GetFile(dst string, u *url.URL) error {
135-
return p.copyFile(u.Path, dst)
136-
}
137-
138-
// ClientMode determines if we're getting a directory or single file.
139-
func (p *stacksFileProvider) ClientMode(u *url.URL) (getter.ClientMode, error) {
140-
fi, err := os.Stat(u.Path)
141-
if err != nil {
142-
return getter.ClientModeInvalid, errors.New(err)
143-
}
144-
145-
if fi.IsDir() {
146-
return getter.ClientModeDir, nil
147-
}
148-
149-
return getter.ClientModeFile, nil
150-
}
151-
152-
// SetClient sets the client for this provider.
153-
func (p *stacksFileProvider) SetClient(c *getter.Client) {
154-
p.client = c
155-
}
156-
157-
func (p *stacksFileProvider) copyFile(src, dst string) error {
158-
if err := os.MkdirAll(filepath.Dir(dst), dirPerm); err != nil {
159-
return errors.New(err)
160-
}
161-
162-
srcFile, err := os.Open(src)
163-
if err != nil {
164-
return errors.New(err)
165-
}
166-
defer srcFile.Close()
167-
168-
srcInfo, err := srcFile.Stat()
169-
if err != nil {
170-
return errors.New(err)
171-
}
172-
173-
dstFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, srcInfo.Mode())
174-
if err != nil {
175-
return errors.New(err)
176-
}
177-
defer dstFile.Close()
178-
179-
if _, err := io.Copy(dstFile, srcFile); err != nil {
180-
return errors.New(err)
181-
}
182-
183-
return nil
184-
}
185-
186-
func (p *stacksFileProvider) copyDir(src, dst string) error {
187-
srcInfo, err := os.Stat(src)
188-
if err != nil {
189-
return errors.New(err)
190-
}
191-
192-
if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil {
193-
return errors.New(err)
86+
func isLocal(opts *options.TerragruntOptions, src string) bool {
87+
// check initially if the source is a local file
88+
src = filepath.Join(opts.WorkingDir, src)
89+
if util.FileExists(src) {
90+
return true
19491
}
195-
196-
entries, err := os.ReadDir(src)
197-
if err != nil {
198-
return errors.New(err)
92+
// check path through getters
93+
req := &getter.Request{
94+
Src: src,
19995
}
200-
201-
for _, entry := range entries {
202-
srcPath := filepath.Join(src, entry.Name())
203-
dstPath := filepath.Join(dst, entry.Name())
204-
205-
if entry.IsDir() {
206-
if err := p.copyDir(srcPath, dstPath); err != nil {
207-
return errors.New(err)
208-
}
209-
96+
for _, g := range getter.Getters {
97+
recognized, err := getter.Detect(req, g)
98+
if err != nil {
99+
opts.Logger.Debugf("Error detecting getter for %s: %v", src, err)
210100
continue
211101
}
212102

213-
if err := p.copyFile(srcPath, dstPath); err != nil {
214-
return errors.New(err)
103+
if recognized {
104+
break
215105
}
216106
}
217107

218-
return nil
108+
return strings.HasPrefix(req.Src, "file://")
219109
}

test/fixtures/stacks/remote/terragrunt.stack.hcl

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ locals {
33
}
44

55
unit "app1" {
6-
source = "github.com/gruntwork-io/terragrunt.git//test/fixtures/stacks/basic/units/chick?ref=${local.version}"
6+
source = "git::https://github.com/gruntwork-io/terragrunt.git//test/fixtures/stacks/basic/units/chick?ref=${local.version}&depth=1"
77
path = "app1"
88
}
99

1010
unit "app2" {
11-
source = "github.com/gruntwork-io/terragrunt.git//test/fixtures/stacks/basic/units/chick?ref=${local.version}"
11+
source = "git::https://github.com/gruntwork-io/terragrunt.git//test/fixtures/stacks/basic/units/chick?ref=${local.version}&depth=1"
1212
path = "app2"
1313
}
1414

test/integration_stacks_test.go

+16
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,22 @@ func TestStacksApply(t *testing.T) {
143143
assert.Contains(t, stdout, "local_file.file: Creation complete")
144144
}
145145

146+
func TestStacksApplyRemote(t *testing.T) {
147+
t.Parallel()
148+
149+
helpers.CleanupTerraformFolder(t, testFixtureStacksRemote)
150+
tmpEnvPath := helpers.CopyEnvironment(t, testFixtureStacksRemote)
151+
rootPath := util.JoinPath(tmpEnvPath, testFixtureStacksRemote)
152+
153+
stdout, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt stack run apply --experiment stacks --terragrunt-non-interactive --terragrunt-working-dir "+rootPath)
154+
require.NoError(t, err)
155+
156+
assert.Contains(t, stdout, "Apply complete! Resources: 1 added, 0 changed, 0 destroyed")
157+
assert.Contains(t, stdout, "local_file.file: Creation complete")
158+
path := util.JoinPath(rootPath, ".terragrunt-stack")
159+
validateStackDir(t, path)
160+
}
161+
146162
func TestStacksDestroy(t *testing.T) {
147163
t.Parallel()
148164

0 commit comments

Comments
 (0)