@@ -2,12 +2,15 @@ package scaffold_test
22
33import (
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.
2023type commandRecorder struct {
2124 names []string
22- mu sync.Mutex
2325}
2426
2527func (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
0 commit comments