Skip to content

Commit 1b0953b

Browse files
committed
Incorporate go-getter support for 'git::' filepaths
The go-getters library now has support for local file system paths to Git repositories, specified with the 'git::' forcing token. The feature works for both absolute and relative filepaths, and supports all the usual go-getter goodies including '//' delimited subdirs and URI-style query parameters.[0][1] We incorporate that capability into Terraform, which allows users to specify paths to locally present Git repositories from which to clone other Terrform modules on which they are dependent. When coupled with Git submodules, this creates a powerful way to manage Terraform modules at specific versions without requiring those modules to be available on the network (e.g., on GitHub): module "my_module" { source = "git::../git-submodules/tf-modules/some-tf-module?ref=v0.1.0" // ... } From the perspective of Terraform, such Git repositories are "remote" in the same way that repositories on GitHub are. Note that within a Terraform module "call" block, the filepaths specified are relative to the directory in which the *.tf file lives, not relative to the current working directory of the Terraform process. In order to support this feature, Terraform needs to supply that contextual information to go-getter to allow relative filepath resolution to work. In order to do so, we needed to switch over to using go-getter's new "Contextual Detector" API. It works in the same basic way as the traditional Detector API, but allows us to provide this additional information. In keeping with the "keep things simple" comment in the commit message of 2b2ac1f, we are here maintaining our custom go-getter detectors in two places. Only now each is called FooCtxDetector rather than FooDetector. Nevertheless, all except the GitCtxDetector do little more than "pass through" delegation to its analogous FooDetector counterpart. Fixes hashicorp#25488 Fixes hashicorp#21107 [0] hashicorp/go-getter#268 [1] hashicorp/go-getter#269
1 parent 7032e65 commit 1b0953b

File tree

3 files changed

+75
-20
lines changed

3 files changed

+75
-20
lines changed

configs/configload/getter.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ import (
1717
// any meddling that might be done by other go-getter callers linked into our
1818
// executable.
1919

20-
var goGetterDetectors = []getter.Detector{
21-
new(getter.GitHubDetector),
22-
new(getter.GitDetector),
23-
new(getter.BitBucketDetector),
24-
new(getter.GCSDetector),
25-
new(getter.S3Detector),
26-
new(getter.FileDetector),
20+
var goGetterCtxDetectors = []getter.CtxDetector{
21+
new(getter.GitHubCtxDetector),
22+
new(getter.GitCtxDetector),
23+
new(getter.BitBucketCtxDetector),
24+
new(getter.GCSCtxDetector),
25+
new(getter.S3CtxDetector),
26+
new(getter.FileCtxDetector),
2727
}
2828

2929
var goGetterNoDetectors = []getter.Detector{}
@@ -80,12 +80,12 @@ type reusingGetter map[string]string
8080
// end-user-actionable error messages. At this time we do not have any
8181
// reasonable way to improve these error messages at this layer because
8282
// the underlying errors are not separatelyr recognizable.
83-
func (g reusingGetter) getWithGoGetter(instPath, addr string) (string, error) {
83+
func (g reusingGetter) getWithGoGetter(instPath, addr, srcAbs string) (string, error) {
8484
packageAddr, subDir := splitAddrSubdir(addr)
8585

8686
log.Printf("[DEBUG] will download %q to %s", packageAddr, instPath)
8787

88-
realAddr, err := getter.Detect(packageAddr, instPath, goGetterDetectors)
88+
realAddr, err := getter.CtxDetect(packageAddr, instPath, srcAbs, goGetterCtxDetectors)
8989
if err != nil {
9090
return "", err
9191
}

internal/initwd/getter.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ import (
2020
// any meddling that might be done by other go-getter callers linked into our
2121
// executable.
2222

23-
var goGetterDetectors = []getter.Detector{
24-
new(getter.GitHubDetector),
25-
new(getter.GitDetector),
26-
new(getter.BitBucketDetector),
27-
new(getter.GCSDetector),
28-
new(getter.S3Detector),
29-
new(getter.FileDetector),
23+
var goGetterCtxDetectors = []getter.CtxDetector{
24+
new(getter.GitHubCtxDetector),
25+
new(getter.GitCtxDetector),
26+
new(getter.BitBucketCtxDetector),
27+
new(getter.GCSCtxDetector),
28+
new(getter.S3CtxDetector),
29+
new(getter.FileCtxDetector),
3030
}
3131

3232
var goGetterNoDetectors = []getter.Detector{}
@@ -83,12 +83,12 @@ type reusingGetter map[string]string
8383
// end-user-actionable error messages. At this time we do not have any
8484
// reasonable way to improve these error messages at this layer because
8585
// the underlying errors are not separately recognizable.
86-
func (g reusingGetter) getWithGoGetter(instPath, addr string) (string, error) {
86+
func (g reusingGetter) getWithGoGetter(instPath, addr, srcAbs string) (string, error) {
8787
packageAddr, subDir := splitAddrSubdir(addr)
8888

8989
log.Printf("[DEBUG] will download %q to %s", packageAddr, instPath)
9090

91-
realAddr, err := getter.Detect(packageAddr, instPath, goGetterDetectors)
91+
realAddr, err := getter.CtxDetect(packageAddr, instPath, srcAbs, goGetterCtxDetectors)
9292
if err != nil {
9393
return "", err
9494
}

internal/initwd/module_install.go

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,13 @@ func (i *ModuleInstaller) installRegistryModule(req *earlyconfig.ModuleRequest,
450450

451451
log.Printf("[TRACE] ModuleInstaller: %s %s %s is available at %q", key, addr, latestMatch, dlAddr)
452452

453-
modDir, err := getter.getWithGoGetter(instPath, dlAddr)
453+
srcDirAbs, sdDiags := i.calledFromSourceDirAbs(req)
454+
if srcDirAbs == "" {
455+
diags = append(diags, sdDiags...)
456+
return nil, nil, diags
457+
}
458+
459+
modDir, err := getter.getWithGoGetter(instPath, dlAddr, srcDirAbs)
454460
if err != nil {
455461
// Errors returned by go-getter have very inconsistent quality as
456462
// end-user error messages, but for now we're accepting that because
@@ -521,7 +527,13 @@ func (i *ModuleInstaller) installGoGetterModule(req *earlyconfig.ModuleRequest,
521527
return nil, diags
522528
}
523529

524-
modDir, err := getter.getWithGoGetter(instPath, req.SourceAddr)
530+
srcDirAbs, sdDiags := i.calledFromSourceDirAbs(req)
531+
if srcDirAbs == "" {
532+
diags = append(diags, sdDiags...)
533+
return nil, diags
534+
}
535+
536+
modDir, err := getter.getWithGoGetter(instPath, req.SourceAddr, srcDirAbs)
525537
if err != nil {
526538
if _, ok := err.(*MaybeRelativePathErr); ok {
527539
log.Printf(
@@ -588,3 +600,46 @@ func (i *ModuleInstaller) installGoGetterModule(req *earlyconfig.ModuleRequest,
588600
func (i *ModuleInstaller) packageInstallPath(modulePath addrs.Module) string {
589601
return filepath.Join(i.modsDir, strings.Join(modulePath, "."))
590602
}
603+
604+
// A called module can exist in a (possibly remote) source code repository,
605+
// and go-getter will attempt to retrieve the modules from the repo if that is
606+
// the case.
607+
//
608+
// There is special case notion of "remote" for Terraform modules that live in
609+
// source code repositories that are "outside of the user's current Terraform
610+
// project", but which are expected to be present on the local file system. In
611+
// particular, Git submodules (see git-submodule(1)) can be used to keep such
612+
// repos nearby, in a location from which they can be cloned relative to
613+
// top-level directory of the current project.
614+
//
615+
// The location to such local repos can only be known in advance by the
616+
// relative path **from the Terraform module** that is "calling" a module
617+
// whose source comes from such a repo:
618+
//
619+
// module "my_module" {
620+
// source = "git::../../../git-submodules/tf-modules/my-tf-module//some/subdir?rev=v1.2.3"
621+
// // ...
622+
// }
623+
//
624+
// To locate such repos, we must provide go-getter with the absolute path to
625+
// the directory for the Terraform module that contains such references.
626+
//
627+
func (i *ModuleInstaller) calledFromSourceDirAbs(req *earlyconfig.ModuleRequest) (string, tfdiags.Diagnostics) {
628+
callingModSourceDir := filepath.Dir(req.CallPos.Filename) // dirname
629+
630+
var diags tfdiags.Diagnostics
631+
632+
abs, err := filepath.Abs(callingModSourceDir)
633+
if err != nil {
634+
diags = diags.Append(tfdiags.Sourceless(
635+
tfdiags.Error,
636+
"Unable to resolve absolute filepath of Terraform module",
637+
fmt.Sprintf(
638+
"Terraform tried resolve the absolute filepath of source file \"%s\", but encountered an error: %s",
639+
callingModSourceDir, err,
640+
),
641+
))
642+
}
643+
644+
return abs, diags
645+
}

0 commit comments

Comments
 (0)