2023-05-02 10:33:06 -05:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2019-01-08 20:39:14 -06:00
package initwd
2018-02-13 16:40:53 -06:00
import (
initwd: Error message for local paths escaping module packages
Our module installer has a somewhat-informal idea of a "module package",
which is some external thing we can go fetch in order to add one or more
modules to the current configuration. Our documentation doesn't talk much
about it because most users seem to have found the distinction between
external and local modules pretty intuitive without us throwing a lot of
funny terminology at them, but there are some situations where the
distinction between a module and a module package are material to the
end-user.
One such situation is when using an absolute rather than relative
filesystem path: we treat that as an external package in order to make the
resulting working directory theoretically "portable" (although users can
do various other things to defeat that), and so Terraform will copy the
directory into .terraform/modules in the same way as it would download and
extract a remote archive package or clone a git repository.
A consequence of this, though, is that any relative paths called from
inside a module loaded from an absolute path will fail if they try to
traverse upward into the parent directory, because at runtime we're
actually running from a copy of the directory that's been taking out of
its original context.
A similar sort of situation can occur in a truly remote module package if
the author accidentally writes a "../" source path that traverses up out
of the package root, and so this commit introduces a special error message
for both situations that tries to be a bit clearer about there being a
package boundary and use that to explain why installation failed.
We would ideally have made escaping local references like that illegal in
the first place, but sadly we did not and so when we rebuilt the module
installer for Terraform v0.12 we ended up keeping the previous behavior of
just trying it and letting it succeed if there happened to somehow be a
matching directory at the given path, in order to remain compatible with
situations that had worked by coincidence rather than intention. For that
same reason, I've implemented this as a replacement error message we will
return only if local module installation was going to fail anyway, and
thus it only modifies the error message for some existing error situations
rather than introducing new error situations.
This also includes some light updates to the documentation to say a little
more about how Terraform treats absolute paths, though aiming not to get
too much into the weeds about module packages since it's something that
most users can get away with never knowing.
2021-05-21 17:28:20 -05:00
"bytes"
2021-11-01 15:09:16 -05:00
"context"
2019-01-08 20:39:14 -06:00
"flag"
"fmt"
2018-02-13 18:36:36 -06:00
"os"
2018-02-13 16:40:53 -06:00
"path/filepath"
2018-02-15 11:23:07 -06:00
"strings"
2018-02-13 16:40:53 -06:00
"testing"
Refactoring of module source addresses and module installation
It's been a long while since we gave close attention to the codepaths for
module source address parsing and external module package installation.
Due to their age, these codepaths often diverged from our modern practices
such as representing address types in the addrs package, and encapsulating
package installation details only in a particular location.
In particular, this refactor makes source address parsing a separate step
from module installation, which therefore makes the result of that parsing
available to other Terraform subsystems which work with the configuration
representation objects.
This also presented the opportunity to better encapsulate our use of
go-getter into a new package "getmodules" (echoing "getproviders"), which
is intended to be the only part of Terraform that directly interacts with
go-getter.
This is largely just a refactor of the existing functionality into a new
code organization, but there is one notable change in behavior here: the
source address parsing now happens during configuration loading rather
than module installation, which may cause errors about invalid addresses
to be returned in different situations than before. That counts as
backward compatible because we only promise to remain compatible with
configurations that are _valid_, which means that they can be initialized,
planned, and applied without any errors. This doesn't introduce any new
error cases, and instead just makes a pre-existing error case be detected
earlier.
Our module registry client is still using its own special module address
type from registry/regsrc for now, with a small shim from the new
addrs.ModuleSourceRegistry type. Hopefully in a later commit we'll also
rework the registry client to work with the new address type, but this
commit is already big enough as it is.
2021-05-27 21:24:59 -05:00
"github.com/davecgh/go-spew/spew"
2019-01-08 20:39:14 -06:00
"github.com/go-test/deep"
Refactoring of module source addresses and module installation
It's been a long while since we gave close attention to the codepaths for
module source address parsing and external module package installation.
Due to their age, these codepaths often diverged from our modern practices
such as representing address types in the addrs package, and encapsulating
package installation details only in a particular location.
In particular, this refactor makes source address parsing a separate step
from module installation, which therefore makes the result of that parsing
available to other Terraform subsystems which work with the configuration
representation objects.
This also presented the opportunity to better encapsulate our use of
go-getter into a new package "getmodules" (echoing "getproviders"), which
is intended to be the only part of Terraform that directly interacts with
go-getter.
This is largely just a refactor of the existing functionality into a new
code organization, but there is one notable change in behavior here: the
source address parsing now happens during configuration loading rather
than module installation, which may cause errors about invalid addresses
to be returned in different situations than before. That counts as
backward compatible because we only promise to remain compatible with
configurations that are _valid_, which means that they can be initialized,
planned, and applied without any errors. This doesn't introduce any new
error cases, and instead just makes a pre-existing error case be detected
earlier.
Our module registry client is still using its own special module address
type from registry/regsrc for now, with a small shim from the new
addrs.ModuleSourceRegistry type. Hopefully in a later commit we'll also
rework the registry client to work with the new address type, but this
commit is already big enough as it is.
2021-05-27 21:24:59 -05:00
"github.com/google/go-cmp/cmp"
2018-02-13 16:40:53 -06:00
version "github.com/hashicorp/go-version"
2021-06-02 14:26:35 -05:00
svchost "github.com/hashicorp/terraform-svchost"
2023-07-10 05:42:05 -05:00
2023-09-20 06:35:35 -05:00
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/configs/configload"
"github.com/opentofu/opentofu/internal/copy"
"github.com/opentofu/opentofu/internal/registry"
"github.com/opentofu/opentofu/internal/tfdiags"
2023-08-17 07:45:11 -05:00
2023-09-20 06:35:35 -05:00
_ "github.com/opentofu/opentofu/internal/logging"
2018-02-13 16:40:53 -06:00
)
2019-01-08 20:39:14 -06:00
func TestMain ( m * testing . M ) {
flag . Parse ( )
os . Exit ( m . Run ( ) )
}
func TestModuleInstaller ( t * testing . T ) {
2019-06-30 02:38:36 -05:00
fixtureDir := filepath . Clean ( "testdata/local-modules" )
2019-01-08 20:39:14 -06:00
dir , done := tempChdir ( t , fixtureDir )
2018-02-13 18:36:36 -06:00
defer done ( )
2018-02-13 16:40:53 -06:00
hooks := & testInstallHooks { }
2019-01-08 20:39:14 -06:00
modulesDir := filepath . Join ( dir , ".terraform/modules" )
2023-02-17 05:40:47 -06:00
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , nil )
2023-08-07 13:54:28 -05:00
_ , diags := inst . InstallModules ( context . Background ( ) , "." , "tests" , false , false , hooks )
2018-02-13 16:40:53 -06:00
assertNoDiagnostics ( t , diags )
wantCalls := [ ] testInstallHookCall {
{
Name : "Install" ,
ModuleAddr : "child_a" ,
PackageAddr : "" ,
2018-02-13 18:36:36 -06:00
LocalPath : "child_a" ,
2018-02-13 16:40:53 -06:00
} ,
{
Name : "Install" ,
ModuleAddr : "child_a.child_b" ,
PackageAddr : "" ,
2018-02-13 18:36:36 -06:00
LocalPath : "child_a/child_b" ,
2018-02-13 16:40:53 -06:00
} ,
}
2018-02-13 18:36:36 -06:00
if assertResultDeepEqual ( t , hooks . Calls , wantCalls ) {
return
}
2019-01-08 20:39:14 -06:00
loader , err := configload . NewLoader ( & configload . Config {
ModulesDir : modulesDir ,
} )
if err != nil {
t . Fatal ( err )
}
2018-02-13 18:36:36 -06:00
// Make sure the configuration is loadable now.
// (This ensures that correct information is recorded in the manifest.)
2018-02-15 11:23:07 -06:00
config , loadDiags := loader . LoadConfig ( "." )
2019-01-08 20:39:14 -06:00
assertNoDiagnostics ( t , tfdiags . Diagnostics { } . Append ( loadDiags ) )
2018-02-15 11:23:07 -06:00
wantTraces := map [ string ] string {
"" : "in root module" ,
"child_a" : "in child_a module" ,
"child_a.child_b" : "in child_b module" ,
}
gotTraces := map [ string ] string { }
config . DeepEach ( func ( c * configs . Config ) {
path := strings . Join ( c . Path , "." )
if c . Module . Variables [ "v" ] == nil {
gotTraces [ path ] = "<missing>"
return
}
varDesc := c . Module . Variables [ "v" ] . Description
gotTraces [ path ] = varDesc
} )
assertResultDeepEqual ( t , gotTraces , wantTraces )
2018-02-13 18:36:36 -06:00
}
2019-03-11 17:25:21 -05:00
func TestModuleInstaller_error ( t * testing . T ) {
2019-06-30 02:38:36 -05:00
fixtureDir := filepath . Clean ( "testdata/local-module-error" )
2019-03-11 17:25:21 -05:00
dir , done := tempChdir ( t , fixtureDir )
defer done ( )
hooks := & testInstallHooks { }
modulesDir := filepath . Join ( dir , ".terraform/modules" )
2023-02-17 05:40:47 -06:00
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , nil )
2023-08-07 13:54:28 -05:00
_ , diags := inst . InstallModules ( context . Background ( ) , "." , "tests" , false , false , hooks )
2019-03-11 17:25:21 -05:00
if ! diags . HasErrors ( ) {
t . Fatal ( "expected error" )
} else {
Refactoring of module source addresses and module installation
It's been a long while since we gave close attention to the codepaths for
module source address parsing and external module package installation.
Due to their age, these codepaths often diverged from our modern practices
such as representing address types in the addrs package, and encapsulating
package installation details only in a particular location.
In particular, this refactor makes source address parsing a separate step
from module installation, which therefore makes the result of that parsing
available to other Terraform subsystems which work with the configuration
representation objects.
This also presented the opportunity to better encapsulate our use of
go-getter into a new package "getmodules" (echoing "getproviders"), which
is intended to be the only part of Terraform that directly interacts with
go-getter.
This is largely just a refactor of the existing functionality into a new
code organization, but there is one notable change in behavior here: the
source address parsing now happens during configuration loading rather
than module installation, which may cause errors about invalid addresses
to be returned in different situations than before. That counts as
backward compatible because we only promise to remain compatible with
configurations that are _valid_, which means that they can be initialized,
planned, and applied without any errors. This doesn't introduce any new
error cases, and instead just makes a pre-existing error case be detected
earlier.
Our module registry client is still using its own special module address
type from registry/regsrc for now, with a small shim from the new
addrs.ModuleSourceRegistry type. Hopefully in a later commit we'll also
rework the registry client to work with the new address type, but this
commit is already big enough as it is.
2021-05-27 21:24:59 -05:00
assertDiagnosticSummary ( t , diags , "Invalid module source address" )
2019-03-11 17:25:21 -05:00
}
}
2023-02-17 05:40:47 -06:00
func TestModuleInstaller_emptyModuleName ( t * testing . T ) {
fixtureDir := filepath . Clean ( "testdata/empty-module-name" )
dir , done := tempChdir ( t , fixtureDir )
defer done ( )
hooks := & testInstallHooks { }
modulesDir := filepath . Join ( dir , ".terraform/modules" )
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , nil )
2023-08-07 13:54:28 -05:00
_ , diags := inst . InstallModules ( context . Background ( ) , "." , "tests" , false , false , hooks )
2023-02-17 05:40:47 -06:00
if ! diags . HasErrors ( ) {
t . Fatal ( "expected error" )
} else {
assertDiagnosticSummary ( t , diags , "Invalid module instance name" )
}
}
2023-09-10 04:29:40 -05:00
func TestModuleInstaller_invalidModuleName ( t * testing . T ) {
fixtureDir := filepath . Clean ( "testdata/invalid-module-name" )
dir , done := tempChdir ( t , fixtureDir )
defer done ( )
hooks := & testInstallHooks { }
modulesDir := filepath . Join ( dir , ".terraform/modules" )
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , registry . NewClient ( nil , nil ) )
_ , diags := inst . InstallModules ( context . Background ( ) , dir , "tests" , false , false , hooks )
if ! diags . HasErrors ( ) {
t . Fatal ( "expected error" )
} else {
assertDiagnosticSummary ( t , diags , "Invalid module instance name" )
}
}
initwd: Error message for local paths escaping module packages
Our module installer has a somewhat-informal idea of a "module package",
which is some external thing we can go fetch in order to add one or more
modules to the current configuration. Our documentation doesn't talk much
about it because most users seem to have found the distinction between
external and local modules pretty intuitive without us throwing a lot of
funny terminology at them, but there are some situations where the
distinction between a module and a module package are material to the
end-user.
One such situation is when using an absolute rather than relative
filesystem path: we treat that as an external package in order to make the
resulting working directory theoretically "portable" (although users can
do various other things to defeat that), and so Terraform will copy the
directory into .terraform/modules in the same way as it would download and
extract a remote archive package or clone a git repository.
A consequence of this, though, is that any relative paths called from
inside a module loaded from an absolute path will fail if they try to
traverse upward into the parent directory, because at runtime we're
actually running from a copy of the directory that's been taking out of
its original context.
A similar sort of situation can occur in a truly remote module package if
the author accidentally writes a "../" source path that traverses up out
of the package root, and so this commit introduces a special error message
for both situations that tries to be a bit clearer about there being a
package boundary and use that to explain why installation failed.
We would ideally have made escaping local references like that illegal in
the first place, but sadly we did not and so when we rebuilt the module
installer for Terraform v0.12 we ended up keeping the previous behavior of
just trying it and letting it succeed if there happened to somehow be a
matching directory at the given path, in order to remain compatible with
situations that had worked by coincidence rather than intention. For that
same reason, I've implemented this as a replacement error message we will
return only if local module installation was going to fail anyway, and
thus it only modifies the error message for some existing error situations
rather than introducing new error situations.
This also includes some light updates to the documentation to say a little
more about how Terraform treats absolute paths, though aiming not to get
too much into the weeds about module packages since it's something that
most users can get away with never knowing.
2021-05-21 17:28:20 -05:00
func TestModuleInstaller_packageEscapeError ( t * testing . T ) {
fixtureDir := filepath . Clean ( "testdata/load-module-package-escape" )
dir , done := tempChdir ( t , fixtureDir )
defer done ( )
// For this particular test we need an absolute path in the root module
// that must actually resolve to our temporary directory in "dir", so
// we need to do a little rewriting. We replace the arbitrary placeholder
// %%BASE%% with the temporary directory path.
{
rootFilename := filepath . Join ( dir , "package-escape.tf" )
2023-09-08 03:04:34 -05:00
template , err := os . ReadFile ( rootFilename )
initwd: Error message for local paths escaping module packages
Our module installer has a somewhat-informal idea of a "module package",
which is some external thing we can go fetch in order to add one or more
modules to the current configuration. Our documentation doesn't talk much
about it because most users seem to have found the distinction between
external and local modules pretty intuitive without us throwing a lot of
funny terminology at them, but there are some situations where the
distinction between a module and a module package are material to the
end-user.
One such situation is when using an absolute rather than relative
filesystem path: we treat that as an external package in order to make the
resulting working directory theoretically "portable" (although users can
do various other things to defeat that), and so Terraform will copy the
directory into .terraform/modules in the same way as it would download and
extract a remote archive package or clone a git repository.
A consequence of this, though, is that any relative paths called from
inside a module loaded from an absolute path will fail if they try to
traverse upward into the parent directory, because at runtime we're
actually running from a copy of the directory that's been taking out of
its original context.
A similar sort of situation can occur in a truly remote module package if
the author accidentally writes a "../" source path that traverses up out
of the package root, and so this commit introduces a special error message
for both situations that tries to be a bit clearer about there being a
package boundary and use that to explain why installation failed.
We would ideally have made escaping local references like that illegal in
the first place, but sadly we did not and so when we rebuilt the module
installer for Terraform v0.12 we ended up keeping the previous behavior of
just trying it and letting it succeed if there happened to somehow be a
matching directory at the given path, in order to remain compatible with
situations that had worked by coincidence rather than intention. For that
same reason, I've implemented this as a replacement error message we will
return only if local module installation was going to fail anyway, and
thus it only modifies the error message for some existing error situations
rather than introducing new error situations.
This also includes some light updates to the documentation to say a little
more about how Terraform treats absolute paths, though aiming not to get
too much into the weeds about module packages since it's something that
most users can get away with never knowing.
2021-05-21 17:28:20 -05:00
if err != nil {
t . Fatal ( err )
}
final := bytes . ReplaceAll ( template , [ ] byte ( "%%BASE%%" ) , [ ] byte ( filepath . ToSlash ( dir ) ) )
2023-09-08 03:04:34 -05:00
err = os . WriteFile ( rootFilename , final , 0644 )
initwd: Error message for local paths escaping module packages
Our module installer has a somewhat-informal idea of a "module package",
which is some external thing we can go fetch in order to add one or more
modules to the current configuration. Our documentation doesn't talk much
about it because most users seem to have found the distinction between
external and local modules pretty intuitive without us throwing a lot of
funny terminology at them, but there are some situations where the
distinction between a module and a module package are material to the
end-user.
One such situation is when using an absolute rather than relative
filesystem path: we treat that as an external package in order to make the
resulting working directory theoretically "portable" (although users can
do various other things to defeat that), and so Terraform will copy the
directory into .terraform/modules in the same way as it would download and
extract a remote archive package or clone a git repository.
A consequence of this, though, is that any relative paths called from
inside a module loaded from an absolute path will fail if they try to
traverse upward into the parent directory, because at runtime we're
actually running from a copy of the directory that's been taking out of
its original context.
A similar sort of situation can occur in a truly remote module package if
the author accidentally writes a "../" source path that traverses up out
of the package root, and so this commit introduces a special error message
for both situations that tries to be a bit clearer about there being a
package boundary and use that to explain why installation failed.
We would ideally have made escaping local references like that illegal in
the first place, but sadly we did not and so when we rebuilt the module
installer for Terraform v0.12 we ended up keeping the previous behavior of
just trying it and letting it succeed if there happened to somehow be a
matching directory at the given path, in order to remain compatible with
situations that had worked by coincidence rather than intention. For that
same reason, I've implemented this as a replacement error message we will
return only if local module installation was going to fail anyway, and
thus it only modifies the error message for some existing error situations
rather than introducing new error situations.
This also includes some light updates to the documentation to say a little
more about how Terraform treats absolute paths, though aiming not to get
too much into the weeds about module packages since it's something that
most users can get away with never knowing.
2021-05-21 17:28:20 -05:00
if err != nil {
t . Fatal ( err )
}
}
hooks := & testInstallHooks { }
modulesDir := filepath . Join ( dir , ".terraform/modules" )
2023-02-17 05:40:47 -06:00
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , nil )
2023-08-07 13:54:28 -05:00
_ , diags := inst . InstallModules ( context . Background ( ) , "." , "tests" , false , false , hooks )
initwd: Error message for local paths escaping module packages
Our module installer has a somewhat-informal idea of a "module package",
which is some external thing we can go fetch in order to add one or more
modules to the current configuration. Our documentation doesn't talk much
about it because most users seem to have found the distinction between
external and local modules pretty intuitive without us throwing a lot of
funny terminology at them, but there are some situations where the
distinction between a module and a module package are material to the
end-user.
One such situation is when using an absolute rather than relative
filesystem path: we treat that as an external package in order to make the
resulting working directory theoretically "portable" (although users can
do various other things to defeat that), and so Terraform will copy the
directory into .terraform/modules in the same way as it would download and
extract a remote archive package or clone a git repository.
A consequence of this, though, is that any relative paths called from
inside a module loaded from an absolute path will fail if they try to
traverse upward into the parent directory, because at runtime we're
actually running from a copy of the directory that's been taking out of
its original context.
A similar sort of situation can occur in a truly remote module package if
the author accidentally writes a "../" source path that traverses up out
of the package root, and so this commit introduces a special error message
for both situations that tries to be a bit clearer about there being a
package boundary and use that to explain why installation failed.
We would ideally have made escaping local references like that illegal in
the first place, but sadly we did not and so when we rebuilt the module
installer for Terraform v0.12 we ended up keeping the previous behavior of
just trying it and letting it succeed if there happened to somehow be a
matching directory at the given path, in order to remain compatible with
situations that had worked by coincidence rather than intention. For that
same reason, I've implemented this as a replacement error message we will
return only if local module installation was going to fail anyway, and
thus it only modifies the error message for some existing error situations
rather than introducing new error situations.
This also includes some light updates to the documentation to say a little
more about how Terraform treats absolute paths, though aiming not to get
too much into the weeds about module packages since it's something that
most users can get away with never knowing.
2021-05-21 17:28:20 -05:00
if ! diags . HasErrors ( ) {
t . Fatal ( "expected error" )
} else {
assertDiagnosticSummary ( t , diags , "Local module path escapes module package" )
}
}
func TestModuleInstaller_explicitPackageBoundary ( t * testing . T ) {
fixtureDir := filepath . Clean ( "testdata/load-module-package-prefix" )
dir , done := tempChdir ( t , fixtureDir )
defer done ( )
// For this particular test we need an absolute path in the root module
// that must actually resolve to our temporary directory in "dir", so
// we need to do a little rewriting. We replace the arbitrary placeholder
// %%BASE%% with the temporary directory path.
{
rootFilename := filepath . Join ( dir , "package-prefix.tf" )
2023-09-08 03:04:34 -05:00
template , err := os . ReadFile ( rootFilename )
initwd: Error message for local paths escaping module packages
Our module installer has a somewhat-informal idea of a "module package",
which is some external thing we can go fetch in order to add one or more
modules to the current configuration. Our documentation doesn't talk much
about it because most users seem to have found the distinction between
external and local modules pretty intuitive without us throwing a lot of
funny terminology at them, but there are some situations where the
distinction between a module and a module package are material to the
end-user.
One such situation is when using an absolute rather than relative
filesystem path: we treat that as an external package in order to make the
resulting working directory theoretically "portable" (although users can
do various other things to defeat that), and so Terraform will copy the
directory into .terraform/modules in the same way as it would download and
extract a remote archive package or clone a git repository.
A consequence of this, though, is that any relative paths called from
inside a module loaded from an absolute path will fail if they try to
traverse upward into the parent directory, because at runtime we're
actually running from a copy of the directory that's been taking out of
its original context.
A similar sort of situation can occur in a truly remote module package if
the author accidentally writes a "../" source path that traverses up out
of the package root, and so this commit introduces a special error message
for both situations that tries to be a bit clearer about there being a
package boundary and use that to explain why installation failed.
We would ideally have made escaping local references like that illegal in
the first place, but sadly we did not and so when we rebuilt the module
installer for Terraform v0.12 we ended up keeping the previous behavior of
just trying it and letting it succeed if there happened to somehow be a
matching directory at the given path, in order to remain compatible with
situations that had worked by coincidence rather than intention. For that
same reason, I've implemented this as a replacement error message we will
return only if local module installation was going to fail anyway, and
thus it only modifies the error message for some existing error situations
rather than introducing new error situations.
This also includes some light updates to the documentation to say a little
more about how Terraform treats absolute paths, though aiming not to get
too much into the weeds about module packages since it's something that
most users can get away with never knowing.
2021-05-21 17:28:20 -05:00
if err != nil {
t . Fatal ( err )
}
final := bytes . ReplaceAll ( template , [ ] byte ( "%%BASE%%" ) , [ ] byte ( filepath . ToSlash ( dir ) ) )
2023-09-08 03:04:34 -05:00
err = os . WriteFile ( rootFilename , final , 0644 )
initwd: Error message for local paths escaping module packages
Our module installer has a somewhat-informal idea of a "module package",
which is some external thing we can go fetch in order to add one or more
modules to the current configuration. Our documentation doesn't talk much
about it because most users seem to have found the distinction between
external and local modules pretty intuitive without us throwing a lot of
funny terminology at them, but there are some situations where the
distinction between a module and a module package are material to the
end-user.
One such situation is when using an absolute rather than relative
filesystem path: we treat that as an external package in order to make the
resulting working directory theoretically "portable" (although users can
do various other things to defeat that), and so Terraform will copy the
directory into .terraform/modules in the same way as it would download and
extract a remote archive package or clone a git repository.
A consequence of this, though, is that any relative paths called from
inside a module loaded from an absolute path will fail if they try to
traverse upward into the parent directory, because at runtime we're
actually running from a copy of the directory that's been taking out of
its original context.
A similar sort of situation can occur in a truly remote module package if
the author accidentally writes a "../" source path that traverses up out
of the package root, and so this commit introduces a special error message
for both situations that tries to be a bit clearer about there being a
package boundary and use that to explain why installation failed.
We would ideally have made escaping local references like that illegal in
the first place, but sadly we did not and so when we rebuilt the module
installer for Terraform v0.12 we ended up keeping the previous behavior of
just trying it and letting it succeed if there happened to somehow be a
matching directory at the given path, in order to remain compatible with
situations that had worked by coincidence rather than intention. For that
same reason, I've implemented this as a replacement error message we will
return only if local module installation was going to fail anyway, and
thus it only modifies the error message for some existing error situations
rather than introducing new error situations.
This also includes some light updates to the documentation to say a little
more about how Terraform treats absolute paths, though aiming not to get
too much into the weeds about module packages since it's something that
most users can get away with never knowing.
2021-05-21 17:28:20 -05:00
if err != nil {
t . Fatal ( err )
}
}
hooks := & testInstallHooks { }
modulesDir := filepath . Join ( dir , ".terraform/modules" )
2023-02-17 05:40:47 -06:00
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , nil )
2023-08-07 13:54:28 -05:00
_ , diags := inst . InstallModules ( context . Background ( ) , "." , "tests" , false , false , hooks )
initwd: Error message for local paths escaping module packages
Our module installer has a somewhat-informal idea of a "module package",
which is some external thing we can go fetch in order to add one or more
modules to the current configuration. Our documentation doesn't talk much
about it because most users seem to have found the distinction between
external and local modules pretty intuitive without us throwing a lot of
funny terminology at them, but there are some situations where the
distinction between a module and a module package are material to the
end-user.
One such situation is when using an absolute rather than relative
filesystem path: we treat that as an external package in order to make the
resulting working directory theoretically "portable" (although users can
do various other things to defeat that), and so Terraform will copy the
directory into .terraform/modules in the same way as it would download and
extract a remote archive package or clone a git repository.
A consequence of this, though, is that any relative paths called from
inside a module loaded from an absolute path will fail if they try to
traverse upward into the parent directory, because at runtime we're
actually running from a copy of the directory that's been taking out of
its original context.
A similar sort of situation can occur in a truly remote module package if
the author accidentally writes a "../" source path that traverses up out
of the package root, and so this commit introduces a special error message
for both situations that tries to be a bit clearer about there being a
package boundary and use that to explain why installation failed.
We would ideally have made escaping local references like that illegal in
the first place, but sadly we did not and so when we rebuilt the module
installer for Terraform v0.12 we ended up keeping the previous behavior of
just trying it and letting it succeed if there happened to somehow be a
matching directory at the given path, in order to remain compatible with
situations that had worked by coincidence rather than intention. For that
same reason, I've implemented this as a replacement error message we will
return only if local module installation was going to fail anyway, and
thus it only modifies the error message for some existing error situations
rather than introducing new error situations.
This also includes some light updates to the documentation to say a little
more about how Terraform treats absolute paths, though aiming not to get
too much into the weeds about module packages since it's something that
most users can get away with never knowing.
2021-05-21 17:28:20 -05:00
if diags . HasErrors ( ) {
t . Fatalf ( "unexpected errors\n%s" , diags . Err ( ) . Error ( ) )
}
}
2022-12-14 10:02:11 -06:00
func TestModuleInstaller_ExactMatchPrerelease ( t * testing . T ) {
if os . Getenv ( "TF_ACC" ) == "" {
2023-10-03 02:49:38 -05:00
t . Skip ( "this test accesses registry.opentofu.org and github.com; set TF_ACC=1 to run it" )
2022-12-14 10:02:11 -06:00
}
fixtureDir := filepath . Clean ( "testdata/prerelease-version-constraint-match" )
dir , done := tempChdir ( t , fixtureDir )
defer done ( )
hooks := & testInstallHooks { }
modulesDir := filepath . Join ( dir , ".terraform/modules" )
2023-02-17 05:40:47 -06:00
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , registry . NewClient ( nil , nil ) )
2023-08-07 13:54:28 -05:00
cfg , diags := inst . InstallModules ( context . Background ( ) , "." , "tests" , false , false , hooks )
2022-12-14 10:02:11 -06:00
if diags . HasErrors ( ) {
t . Fatalf ( "found unexpected errors: %s" , diags . Err ( ) )
}
if ! cfg . Children [ "acctest_exact" ] . Version . Equal ( version . Must ( version . NewVersion ( "v0.0.3-alpha.1" ) ) ) {
t . Fatalf ( "expected version %s but found version %s" , "v0.0.3-alpha.1" , cfg . Version . String ( ) )
}
}
func TestModuleInstaller_PartialMatchPrerelease ( t * testing . T ) {
if os . Getenv ( "TF_ACC" ) == "" {
2023-10-03 02:49:38 -05:00
t . Skip ( "this test accesses registry.opentofu.org and github.com; set TF_ACC=1 to run it" )
2022-12-14 10:02:11 -06:00
}
fixtureDir := filepath . Clean ( "testdata/prerelease-version-constraint" )
dir , done := tempChdir ( t , fixtureDir )
defer done ( )
hooks := & testInstallHooks { }
modulesDir := filepath . Join ( dir , ".terraform/modules" )
2023-02-17 05:40:47 -06:00
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , registry . NewClient ( nil , nil ) )
2023-08-07 13:54:28 -05:00
cfg , diags := inst . InstallModules ( context . Background ( ) , "." , "tests" , false , false , hooks )
2022-12-14 10:02:11 -06:00
if diags . HasErrors ( ) {
t . Fatalf ( "found unexpected errors: %s" , diags . Err ( ) )
}
if ! cfg . Children [ "acctest_partial" ] . Version . Equal ( version . Must ( version . NewVersion ( "v0.0.2" ) ) ) {
t . Fatalf ( "expected version %s but found version %s" , "v0.0.2" , cfg . Version . String ( ) )
}
}
2019-05-17 15:19:31 -05:00
func TestModuleInstaller_invalid_version_constraint_error ( t * testing . T ) {
2019-06-30 02:38:36 -05:00
fixtureDir := filepath . Clean ( "testdata/invalid-version-constraint" )
2019-05-17 15:19:31 -05:00
dir , done := tempChdir ( t , fixtureDir )
defer done ( )
hooks := & testInstallHooks { }
modulesDir := filepath . Join ( dir , ".terraform/modules" )
2023-02-17 05:40:47 -06:00
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , nil )
2023-08-07 13:54:28 -05:00
_ , diags := inst . InstallModules ( context . Background ( ) , "." , "tests" , false , false , hooks )
2019-05-31 09:57:42 -05:00
if ! diags . HasErrors ( ) {
t . Fatal ( "expected error" )
} else {
2021-11-30 16:30:44 -06:00
// We use the presence of the "version" argument as a heuristic for
// user intent to use a registry module, and so we intentionally catch
// this as an invalid registry module address rather than an invalid
// version constraint, so we can surface the specific address parsing
// error instead of a generic version constraint error.
assertDiagnosticSummary ( t , diags , "Invalid registry module source address" )
2019-05-31 09:57:42 -05:00
}
}
func TestModuleInstaller_invalidVersionConstraintGetter ( t * testing . T ) {
2019-06-30 02:38:36 -05:00
fixtureDir := filepath . Clean ( "testdata/invalid-version-constraint" )
2019-05-31 09:57:42 -05:00
dir , done := tempChdir ( t , fixtureDir )
defer done ( )
hooks := & testInstallHooks { }
modulesDir := filepath . Join ( dir , ".terraform/modules" )
2023-02-17 05:40:47 -06:00
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , nil )
2023-08-07 13:54:28 -05:00
_ , diags := inst . InstallModules ( context . Background ( ) , "." , "tests" , false , false , hooks )
2019-05-31 09:57:42 -05:00
if ! diags . HasErrors ( ) {
t . Fatal ( "expected error" )
} else {
2021-11-30 16:30:44 -06:00
// We use the presence of the "version" argument as a heuristic for
// user intent to use a registry module, and so we intentionally catch
// this as an invalid registry module address rather than an invalid
// version constraint, so we can surface the specific address parsing
// error instead of a generic version constraint error.
assertDiagnosticSummary ( t , diags , "Invalid registry module source address" )
2019-05-31 09:57:42 -05:00
}
}
func TestModuleInstaller_invalidVersionConstraintLocal ( t * testing . T ) {
2019-06-30 02:38:36 -05:00
fixtureDir := filepath . Clean ( "testdata/invalid-version-constraint-local" )
2019-05-31 09:57:42 -05:00
dir , done := tempChdir ( t , fixtureDir )
defer done ( )
hooks := & testInstallHooks { }
modulesDir := filepath . Join ( dir , ".terraform/modules" )
2023-02-17 05:40:47 -06:00
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , nil )
2023-08-07 13:54:28 -05:00
_ , diags := inst . InstallModules ( context . Background ( ) , "." , "tests" , false , false , hooks )
2019-05-17 15:19:31 -05:00
if ! diags . HasErrors ( ) {
t . Fatal ( "expected error" )
} else {
2021-11-30 16:30:44 -06:00
// We use the presence of the "version" argument as a heuristic for
// user intent to use a registry module, and so we intentionally catch
// this as an invalid registry module address rather than an invalid
// version constraint, so we can surface the specific address parsing
// error instead of a generic version constraint error.
assertDiagnosticSummary ( t , diags , "Invalid registry module source address" )
2019-05-17 15:19:31 -05:00
}
}
2019-04-24 07:19:27 -05:00
func TestModuleInstaller_symlink ( t * testing . T ) {
2019-06-30 02:38:36 -05:00
fixtureDir := filepath . Clean ( "testdata/local-module-symlink" )
2019-04-24 07:19:27 -05:00
dir , done := tempChdir ( t , fixtureDir )
defer done ( )
hooks := & testInstallHooks { }
modulesDir := filepath . Join ( dir , ".terraform/modules" )
2023-02-17 05:40:47 -06:00
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , nil )
2023-08-07 13:54:28 -05:00
_ , diags := inst . InstallModules ( context . Background ( ) , "." , "tests" , false , false , hooks )
2019-04-24 07:19:27 -05:00
assertNoDiagnostics ( t , diags )
wantCalls := [ ] testInstallHookCall {
{
Name : "Install" ,
ModuleAddr : "child_a" ,
PackageAddr : "" ,
LocalPath : "child_a" ,
} ,
{
Name : "Install" ,
ModuleAddr : "child_a.child_b" ,
PackageAddr : "" ,
LocalPath : "child_a/child_b" ,
} ,
}
if assertResultDeepEqual ( t , hooks . Calls , wantCalls ) {
return
}
loader , err := configload . NewLoader ( & configload . Config {
ModulesDir : modulesDir ,
} )
if err != nil {
t . Fatal ( err )
}
// Make sure the configuration is loadable now.
// (This ensures that correct information is recorded in the manifest.)
config , loadDiags := loader . LoadConfig ( "." )
assertNoDiagnostics ( t , tfdiags . Diagnostics { } . Append ( loadDiags ) )
wantTraces := map [ string ] string {
"" : "in root module" ,
"child_a" : "in child_a module" ,
"child_a.child_b" : "in child_b module" ,
}
gotTraces := map [ string ] string { }
config . DeepEach ( func ( c * configs . Config ) {
path := strings . Join ( c . Path , "." )
if c . Module . Variables [ "v" ] == nil {
gotTraces [ path ] = "<missing>"
return
}
varDesc := c . Module . Variables [ "v" ] . Description
gotTraces [ path ] = varDesc
} )
assertResultDeepEqual ( t , gotTraces , wantTraces )
}
2018-02-13 18:36:36 -06:00
func TestLoaderInstallModules_registry ( t * testing . T ) {
if os . Getenv ( "TF_ACC" ) == "" {
2023-10-03 02:49:38 -05:00
t . Skip ( "this test accesses registry.opentofu.org and github.com; set TF_ACC=1 to run it" )
2018-02-13 18:36:36 -06:00
}
2019-06-30 02:38:36 -05:00
fixtureDir := filepath . Clean ( "testdata/registry-modules" )
2019-09-25 15:27:17 -05:00
tmpDir , done := tempChdir ( t , fixtureDir )
2019-09-26 10:30:52 -05:00
// the module installer runs filepath.EvalSymlinks() on the destination
// directory before copying files, and the resultant directory is what is
// returned by the install hooks. Without this, tests could fail on machines
// where the default temp dir was a symlink.
2019-09-25 15:27:17 -05:00
dir , err := filepath . EvalSymlinks ( tmpDir )
if err != nil {
t . Error ( err )
}
2018-02-13 18:36:36 -06:00
defer done ( )
hooks := & testInstallHooks { }
2019-01-08 20:39:14 -06:00
modulesDir := filepath . Join ( dir , ".terraform/modules" )
2023-02-17 05:40:47 -06:00
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , registry . NewClient ( nil , nil ) )
2023-08-07 13:54:28 -05:00
_ , diags := inst . InstallModules ( context . Background ( ) , dir , "tests" , false , false , hooks )
2018-02-13 18:36:36 -06:00
assertNoDiagnostics ( t , diags )
v := version . Must ( version . NewVersion ( "0.0.1" ) )
wantCalls := [ ] testInstallHookCall {
// the configuration builder visits each level of calls in lexicographical
// order by name, so the following list is kept in the same order.
// acctest_child_a accesses //modules/child_a directly
{
Name : "Download" ,
ModuleAddr : "acctest_child_a" ,
2023-10-03 02:49:38 -05:00
PackageAddr : "registry.opentofu.org/hashicorp/module-installer-acctest/aws" , // intentionally excludes the subdir because we're downloading the whole package here
2018-02-13 18:36:36 -06:00
Version : v ,
} ,
{
Name : "Install" ,
ModuleAddr : "acctest_child_a" ,
Version : v ,
2021-06-02 13:38:53 -05:00
// NOTE: This local path and the other paths derived from it below
// can vary depending on how the registry is implemented. At the
2023-10-03 02:49:38 -05:00
// time of writing this test, registry.opentofu.org returns
2021-06-02 13:38:53 -05:00
// git repository source addresses and so this path refers to the
// root of the git clone, but historically the registry referred
// to GitHub-provided tar archives which meant that there was an
// extra level of subdirectory here for the typical directory
// nesting in tar archives, which would've been reflected as
// an extra segment on this path. If this test fails due to an
// additional path segment in future, then a change to the upstream
// registry might be the root cause.
LocalPath : filepath . Join ( dir , ".terraform/modules/acctest_child_a/modules/child_a" ) ,
2018-02-13 18:36:36 -06:00
} ,
// acctest_child_a.child_b
// (no download because it's a relative path inside acctest_child_a)
{
Name : "Install" ,
ModuleAddr : "acctest_child_a.child_b" ,
2021-06-02 13:38:53 -05:00
LocalPath : filepath . Join ( dir , ".terraform/modules/acctest_child_a/modules/child_b" ) ,
2018-02-13 18:36:36 -06:00
} ,
// acctest_child_b accesses //modules/child_b directly
{
Name : "Download" ,
ModuleAddr : "acctest_child_b" ,
2023-10-03 02:49:38 -05:00
PackageAddr : "registry.opentofu.org/hashicorp/module-installer-acctest/aws" , // intentionally excludes the subdir because we're downloading the whole package here
2018-02-13 18:36:36 -06:00
Version : v ,
} ,
{
Name : "Install" ,
ModuleAddr : "acctest_child_b" ,
Version : v ,
2021-06-02 13:38:53 -05:00
LocalPath : filepath . Join ( dir , ".terraform/modules/acctest_child_b/modules/child_b" ) ,
2018-02-13 18:36:36 -06:00
} ,
// acctest_root
{
Name : "Download" ,
ModuleAddr : "acctest_root" ,
2023-10-03 02:49:38 -05:00
PackageAddr : "registry.opentofu.org/hashicorp/module-installer-acctest/aws" ,
2018-02-13 18:36:36 -06:00
Version : v ,
} ,
{
Name : "Install" ,
ModuleAddr : "acctest_root" ,
Version : v ,
2021-06-02 13:38:53 -05:00
LocalPath : filepath . Join ( dir , ".terraform/modules/acctest_root" ) ,
2018-02-13 18:36:36 -06:00
} ,
// acctest_root.child_a
// (no download because it's a relative path inside acctest_root)
{
Name : "Install" ,
ModuleAddr : "acctest_root.child_a" ,
2021-06-02 13:38:53 -05:00
LocalPath : filepath . Join ( dir , ".terraform/modules/acctest_root/modules/child_a" ) ,
2018-02-13 18:36:36 -06:00
} ,
// acctest_root.child_a.child_b
// (no download because it's a relative path inside acctest_root, via acctest_root.child_a)
{
Name : "Install" ,
ModuleAddr : "acctest_root.child_a.child_b" ,
2021-06-02 13:38:53 -05:00
LocalPath : filepath . Join ( dir , ".terraform/modules/acctest_root/modules/child_b" ) ,
2018-02-13 18:36:36 -06:00
} ,
}
Refactoring of module source addresses and module installation
It's been a long while since we gave close attention to the codepaths for
module source address parsing and external module package installation.
Due to their age, these codepaths often diverged from our modern practices
such as representing address types in the addrs package, and encapsulating
package installation details only in a particular location.
In particular, this refactor makes source address parsing a separate step
from module installation, which therefore makes the result of that parsing
available to other Terraform subsystems which work with the configuration
representation objects.
This also presented the opportunity to better encapsulate our use of
go-getter into a new package "getmodules" (echoing "getproviders"), which
is intended to be the only part of Terraform that directly interacts with
go-getter.
This is largely just a refactor of the existing functionality into a new
code organization, but there is one notable change in behavior here: the
source address parsing now happens during configuration loading rather
than module installation, which may cause errors about invalid addresses
to be returned in different situations than before. That counts as
backward compatible because we only promise to remain compatible with
configurations that are _valid_, which means that they can be initialized,
planned, and applied without any errors. This doesn't introduce any new
error cases, and instead just makes a pre-existing error case be detected
earlier.
Our module registry client is still using its own special module address
type from registry/regsrc for now, with a small shim from the new
addrs.ModuleSourceRegistry type. Hopefully in a later commit we'll also
rework the registry client to work with the new address type, but this
commit is already big enough as it is.
2021-05-27 21:24:59 -05:00
if diff := cmp . Diff ( wantCalls , hooks . Calls ) ; diff != "" {
t . Fatalf ( "wrong installer calls\n%s" , diff )
2018-02-13 18:36:36 -06:00
}
2020-01-07 14:03:23 -06:00
//check that the registry reponses were cached
2021-06-02 14:26:35 -05:00
packageAddr := addrs . ModuleRegistryPackage {
2023-10-03 02:49:38 -05:00
Host : svchost . Hostname ( "registry.opentofu.org" ) ,
2021-06-02 14:26:35 -05:00
Namespace : "hashicorp" ,
Name : "module-installer-acctest" ,
TargetSystem : "aws" ,
2020-01-07 14:03:23 -06:00
}
2021-06-02 14:26:35 -05:00
if _ , ok := inst . registryPackageVersions [ packageAddr ] ; ! ok {
t . Errorf ( "module versions cache was not populated\ngot: %s\nwant: key hashicorp/module-installer-acctest/aws" , spew . Sdump ( inst . registryPackageVersions ) )
}
if _ , ok := inst . registryPackageSources [ moduleVersion { module : packageAddr , version : "0.0.1" } ] ; ! ok {
t . Errorf ( "module download url cache was not populated\ngot: %s" , spew . Sdump ( inst . registryPackageSources ) )
2020-01-07 14:03:23 -06:00
}
2023-02-17 05:40:47 -06:00
loader , err = configload . NewLoader ( & configload . Config {
2019-01-08 20:39:14 -06:00
ModulesDir : modulesDir ,
} )
if err != nil {
t . Fatal ( err )
}
2018-02-13 18:36:36 -06:00
// Make sure the configuration is loadable now.
// (This ensures that correct information is recorded in the manifest.)
2018-02-15 11:23:07 -06:00
config , loadDiags := loader . LoadConfig ( "." )
2019-01-08 20:39:14 -06:00
assertNoDiagnostics ( t , tfdiags . Diagnostics { } . Append ( loadDiags ) )
2018-02-15 11:23:07 -06:00
wantTraces := map [ string ] string {
"" : "in local caller for registry-modules" ,
"acctest_root" : "in root module" ,
"acctest_root.child_a" : "in child_a module" ,
"acctest_root.child_a.child_b" : "in child_b module" ,
"acctest_child_a" : "in child_a module" ,
"acctest_child_a.child_b" : "in child_b module" ,
"acctest_child_b" : "in child_b module" ,
}
gotTraces := map [ string ] string { }
config . DeepEach ( func ( c * configs . Config ) {
path := strings . Join ( c . Path , "." )
if c . Module . Variables [ "v" ] == nil {
gotTraces [ path ] = "<missing>"
return
}
varDesc := c . Module . Variables [ "v" ] . Description
gotTraces [ path ] = varDesc
} )
assertResultDeepEqual ( t , gotTraces , wantTraces )
2018-02-13 18:36:36 -06:00
}
func TestLoaderInstallModules_goGetter ( t * testing . T ) {
if os . Getenv ( "TF_ACC" ) == "" {
t . Skip ( "this test accesses github.com; set TF_ACC=1 to run it" )
}
2019-06-30 02:38:36 -05:00
fixtureDir := filepath . Clean ( "testdata/go-getter-modules" )
2019-09-25 15:27:17 -05:00
tmpDir , done := tempChdir ( t , fixtureDir )
2019-09-26 10:30:52 -05:00
// the module installer runs filepath.EvalSymlinks() on the destination
// directory before copying files, and the resultant directory is what is
// returned by the install hooks. Without this, tests could fail on machines
// where the default temp dir was a symlink.
2019-09-25 15:27:17 -05:00
dir , err := filepath . EvalSymlinks ( tmpDir )
if err != nil {
t . Error ( err )
}
2018-02-13 18:36:36 -06:00
defer done ( )
hooks := & testInstallHooks { }
2019-01-08 20:39:14 -06:00
modulesDir := filepath . Join ( dir , ".terraform/modules" )
2023-02-17 05:40:47 -06:00
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , registry . NewClient ( nil , nil ) )
2023-08-07 13:54:28 -05:00
_ , diags := inst . InstallModules ( context . Background ( ) , dir , "tests" , false , false , hooks )
2018-02-13 18:36:36 -06:00
assertNoDiagnostics ( t , diags )
wantCalls := [ ] testInstallHookCall {
// the configuration builder visits each level of calls in lexicographical
// order by name, so the following list is kept in the same order.
// acctest_child_a accesses //modules/child_a directly
{
Name : "Download" ,
ModuleAddr : "acctest_child_a" ,
Refactoring of module source addresses and module installation
It's been a long while since we gave close attention to the codepaths for
module source address parsing and external module package installation.
Due to their age, these codepaths often diverged from our modern practices
such as representing address types in the addrs package, and encapsulating
package installation details only in a particular location.
In particular, this refactor makes source address parsing a separate step
from module installation, which therefore makes the result of that parsing
available to other Terraform subsystems which work with the configuration
representation objects.
This also presented the opportunity to better encapsulate our use of
go-getter into a new package "getmodules" (echoing "getproviders"), which
is intended to be the only part of Terraform that directly interacts with
go-getter.
This is largely just a refactor of the existing functionality into a new
code organization, but there is one notable change in behavior here: the
source address parsing now happens during configuration loading rather
than module installation, which may cause errors about invalid addresses
to be returned in different situations than before. That counts as
backward compatible because we only promise to remain compatible with
configurations that are _valid_, which means that they can be initialized,
planned, and applied without any errors. This doesn't introduce any new
error cases, and instead just makes a pre-existing error case be detected
earlier.
Our module registry client is still using its own special module address
type from registry/regsrc for now, with a small shim from the new
addrs.ModuleSourceRegistry type. Hopefully in a later commit we'll also
rework the registry client to work with the new address type, but this
commit is already big enough as it is.
2021-05-27 21:24:59 -05:00
PackageAddr : "git::https://github.com/hashicorp/terraform-aws-module-installer-acctest.git?ref=v0.0.1" , // intentionally excludes the subdir because we're downloading the whole repo here
2018-02-13 18:36:36 -06:00
} ,
{
Name : "Install" ,
ModuleAddr : "acctest_child_a" ,
2019-03-11 17:25:21 -05:00
LocalPath : filepath . Join ( dir , ".terraform/modules/acctest_child_a/modules/child_a" ) ,
2018-02-13 18:36:36 -06:00
} ,
// acctest_child_a.child_b
// (no download because it's a relative path inside acctest_child_a)
{
Name : "Install" ,
ModuleAddr : "acctest_child_a.child_b" ,
2019-03-11 17:25:21 -05:00
LocalPath : filepath . Join ( dir , ".terraform/modules/acctest_child_a/modules/child_b" ) ,
2018-02-13 18:36:36 -06:00
} ,
// acctest_child_b accesses //modules/child_b directly
{
Name : "Download" ,
ModuleAddr : "acctest_child_b" ,
Refactoring of module source addresses and module installation
It's been a long while since we gave close attention to the codepaths for
module source address parsing and external module package installation.
Due to their age, these codepaths often diverged from our modern practices
such as representing address types in the addrs package, and encapsulating
package installation details only in a particular location.
In particular, this refactor makes source address parsing a separate step
from module installation, which therefore makes the result of that parsing
available to other Terraform subsystems which work with the configuration
representation objects.
This also presented the opportunity to better encapsulate our use of
go-getter into a new package "getmodules" (echoing "getproviders"), which
is intended to be the only part of Terraform that directly interacts with
go-getter.
This is largely just a refactor of the existing functionality into a new
code organization, but there is one notable change in behavior here: the
source address parsing now happens during configuration loading rather
than module installation, which may cause errors about invalid addresses
to be returned in different situations than before. That counts as
backward compatible because we only promise to remain compatible with
configurations that are _valid_, which means that they can be initialized,
planned, and applied without any errors. This doesn't introduce any new
error cases, and instead just makes a pre-existing error case be detected
earlier.
Our module registry client is still using its own special module address
type from registry/regsrc for now, with a small shim from the new
addrs.ModuleSourceRegistry type. Hopefully in a later commit we'll also
rework the registry client to work with the new address type, but this
commit is already big enough as it is.
2021-05-27 21:24:59 -05:00
PackageAddr : "git::https://github.com/hashicorp/terraform-aws-module-installer-acctest.git?ref=v0.0.1" , // intentionally excludes the subdir because we're downloading the whole package here
2018-02-13 18:36:36 -06:00
} ,
{
Name : "Install" ,
ModuleAddr : "acctest_child_b" ,
2019-03-11 17:25:21 -05:00
LocalPath : filepath . Join ( dir , ".terraform/modules/acctest_child_b/modules/child_b" ) ,
2018-02-13 18:36:36 -06:00
} ,
// acctest_root
{
Name : "Download" ,
ModuleAddr : "acctest_root" ,
Refactoring of module source addresses and module installation
It's been a long while since we gave close attention to the codepaths for
module source address parsing and external module package installation.
Due to their age, these codepaths often diverged from our modern practices
such as representing address types in the addrs package, and encapsulating
package installation details only in a particular location.
In particular, this refactor makes source address parsing a separate step
from module installation, which therefore makes the result of that parsing
available to other Terraform subsystems which work with the configuration
representation objects.
This also presented the opportunity to better encapsulate our use of
go-getter into a new package "getmodules" (echoing "getproviders"), which
is intended to be the only part of Terraform that directly interacts with
go-getter.
This is largely just a refactor of the existing functionality into a new
code organization, but there is one notable change in behavior here: the
source address parsing now happens during configuration loading rather
than module installation, which may cause errors about invalid addresses
to be returned in different situations than before. That counts as
backward compatible because we only promise to remain compatible with
configurations that are _valid_, which means that they can be initialized,
planned, and applied without any errors. This doesn't introduce any new
error cases, and instead just makes a pre-existing error case be detected
earlier.
Our module registry client is still using its own special module address
type from registry/regsrc for now, with a small shim from the new
addrs.ModuleSourceRegistry type. Hopefully in a later commit we'll also
rework the registry client to work with the new address type, but this
commit is already big enough as it is.
2021-05-27 21:24:59 -05:00
PackageAddr : "git::https://github.com/hashicorp/terraform-aws-module-installer-acctest.git?ref=v0.0.1" ,
2018-02-13 18:36:36 -06:00
} ,
{
Name : "Install" ,
ModuleAddr : "acctest_root" ,
2019-03-11 17:25:21 -05:00
LocalPath : filepath . Join ( dir , ".terraform/modules/acctest_root" ) ,
2018-02-13 18:36:36 -06:00
} ,
// acctest_root.child_a
// (no download because it's a relative path inside acctest_root)
{
Name : "Install" ,
ModuleAddr : "acctest_root.child_a" ,
2019-03-11 17:25:21 -05:00
LocalPath : filepath . Join ( dir , ".terraform/modules/acctest_root/modules/child_a" ) ,
2018-02-13 18:36:36 -06:00
} ,
// acctest_root.child_a.child_b
// (no download because it's a relative path inside acctest_root, via acctest_root.child_a)
{
Name : "Install" ,
ModuleAddr : "acctest_root.child_a.child_b" ,
2019-03-11 17:25:21 -05:00
LocalPath : filepath . Join ( dir , ".terraform/modules/acctest_root/modules/child_b" ) ,
2018-02-13 18:36:36 -06:00
} ,
}
Refactoring of module source addresses and module installation
It's been a long while since we gave close attention to the codepaths for
module source address parsing and external module package installation.
Due to their age, these codepaths often diverged from our modern practices
such as representing address types in the addrs package, and encapsulating
package installation details only in a particular location.
In particular, this refactor makes source address parsing a separate step
from module installation, which therefore makes the result of that parsing
available to other Terraform subsystems which work with the configuration
representation objects.
This also presented the opportunity to better encapsulate our use of
go-getter into a new package "getmodules" (echoing "getproviders"), which
is intended to be the only part of Terraform that directly interacts with
go-getter.
This is largely just a refactor of the existing functionality into a new
code organization, but there is one notable change in behavior here: the
source address parsing now happens during configuration loading rather
than module installation, which may cause errors about invalid addresses
to be returned in different situations than before. That counts as
backward compatible because we only promise to remain compatible with
configurations that are _valid_, which means that they can be initialized,
planned, and applied without any errors. This doesn't introduce any new
error cases, and instead just makes a pre-existing error case be detected
earlier.
Our module registry client is still using its own special module address
type from registry/regsrc for now, with a small shim from the new
addrs.ModuleSourceRegistry type. Hopefully in a later commit we'll also
rework the registry client to work with the new address type, but this
commit is already big enough as it is.
2021-05-27 21:24:59 -05:00
if diff := cmp . Diff ( wantCalls , hooks . Calls ) ; diff != "" {
t . Fatalf ( "wrong installer calls\n%s" , diff )
2018-02-13 18:36:36 -06:00
}
2023-02-17 05:40:47 -06:00
loader , err = configload . NewLoader ( & configload . Config {
2019-01-08 20:39:14 -06:00
ModulesDir : modulesDir ,
} )
if err != nil {
t . Fatal ( err )
}
2018-02-13 18:36:36 -06:00
// Make sure the configuration is loadable now.
// (This ensures that correct information is recorded in the manifest.)
2018-02-15 11:23:07 -06:00
config , loadDiags := loader . LoadConfig ( "." )
2019-01-08 20:39:14 -06:00
assertNoDiagnostics ( t , tfdiags . Diagnostics { } . Append ( loadDiags ) )
2018-02-15 11:23:07 -06:00
wantTraces := map [ string ] string {
"" : "in local caller for go-getter-modules" ,
"acctest_root" : "in root module" ,
"acctest_root.child_a" : "in child_a module" ,
"acctest_root.child_a.child_b" : "in child_b module" ,
"acctest_child_a" : "in child_a module" ,
"acctest_child_a.child_b" : "in child_b module" ,
"acctest_child_b" : "in child_b module" ,
}
gotTraces := map [ string ] string { }
config . DeepEach ( func ( c * configs . Config ) {
path := strings . Join ( c . Path , "." )
if c . Module . Variables [ "v" ] == nil {
gotTraces [ path ] = "<missing>"
return
}
varDesc := c . Module . Variables [ "v" ] . Description
gotTraces [ path ] = varDesc
} )
assertResultDeepEqual ( t , gotTraces , wantTraces )
2018-02-13 16:40:53 -06:00
}
2023-07-10 05:42:05 -05:00
func TestModuleInstaller_fromTests ( t * testing . T ) {
fixtureDir := filepath . Clean ( "testdata/local-module-from-test" )
dir , done := tempChdir ( t , fixtureDir )
defer done ( )
hooks := & testInstallHooks { }
modulesDir := filepath . Join ( dir , ".terraform/modules" )
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , nil )
2023-08-07 13:54:28 -05:00
_ , diags := inst . InstallModules ( context . Background ( ) , "." , "tests" , false , false , hooks )
2023-07-10 05:42:05 -05:00
assertNoDiagnostics ( t , diags )
wantCalls := [ ] testInstallHookCall {
{
Name : "Install" ,
ModuleAddr : "test.tests.main.setup" ,
PackageAddr : "" ,
LocalPath : "setup" ,
} ,
}
if assertResultDeepEqual ( t , hooks . Calls , wantCalls ) {
return
}
loader , err := configload . NewLoader ( & configload . Config {
ModulesDir : modulesDir ,
} )
if err != nil {
t . Fatal ( err )
}
// Make sure the configuration is loadable now.
// (This ensures that correct information is recorded in the manifest.)
config , loadDiags := loader . LoadConfigWithTests ( "." , "tests" )
assertNoDiagnostics ( t , tfdiags . Diagnostics { } . Append ( loadDiags ) )
2023-07-20 09:57:05 -05:00
if config . Module . Tests [ "tests/main.tftest.hcl" ] . Runs [ 0 ] . ConfigUnderTest == nil {
2023-07-10 05:42:05 -05:00
t . Fatalf ( "should have loaded config into the relevant run block but did not" )
}
}
func TestLoadInstallModules_registryFromTest ( t * testing . T ) {
if os . Getenv ( "TF_ACC" ) == "" {
2023-10-03 02:49:38 -05:00
t . Skip ( "this test accesses registry.opentofu.org and github.com; set TF_ACC=1 to run it" )
2023-07-10 05:42:05 -05:00
}
fixtureDir := filepath . Clean ( "testdata/registry-module-from-test" )
tmpDir , done := tempChdir ( t , fixtureDir )
// the module installer runs filepath.EvalSymlinks() on the destination
// directory before copying files, and the resultant directory is what is
// returned by the install hooks. Without this, tests could fail on machines
// where the default temp dir was a symlink.
dir , err := filepath . EvalSymlinks ( tmpDir )
if err != nil {
t . Error ( err )
}
defer done ( )
hooks := & testInstallHooks { }
modulesDir := filepath . Join ( dir , ".terraform/modules" )
loader , close := configload . NewLoaderForTests ( t )
defer close ( )
inst := NewModuleInstaller ( modulesDir , loader , registry . NewClient ( nil , nil ) )
2023-08-07 13:54:28 -05:00
_ , diags := inst . InstallModules ( context . Background ( ) , dir , "tests" , false , false , hooks )
2023-07-10 05:42:05 -05:00
assertNoDiagnostics ( t , diags )
v := version . Must ( version . NewVersion ( "0.0.1" ) )
wantCalls := [ ] testInstallHookCall {
// the configuration builder visits each level of calls in lexicographical
// order by name, so the following list is kept in the same order.
// setup access acctest directly.
{
Name : "Download" ,
ModuleAddr : "test.main.setup" ,
2023-10-03 02:49:38 -05:00
PackageAddr : "registry.opentofu.org/hashicorp/module-installer-acctest/aws" , // intentionally excludes the subdir because we're downloading the whole package here
2023-07-10 05:42:05 -05:00
Version : v ,
} ,
{
Name : "Install" ,
ModuleAddr : "test.main.setup" ,
Version : v ,
// NOTE: This local path and the other paths derived from it below
// can vary depending on how the registry is implemented. At the
2023-10-03 02:49:38 -05:00
// time of writing this test, registry.opentofu.org returns
2023-07-10 05:42:05 -05:00
// git repository source addresses and so this path refers to the
// root of the git clone, but historically the registry referred
// to GitHub-provided tar archives which meant that there was an
// extra level of subdirectory here for the typical directory
// nesting in tar archives, which would've been reflected as
// an extra segment on this path. If this test fails due to an
// additional path segment in future, then a change to the upstream
// registry might be the root cause.
LocalPath : filepath . Join ( dir , ".terraform/modules/test.main.setup" ) ,
} ,
2023-07-20 09:57:05 -05:00
// main.tftest.hcl.setup.child_a
2023-07-10 05:42:05 -05:00
// (no download because it's a relative path inside acctest_child_a)
{
Name : "Install" ,
ModuleAddr : "test.main.setup.child_a" ,
LocalPath : filepath . Join ( dir , ".terraform/modules/test.main.setup/modules/child_a" ) ,
} ,
2023-07-20 09:57:05 -05:00
// main.tftest.hcl.setup.child_a.child_b
// (no download because it's a relative path inside main.tftest.hcl.setup.child_a)
2023-07-10 05:42:05 -05:00
{
Name : "Install" ,
ModuleAddr : "test.main.setup.child_a.child_b" ,
LocalPath : filepath . Join ( dir , ".terraform/modules/test.main.setup/modules/child_b" ) ,
} ,
}
if diff := cmp . Diff ( wantCalls , hooks . Calls ) ; diff != "" {
t . Fatalf ( "wrong installer calls\n%s" , diff )
}
//check that the registry reponses were cached
packageAddr := addrs . ModuleRegistryPackage {
2023-10-03 02:49:38 -05:00
Host : svchost . Hostname ( "registry.opentofu.org" ) ,
2023-07-10 05:42:05 -05:00
Namespace : "hashicorp" ,
Name : "module-installer-acctest" ,
TargetSystem : "aws" ,
}
if _ , ok := inst . registryPackageVersions [ packageAddr ] ; ! ok {
t . Errorf ( "module versions cache was not populated\ngot: %s\nwant: key hashicorp/module-installer-acctest/aws" , spew . Sdump ( inst . registryPackageVersions ) )
}
if _ , ok := inst . registryPackageSources [ moduleVersion { module : packageAddr , version : "0.0.1" } ] ; ! ok {
t . Errorf ( "module download url cache was not populated\ngot: %s" , spew . Sdump ( inst . registryPackageSources ) )
}
loader , err = configload . NewLoader ( & configload . Config {
ModulesDir : modulesDir ,
} )
if err != nil {
t . Fatal ( err )
}
// Make sure the configuration is loadable now.
// (This ensures that correct information is recorded in the manifest.)
config , loadDiags := loader . LoadConfigWithTests ( "." , "tests" )
assertNoDiagnostics ( t , tfdiags . Diagnostics { } . Append ( loadDiags ) )
2023-07-20 09:57:05 -05:00
if config . Module . Tests [ "main.tftest.hcl" ] . Runs [ 0 ] . ConfigUnderTest == nil {
2023-07-10 05:42:05 -05:00
t . Fatalf ( "should have loaded config into the relevant run block but did not" )
}
}
2018-02-13 16:40:53 -06:00
type testInstallHooks struct {
Calls [ ] testInstallHookCall
}
type testInstallHookCall struct {
Name string
ModuleAddr string
PackageAddr string
Version * version . Version
LocalPath string
}
func ( h * testInstallHooks ) Download ( moduleAddr , packageAddr string , version * version . Version ) {
h . Calls = append ( h . Calls , testInstallHookCall {
Name : "Download" ,
ModuleAddr : moduleAddr ,
PackageAddr : packageAddr ,
Version : version ,
} )
}
func ( h * testInstallHooks ) Install ( moduleAddr string , version * version . Version , localPath string ) {
h . Calls = append ( h . Calls , testInstallHookCall {
Name : "Install" ,
ModuleAddr : moduleAddr ,
Version : version ,
LocalPath : localPath ,
} )
}
2019-01-08 20:39:14 -06:00
// tempChdir copies the contents of the given directory to a temporary
// directory and changes the test process's current working directory to
// point to that directory. Also returned is a function that should be
// called at the end of the test (e.g. via "defer") to restore the previous
// working directory.
//
// Tests using this helper cannot safely be run in parallel with other tests.
func tempChdir ( t * testing . T , sourceDir string ) ( string , func ( ) ) {
t . Helper ( )
2023-09-08 03:04:34 -05:00
tmpDir , err := os . MkdirTemp ( "" , "terraform-configload" )
2019-01-08 20:39:14 -06:00
if err != nil {
t . Fatalf ( "failed to create temporary directory: %s" , err )
return "" , nil
}
2020-10-07 11:48:25 -05:00
if err := copy . CopyDir ( tmpDir , sourceDir ) ; err != nil {
2019-01-08 20:39:14 -06:00
t . Fatalf ( "failed to copy fixture to temporary directory: %s" , err )
return "" , nil
}
oldDir , err := os . Getwd ( )
if err != nil {
t . Fatalf ( "failed to determine current working directory: %s" , err )
return "" , nil
}
err = os . Chdir ( tmpDir )
if err != nil {
t . Fatalf ( "failed to switch to temp dir %s: %s" , tmpDir , err )
return "" , nil
}
// Most of the tests need this, so we'll make it just in case.
os . MkdirAll ( filepath . Join ( tmpDir , ".terraform/modules" ) , os . ModePerm )
t . Logf ( "tempChdir switched to %s after copying from %s" , tmpDir , sourceDir )
return tmpDir , func ( ) {
err := os . Chdir ( oldDir )
if err != nil {
2023-09-28 03:18:08 -05:00
panic ( fmt . Errorf ( "failed to restore previous working directory %s: %w" , oldDir , err ) )
2019-01-08 20:39:14 -06:00
}
if os . Getenv ( "TF_CONFIGLOAD_TEST_KEEP_TMP" ) == "" {
os . RemoveAll ( tmpDir )
}
}
}
func assertNoDiagnostics ( t * testing . T , diags tfdiags . Diagnostics ) bool {
t . Helper ( )
return assertDiagnosticCount ( t , diags , 0 )
}
func assertDiagnosticCount ( t * testing . T , diags tfdiags . Diagnostics , want int ) bool {
t . Helper ( )
2023-08-03 15:53:53 -05:00
if len ( diags ) != want {
2019-01-08 20:39:14 -06:00
t . Errorf ( "wrong number of diagnostics %d; want %d" , len ( diags ) , want )
for _ , diag := range diags {
Refactoring of module source addresses and module installation
It's been a long while since we gave close attention to the codepaths for
module source address parsing and external module package installation.
Due to their age, these codepaths often diverged from our modern practices
such as representing address types in the addrs package, and encapsulating
package installation details only in a particular location.
In particular, this refactor makes source address parsing a separate step
from module installation, which therefore makes the result of that parsing
available to other Terraform subsystems which work with the configuration
representation objects.
This also presented the opportunity to better encapsulate our use of
go-getter into a new package "getmodules" (echoing "getproviders"), which
is intended to be the only part of Terraform that directly interacts with
go-getter.
This is largely just a refactor of the existing functionality into a new
code organization, but there is one notable change in behavior here: the
source address parsing now happens during configuration loading rather
than module installation, which may cause errors about invalid addresses
to be returned in different situations than before. That counts as
backward compatible because we only promise to remain compatible with
configurations that are _valid_, which means that they can be initialized,
planned, and applied without any errors. This doesn't introduce any new
error cases, and instead just makes a pre-existing error case be detected
earlier.
Our module registry client is still using its own special module address
type from registry/regsrc for now, with a small shim from the new
addrs.ModuleSourceRegistry type. Hopefully in a later commit we'll also
rework the registry client to work with the new address type, but this
commit is already big enough as it is.
2021-05-27 21:24:59 -05:00
t . Logf ( "- %#v" , diag )
2019-01-08 20:39:14 -06:00
}
return true
}
return false
}
func assertDiagnosticSummary ( t * testing . T , diags tfdiags . Diagnostics , want string ) bool {
t . Helper ( )
for _ , diag := range diags {
if diag . Description ( ) . Summary == want {
return false
}
}
t . Errorf ( "missing diagnostic summary %q" , want )
for _ , diag := range diags {
Refactoring of module source addresses and module installation
It's been a long while since we gave close attention to the codepaths for
module source address parsing and external module package installation.
Due to their age, these codepaths often diverged from our modern practices
such as representing address types in the addrs package, and encapsulating
package installation details only in a particular location.
In particular, this refactor makes source address parsing a separate step
from module installation, which therefore makes the result of that parsing
available to other Terraform subsystems which work with the configuration
representation objects.
This also presented the opportunity to better encapsulate our use of
go-getter into a new package "getmodules" (echoing "getproviders"), which
is intended to be the only part of Terraform that directly interacts with
go-getter.
This is largely just a refactor of the existing functionality into a new
code organization, but there is one notable change in behavior here: the
source address parsing now happens during configuration loading rather
than module installation, which may cause errors about invalid addresses
to be returned in different situations than before. That counts as
backward compatible because we only promise to remain compatible with
configurations that are _valid_, which means that they can be initialized,
planned, and applied without any errors. This doesn't introduce any new
error cases, and instead just makes a pre-existing error case be detected
earlier.
Our module registry client is still using its own special module address
type from registry/regsrc for now, with a small shim from the new
addrs.ModuleSourceRegistry type. Hopefully in a later commit we'll also
rework the registry client to work with the new address type, but this
commit is already big enough as it is.
2021-05-27 21:24:59 -05:00
t . Logf ( "- %#v" , diag )
2019-01-08 20:39:14 -06:00
}
return true
}
func assertResultDeepEqual ( t * testing . T , got , want interface { } ) bool {
t . Helper ( )
if diff := deep . Equal ( got , want ) ; diff != nil {
for _ , problem := range diff {
t . Errorf ( "%s" , problem )
}
return true
}
return false
}