Skip to content

Commit 12ffdd2

Browse files
committed
chore: Cleaning up TestParseModuleURLSkipsGitLookupForNonGitSchemes
1 parent 9c24fab commit 12ffdd2

2 files changed

Lines changed: 83 additions & 14 deletions

File tree

internal/cli/commands/scaffold/module_url_test.go

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ package scaffold_test
22

33
import (
44
"context"
5-
"sync"
5+
"net/http"
6+
"strings"
67
"testing"
78

89
"github.com/gruntwork-io/terragrunt/internal/cli/commands/scaffold"
10+
"github.com/gruntwork-io/terragrunt/internal/tfimpl"
911
"github.com/gruntwork-io/terragrunt/internal/venv"
1012
"github.com/gruntwork-io/terragrunt/internal/vexec"
13+
"github.com/gruntwork-io/terragrunt/internal/vhttp"
1114
"github.com/gruntwork-io/terragrunt/pkg/options"
1215
"github.com/gruntwork-io/terragrunt/test/helpers/logger"
1316
"github.com/gruntwork-io/terragrunt/test/helpers/venvtest"
@@ -19,25 +22,78 @@ import (
1922
// assert on whether a lookup was attempted at all rather than on log output.
2023
type commandRecorder struct {
2124
names []string
22-
mu sync.Mutex
2325
}
2426

2527
func (r *commandRecorder) venv() *venv.Venv {
2628
return venvtest.New().WithHandler(func(_ context.Context, inv vexec.Invocation) vexec.Result {
27-
r.mu.Lock()
28-
defer r.mu.Unlock()
29-
3029
r.names = append(r.names, inv.Name)
3130

3231
return vexec.Result{ExitCode: 1}
3332
})
3433
}
3534

36-
func (r *commandRecorder) recorded() []string {
37-
r.mu.Lock()
38-
defer r.mu.Unlock()
35+
// registryStub answers the registry protocol for one module and records the
36+
// hosts it was asked about, so a test can pin which registry a source with no
37+
// host of its own resolves against.
38+
type registryStub struct {
39+
modulePath string
40+
versions []string
41+
seen []string
42+
}
43+
44+
func (s *registryStub) venv() *venv.Venv {
45+
return venvtest.New().WithHTTP(vhttp.NewMemClient(s.handle))
46+
}
3947

40-
return append([]string(nil), r.names...)
48+
func (s *registryStub) handle(_ context.Context, req *http.Request) (*http.Response, error) {
49+
s.seen = append(s.seen, req.URL.Host)
50+
51+
json := http.Header{"Content-Type": []string{"application/json"}}
52+
53+
if req.URL.Path == "/.well-known/terraform.json" {
54+
return vhttp.Respond(http.StatusOK, []byte(`{"modules.v1":"/v1/modules/"}`), json), nil
55+
}
56+
57+
if req.URL.Path == "/v1/modules/"+s.modulePath+"/versions" {
58+
quoted := make([]string, 0, len(s.versions))
59+
for _, v := range s.versions {
60+
quoted = append(quoted, `{"version":"`+v+`"}`)
61+
}
62+
63+
body := `{"modules":[{"versions":[` + strings.Join(quoted, ",") + `]}]}`
64+
65+
return vhttp.Respond(http.StatusOK, []byte(body), json), nil
66+
}
67+
68+
return vhttp.Respond(http.StatusNotFound, nil, nil), nil
69+
}
70+
71+
// TestParseModuleURLPinsUnpinnedRegistrySourceToLatestStable covers the
72+
// registry a source that names no host of its own resolves against. Only the
73+
// auto provider cache dir setup detects the wrapped binary for scaffold, so a
74+
// run without it must still reach tofu's registry rather than Terraform's.
75+
func TestParseModuleURLPinsUnpinnedRegistrySourceToLatestStable(t *testing.T) {
76+
t.Parallel()
77+
78+
registry := &registryStub{
79+
modulePath: "acme/vpc/aws",
80+
versions: []string{"0.0.1", "1.2.0", "1.3.0-rc1"},
81+
}
82+
83+
opts := options.NewTerragruntOptions()
84+
opts.TofuImplementation = tfimpl.Unknown
85+
86+
resolved, err := scaffold.ParseModuleURL(
87+
t.Context(),
88+
logger.CreateLogger(),
89+
registry.venv(),
90+
opts,
91+
map[string]any{},
92+
"tfr:///acme/vpc/aws",
93+
)
94+
require.NoError(t, err)
95+
assert.Equal(t, "tfr:///acme/vpc/aws?version=1.2.0", resolved)
96+
assert.Equal(t, []string{"registry.opentofu.org", "registry.opentofu.org"}, registry.seen)
4197
}
4298

4399
// TestParseModuleURLSkipsGitLookupForNonGitSchemes covers the scaffold half of
@@ -95,7 +151,7 @@ func TestParseModuleURLSkipsGitLookupForNonGitSchemes(t *testing.T) {
95151
)
96152
require.NoError(t, err)
97153
assert.Equal(t, tc.moduleURL, resolved)
98-
assert.Empty(t, recorder.recorded())
154+
assert.Empty(t, recorder.names)
99155
})
100156
}
101157
}
@@ -121,7 +177,7 @@ func TestParseModuleURLLooksUpTagForGitSources(t *testing.T) {
121177
)
122178
require.NoError(t, err)
123179
assert.Equal(t, moduleURL, resolved)
124-
assert.Contains(t, recorder.recorded(), "git")
180+
assert.Contains(t, recorder.names, "git")
125181
}
126182

127183
// TestParseModuleURLLeavesRegistrySourceUnpinnedWhenRegistryUnreachable pins

internal/cli/commands/scaffold/scaffold.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ func Prepare(
272272
"module_url": resolvedURL,
273273
}, func(ctx context.Context, l log.Logger) error {
274274
registryOpt := getter.WithTFRegistry(getter.NewRegistryGetter(l, v).
275-
WithTofuImplementation(opts.TofuImplementation))
275+
WithTofuImplementation(registryImplementation(opts)))
276276

277277
if _, getErr := getter.GetAny(ctx, v, tempDir, resolvedURL, registryOpt); getErr != nil {
278278
return fmt.Errorf("downloading scaffold module from %s: %w", resolvedURL, getErr)
@@ -709,7 +709,7 @@ func downloadTemplate(
709709
"template_url": baseURL.String(),
710710
}, func(ctx context.Context, l log.Logger) error {
711711
registryOpt := getter.WithTFRegistry(getter.NewRegistryGetter(l, v).
712-
WithTofuImplementation(opts.TofuImplementation))
712+
WithTofuImplementation(registryImplementation(opts)))
713713

714714
if _, getErr := getter.GetAny(ctx, v, templateDir, baseURL.String(), registryOpt); getErr != nil {
715715
return fmt.Errorf(
@@ -1040,6 +1040,19 @@ func IsGitShapedScheme(scheme string) bool {
10401040
return false
10411041
}
10421042

1043+
// registryImplementation reports which implementation the default registry
1044+
// host follows for a tfr:// source that omits its host. Only the auto
1045+
// provider cache dir setup fills TofuImplementation in for scaffold, so a run
1046+
// with that setup disabled arrives here with nothing detected and falls back
1047+
// to tofu rather than to Terraform's registry.
1048+
func registryImplementation(opts *options.TerragruntOptions) tfimpl.Type {
1049+
if opts.TofuImplementation == tfimpl.Unknown {
1050+
return tfimpl.OpenTofu
1051+
}
1052+
1053+
return opts.TofuImplementation
1054+
}
1055+
10431056
// pinLatestRegistryVersion pins a tfr:// registry source carrying no
10441057
// ?version= to the latest stable version the registry publishes, mirroring
10451058
// what [shell.GitLastReleaseTag] pins a git source to. Any other scheme, or a
@@ -1066,7 +1079,7 @@ func pinLatestRegistryVersion(
10661079

10671080
registryDomain := rootSourceURL.Host
10681081
if registryDomain == "" {
1069-
registryDomain = tfimpl.DefaultRegistryDomain(opts.TofuImplementation)
1082+
registryDomain = tfimpl.DefaultRegistryDomain(registryImplementation(opts))
10701083
}
10711084

10721085
modulePath, _ := getter.SourceDirSubdir(rootSourceURL.Path)

0 commit comments

Comments
 (0)