terraform/internal/addrs/module_source_test.go

574 lines
21 KiB
Go
Raw Normal View History

package addrs
import (
"testing"
"github.com/google/go-cmp/cmp"
svchost "github.com/hashicorp/terraform-svchost"
)
func TestParseModuleSource(t *testing.T) {
tests := map[string]struct {
input string
want ModuleSource
wantErr string
}{
// Local paths
"local in subdirectory": {
input: "./child",
want: ModuleSourceLocal("./child"),
},
"local in subdirectory non-normalized": {
input: "./nope/../child",
want: ModuleSourceLocal("./child"),
},
"local in sibling directory": {
input: "../sibling",
want: ModuleSourceLocal("../sibling"),
},
"local in sibling directory non-normalized": {
input: "./nope/../../sibling",
want: ModuleSourceLocal("../sibling"),
},
"Windows-style local in subdirectory": {
input: `.\child`,
want: ModuleSourceLocal("./child"),
},
"Windows-style local in subdirectory non-normalized": {
input: `.\nope\..\child`,
want: ModuleSourceLocal("./child"),
},
"Windows-style local in sibling directory": {
input: `..\sibling`,
want: ModuleSourceLocal("../sibling"),
},
"Windows-style local in sibling directory non-normalized": {
input: `.\nope\..\..\sibling`,
want: ModuleSourceLocal("../sibling"),
},
"an abominable mix of different slashes": {
input: `./nope\nope/why\./please\don't`,
want: ModuleSourceLocal("./nope/nope/why/please/don't"),
},
// Registry addresses
// (NOTE: There is another test function TestParseModuleSourceRegistry
// which tests this situation more exhaustively, so this is just a
// token set of cases to see that we are indeed calling into the
// registry address parser when appropriate.)
"main registry implied": {
input: "hashicorp/subnets/cidr",
want: ModuleSourceRegistry{
addrs: ModuleRegistryPackage for representing module registry packages Previously we had a separation between ModuleSourceRemote and ModulePackage as a way to represent within the type system that there's an important difference between a module source address and a package address, because module packages often contain multiple modules and so a ModuleSourceRemote combines a ModulePackage with a subdirectory to represent one specific module. This commit applies that same strategy to ModuleSourceRegistry, creating a new type ModuleRegistryPackage to represent the different sort of package that we use for registry modules. Again, the main goal here is to try to reflect the conceptual modelling more directly in the type system so that we can more easily verify that uses of these different address types are correct. To make use of that, I've also lightly reworked initwd's module installer to use addrs.ModuleRegistryPackage directly, instead of a string representation thereof. This was in response to some earlier commits where I found myself accidentally mixing up package addresses and source addresses in the installRegistryModule method; with this new organization those bugs would've been caught at compile time, rather than only at unit and integration testing time. While in the area anyway, I also took this opportunity to fix some historical confusing names of fields in initwd.ModuleInstaller, to be clearer that they are only for registry packages and not for all module source address types.
2021-06-02 21:26:35 +02:00
PackageAddr: ModuleRegistryPackage{
Host: svchost.Hostname("registry.terraform.io"),
Namespace: "hashicorp",
Name: "subnets",
TargetSystem: "cidr",
},
Subdir: "",
},
},
"main registry implied, subdir": {
input: "hashicorp/subnets/cidr//examples/foo",
want: ModuleSourceRegistry{
addrs: ModuleRegistryPackage for representing module registry packages Previously we had a separation between ModuleSourceRemote and ModulePackage as a way to represent within the type system that there's an important difference between a module source address and a package address, because module packages often contain multiple modules and so a ModuleSourceRemote combines a ModulePackage with a subdirectory to represent one specific module. This commit applies that same strategy to ModuleSourceRegistry, creating a new type ModuleRegistryPackage to represent the different sort of package that we use for registry modules. Again, the main goal here is to try to reflect the conceptual modelling more directly in the type system so that we can more easily verify that uses of these different address types are correct. To make use of that, I've also lightly reworked initwd's module installer to use addrs.ModuleRegistryPackage directly, instead of a string representation thereof. This was in response to some earlier commits where I found myself accidentally mixing up package addresses and source addresses in the installRegistryModule method; with this new organization those bugs would've been caught at compile time, rather than only at unit and integration testing time. While in the area anyway, I also took this opportunity to fix some historical confusing names of fields in initwd.ModuleInstaller, to be clearer that they are only for registry packages and not for all module source address types.
2021-06-02 21:26:35 +02:00
PackageAddr: ModuleRegistryPackage{
Host: svchost.Hostname("registry.terraform.io"),
Namespace: "hashicorp",
Name: "subnets",
TargetSystem: "cidr",
},
Subdir: "examples/foo",
},
},
"main registry implied, escaping subdir": {
input: "hashicorp/subnets/cidr//../nope",
// NOTE: This error is actually being caught by the _remote package_
// address parser, because any registry parsing failure falls back
// to that but both of them have the same subdir validation. This
// case is here to make sure that stays true, so we keep reporting
// a suitable error when the user writes a registry-looking thing.
wantErr: `subdirectory path "../nope" leads outside of the module package`,
},
"custom registry": {
input: "example.com/awesomecorp/network/happycloud",
want: ModuleSourceRegistry{
addrs: ModuleRegistryPackage for representing module registry packages Previously we had a separation between ModuleSourceRemote and ModulePackage as a way to represent within the type system that there's an important difference between a module source address and a package address, because module packages often contain multiple modules and so a ModuleSourceRemote combines a ModulePackage with a subdirectory to represent one specific module. This commit applies that same strategy to ModuleSourceRegistry, creating a new type ModuleRegistryPackage to represent the different sort of package that we use for registry modules. Again, the main goal here is to try to reflect the conceptual modelling more directly in the type system so that we can more easily verify that uses of these different address types are correct. To make use of that, I've also lightly reworked initwd's module installer to use addrs.ModuleRegistryPackage directly, instead of a string representation thereof. This was in response to some earlier commits where I found myself accidentally mixing up package addresses and source addresses in the installRegistryModule method; with this new organization those bugs would've been caught at compile time, rather than only at unit and integration testing time. While in the area anyway, I also took this opportunity to fix some historical confusing names of fields in initwd.ModuleInstaller, to be clearer that they are only for registry packages and not for all module source address types.
2021-06-02 21:26:35 +02:00
PackageAddr: ModuleRegistryPackage{
Host: svchost.Hostname("example.com"),
Namespace: "awesomecorp",
Name: "network",
TargetSystem: "happycloud",
},
Subdir: "",
},
},
"custom registry, subdir": {
input: "example.com/awesomecorp/network/happycloud//examples/foo",
want: ModuleSourceRegistry{
addrs: ModuleRegistryPackage for representing module registry packages Previously we had a separation between ModuleSourceRemote and ModulePackage as a way to represent within the type system that there's an important difference between a module source address and a package address, because module packages often contain multiple modules and so a ModuleSourceRemote combines a ModulePackage with a subdirectory to represent one specific module. This commit applies that same strategy to ModuleSourceRegistry, creating a new type ModuleRegistryPackage to represent the different sort of package that we use for registry modules. Again, the main goal here is to try to reflect the conceptual modelling more directly in the type system so that we can more easily verify that uses of these different address types are correct. To make use of that, I've also lightly reworked initwd's module installer to use addrs.ModuleRegistryPackage directly, instead of a string representation thereof. This was in response to some earlier commits where I found myself accidentally mixing up package addresses and source addresses in the installRegistryModule method; with this new organization those bugs would've been caught at compile time, rather than only at unit and integration testing time. While in the area anyway, I also took this opportunity to fix some historical confusing names of fields in initwd.ModuleInstaller, to be clearer that they are only for registry packages and not for all module source address types.
2021-06-02 21:26:35 +02:00
PackageAddr: ModuleRegistryPackage{
Host: svchost.Hostname("example.com"),
Namespace: "awesomecorp",
Name: "network",
TargetSystem: "happycloud",
},
Subdir: "examples/foo",
},
},
// Remote package addresses
"github.com shorthand": {
input: "github.com/hashicorp/terraform-cidr-subnets",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("git::https://github.com/hashicorp/terraform-cidr-subnets.git"),
},
},
"github.com shorthand, subdir": {
input: "github.com/hashicorp/terraform-cidr-subnets//example/foo",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("git::https://github.com/hashicorp/terraform-cidr-subnets.git"),
Subdir: "example/foo",
},
},
"git protocol, URL-style": {
input: "git://example.com/code/baz.git",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("git://example.com/code/baz.git"),
},
},
"git protocol, URL-style, subdir": {
input: "git://example.com/code/baz.git//bleep/bloop",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("git://example.com/code/baz.git"),
Subdir: "bleep/bloop",
},
},
"git over HTTPS, URL-style": {
input: "git::https://example.com/code/baz.git",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("git::https://example.com/code/baz.git"),
},
},
"git over HTTPS, URL-style, subdir": {
input: "git::https://example.com/code/baz.git//bleep/bloop",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("git::https://example.com/code/baz.git"),
Subdir: "bleep/bloop",
},
},
"git over SSH, URL-style": {
input: "git::ssh://git@example.com/code/baz.git",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("git::ssh://git@example.com/code/baz.git"),
},
},
"git over SSH, URL-style, subdir": {
input: "git::ssh://git@example.com/code/baz.git//bleep/bloop",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("git::ssh://git@example.com/code/baz.git"),
Subdir: "bleep/bloop",
},
},
"git over SSH, scp-style": {
input: "git::git@example.com:code/baz.git",
want: ModuleSourceRemote{
// Normalized to URL-style
PackageAddr: ModulePackage("git::ssh://git@example.com/code/baz.git"),
},
},
"git over SSH, scp-style, subdir": {
input: "git::git@example.com:code/baz.git//bleep/bloop",
want: ModuleSourceRemote{
// Normalized to URL-style
PackageAddr: ModulePackage("git::ssh://git@example.com/code/baz.git"),
Subdir: "bleep/bloop",
},
},
// NOTE: We intentionally don't test the bitbucket.org shorthands
// here, because that detector makes direct HTTP tequests to the
// Bitbucket API and thus isn't appropriate for unit testing.
"Google Cloud Storage bucket implied, path prefix": {
input: "www.googleapis.com/storage/v1/BUCKET_NAME/PATH_TO_MODULE",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH_TO_MODULE"),
},
},
"Google Cloud Storage bucket, path prefix": {
input: "gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH_TO_MODULE",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH_TO_MODULE"),
},
},
"Google Cloud Storage bucket implied, archive object": {
input: "www.googleapis.com/storage/v1/BUCKET_NAME/PATH/TO/module.zip",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH/TO/module.zip"),
},
},
"Google Cloud Storage bucket, archive object": {
input: "gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH/TO/module.zip",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH/TO/module.zip"),
},
},
"Amazon S3 bucket implied, archive object": {
input: "s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip"),
},
},
"Amazon S3 bucket, archive object": {
input: "s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip"),
},
},
"HTTP URL": {
input: "http://example.com/module",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("http://example.com/module"),
},
},
"HTTPS URL": {
input: "https://example.com/module",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("https://example.com/module"),
},
},
"HTTPS URL, archive file": {
input: "https://example.com/module.zip",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("https://example.com/module.zip"),
},
},
"HTTPS URL, forced archive file": {
input: "https://example.com/module?archive=tar",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("https://example.com/module?archive=tar"),
},
},
"HTTPS URL, forced archive file and checksum": {
input: "https://example.com/module?archive=tar&checksum=blah",
want: ModuleSourceRemote{
// The query string only actually gets processed when we finally
// do the get, so "checksum=blah" is accepted as valid up
// at this parsing layer.
PackageAddr: ModulePackage("https://example.com/module?archive=tar&checksum=blah"),
},
},
"absolute filesystem path": {
// Although a local directory isn't really "remote", we do
// treat it as such because we still need to do all of the same
// high-level steps to work with these, even though "downloading"
// is replaced by a deep filesystem copy instead.
input: "/tmp/foo/example",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("file:///tmp/foo/example"),
},
},
"absolute filesystem path, subdir": {
// This is a funny situation where the user wants to use a
// directory elsewhere on their system as a package containing
// multiple modules, but the entry point is not at the root
// of that subtree, and so they can use the usual subdir
// syntax to move the package root higher in the real filesystem.
input: "/tmp/foo//example",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("file:///tmp/foo"),
Subdir: "example",
},
},
"subdir escaping out of package": {
// This is general logic for all subdir regardless of installation
// protocol, but we're using a filesystem path here just as an
// easy placeholder/
input: "/tmp/foo//example/../../invalid",
wantErr: `subdirectory path "../invalid" leads outside of the module package`,
},
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-28 04:24:59 +02:00
"relative path without the needed prefix": {
input: "boop/bloop",
// For this case we return a generic error message from the addrs
// layer, but using a specialized error type which our module
// installer checks for and produces an extra hint for users who
// were intending to write a local path which then got
// misinterpreted as a remote source due to the missing prefix.
// However, the main message is generic here because this is really
// just a general "this string doesn't match any of our source
// address patterns" situation, not _necessarily_ about relative
// local paths.
wantErr: `Terraform cannot detect a supported external module source type for boop/bloop`,
},
"go-getter will accept all sorts of garbage": {
input: "dfgdfgsd:dgfhdfghdfghdfg/dfghdfghdfg",
want: ModuleSourceRemote{
// Unfortunately go-getter doesn't actually reject a totally
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-28 04:24:59 +02:00
// invalid address like this until getting time, as long as
// it looks somewhat like a URL.
PackageAddr: ModulePackage("dfgdfgsd:dgfhdfghdfghdfg/dfghdfghdfg"),
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
addr, err := ParseModuleSource(test.input)
if test.wantErr != "" {
switch {
case err == nil:
t.Errorf("unexpected success\nwant error: %s", test.wantErr)
case err.Error() != test.wantErr:
t.Errorf("wrong error messages\ngot: %s\nwant: %s", err.Error(), test.wantErr)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}
if diff := cmp.Diff(addr, test.want); diff != "" {
t.Errorf("wrong result\n%s", diff)
}
})
}
}
func TestModuleSourceRemoteFromRegistry(t *testing.T) {
t.Run("both have subdir", func(t *testing.T) {
remote := ModuleSourceRemote{
PackageAddr: ModulePackage("boop"),
Subdir: "foo",
}
registry := ModuleSourceRegistry{
Subdir: "bar",
}
gotAddr := remote.FromRegistry(registry)
if remote.Subdir != "foo" {
t.Errorf("FromRegistry modified the reciever; should be pure function")
}
if registry.Subdir != "bar" {
t.Errorf("FromRegistry modified the given address; should be pure function")
}
if got, want := gotAddr.Subdir, "foo/bar"; got != want {
t.Errorf("wrong resolved subdir\ngot: %s\nwant: %s", got, want)
}
})
t.Run("only remote has subdir", func(t *testing.T) {
remote := ModuleSourceRemote{
PackageAddr: ModulePackage("boop"),
Subdir: "foo",
}
registry := ModuleSourceRegistry{
Subdir: "",
}
gotAddr := remote.FromRegistry(registry)
if remote.Subdir != "foo" {
t.Errorf("FromRegistry modified the reciever; should be pure function")
}
if registry.Subdir != "" {
t.Errorf("FromRegistry modified the given address; should be pure function")
}
if got, want := gotAddr.Subdir, "foo"; got != want {
t.Errorf("wrong resolved subdir\ngot: %s\nwant: %s", got, want)
}
})
t.Run("only registry has subdir", func(t *testing.T) {
remote := ModuleSourceRemote{
PackageAddr: ModulePackage("boop"),
Subdir: "",
}
registry := ModuleSourceRegistry{
Subdir: "bar",
}
gotAddr := remote.FromRegistry(registry)
if remote.Subdir != "" {
t.Errorf("FromRegistry modified the reciever; should be pure function")
}
if registry.Subdir != "bar" {
t.Errorf("FromRegistry modified the given address; should be pure function")
}
if got, want := gotAddr.Subdir, "bar"; got != want {
t.Errorf("wrong resolved subdir\ngot: %s\nwant: %s", got, want)
}
})
}
func TestParseModuleSourceRegistry(t *testing.T) {
// We test parseModuleSourceRegistry alone here, in addition to testing
// it indirectly as part of TestParseModuleSource, because general
// module parsing unfortunately eats all of the error situations from
// registry passing by falling back to trying for a direct remote package
// address.
// Historical note: These test cases were originally derived from the
// ones in the old internal/registry/regsrc package that the
// ModuleSourceRegistry type is replacing. That package had the notion
// of "normalized" addresses as separate from the original user input,
// but this new implementation doesn't try to preserve the original
// user input at all, and so the main string output is always normalized.
//
// That package also had some behaviors to turn the namespace, name, and
// remote system portions into lowercase, but apparently we didn't
// actually make use of that in the end and were preserving the case
// the user provided in the input, and so for backward compatibility
// we're continuing to do that here, at the expense of now making the
// "ForDisplay" output case-preserving where its predecessor in the
// old package wasn't. The main Terraform Registry at registry.terraform.io
// is itself case-insensitive anyway, so our case-preserving here is
// entirely for the benefit of existing third-party registry
// implementations that might be case-sensitive, which we must remain
// compatible with now.
tests := map[string]struct {
input string
wantString string
wantForDisplay string
wantForProtocol string
wantErr string
}{
"public registry": {
input: `hashicorp/consul/aws`,
wantString: `registry.terraform.io/hashicorp/consul/aws`,
wantForDisplay: `hashicorp/consul/aws`,
wantForProtocol: `hashicorp/consul/aws`,
},
"public registry with subdir": {
input: `hashicorp/consul/aws//foo`,
wantString: `registry.terraform.io/hashicorp/consul/aws//foo`,
wantForDisplay: `hashicorp/consul/aws//foo`,
wantForProtocol: `hashicorp/consul/aws`,
},
"public registry using explicit hostname": {
input: `registry.terraform.io/hashicorp/consul/aws`,
wantString: `registry.terraform.io/hashicorp/consul/aws`,
wantForDisplay: `hashicorp/consul/aws`,
wantForProtocol: `hashicorp/consul/aws`,
},
"public registry with mixed case names": {
input: `HashiCorp/Consul/aws`,
wantString: `registry.terraform.io/HashiCorp/Consul/aws`,
wantForDisplay: `HashiCorp/Consul/aws`,
wantForProtocol: `HashiCorp/Consul/aws`,
},
"private registry with non-standard port": {
input: `Example.com:1234/HashiCorp/Consul/aws`,
wantString: `example.com:1234/HashiCorp/Consul/aws`,
wantForDisplay: `example.com:1234/HashiCorp/Consul/aws`,
wantForProtocol: `HashiCorp/Consul/aws`,
},
"private registry with IDN hostname": {
input: `Испытание.com/HashiCorp/Consul/aws`,
wantString: `испытание.com/HashiCorp/Consul/aws`,
wantForDisplay: `испытание.com/HashiCorp/Consul/aws`,
wantForProtocol: `HashiCorp/Consul/aws`,
},
"private registry with IDN hostname and non-standard port": {
input: `Испытание.com:1234/HashiCorp/Consul/aws//Foo`,
wantString: `испытание.com:1234/HashiCorp/Consul/aws//Foo`,
wantForDisplay: `испытание.com:1234/HashiCorp/Consul/aws//Foo`,
wantForProtocol: `HashiCorp/Consul/aws`,
},
"invalid hostname": {
input: `---.com/HashiCorp/Consul/aws`,
wantErr: `invalid module registry hostname "---.com"; internationalized domain names must be given as direct unicode characters, not in punycode`,
},
"hostname with only one label": {
// This was historically forbidden in our initial implementation,
// so we keep it forbidden to avoid newly interpreting such
// addresses as registry addresses rather than remote source
// addresses.
input: `foo/var/baz/qux`,
wantErr: `invalid module registry hostname: must contain at least one dot`,
},
"invalid target system characters": {
input: `foo/var/no-no-no`,
wantErr: `invalid target system "no-no-no": must be between one and 64 ASCII letters or digits`,
},
"invalid target system length": {
input: `foo/var/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah`,
wantErr: `invalid target system "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah": must be between one and 64 ASCII letters or digits`,
},
"invalid namespace": {
input: `boop!/var/baz`,
wantErr: `invalid namespace "boop!": must be between one and 64 characters, including ASCII letters, digits, dashes, and underscores, where dashes and underscores may not be the prefix or suffix`,
},
"missing part with explicit hostname": {
input: `foo.com/var/baz`,
wantErr: `source address must have three more components after the hostname: the namespace, the name, and the target system`,
},
"errant query string": {
input: `foo/var/baz?otherthing`,
wantErr: `module registry addresses may not include a query string portion`,
},
"github.com": {
// We don't allow using github.com like a module registry because
// that conflicts with the historically-supported shorthand for
// installing directly from GitHub-hosted git repositories.
input: `github.com/HashiCorp/Consul/aws`,
wantErr: `can't use "github.com" as a module registry host, because it's reserved for installing directly from version control repositories`,
},
"bitbucket.org": {
// We don't allow using bitbucket.org like a module registry because
// that conflicts with the historically-supported shorthand for
// installing directly from BitBucket-hosted git repositories.
input: `bitbucket.org/HashiCorp/Consul/aws`,
wantErr: `can't use "bitbucket.org" as a module registry host, because it's reserved for installing directly from version control repositories`,
},
"local path from current dir": {
// Can't use a local path when we're specifically trying to parse
// a _registry_ source address.
input: `./boop`,
wantErr: `can't use local directory "./boop" as a module registry address`,
},
"local path from parent dir": {
// Can't use a local path when we're specifically trying to parse
// a _registry_ source address.
input: `../boop`,
wantErr: `can't use local directory "../boop" as a module registry address`,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
addrI, err := ParseModuleSourceRegistry(test.input)
if test.wantErr != "" {
switch {
case err == nil:
t.Errorf("unexpected success\nwant error: %s", test.wantErr)
case err.Error() != test.wantErr:
t.Errorf("wrong error messages\ngot: %s\nwant: %s", err.Error(), test.wantErr)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}
addr, ok := addrI.(ModuleSourceRegistry)
if !ok {
t.Fatalf("wrong address type %T; want %T", addrI, addr)
}
if got, want := addr.String(), test.wantString; got != want {
t.Errorf("wrong String() result\ngot: %s\nwant: %s", got, want)
}
if got, want := addr.ForDisplay(), test.wantForDisplay; got != want {
t.Errorf("wrong ForDisplay() result\ngot: %s\nwant: %s", got, want)
}
addrs: ModuleRegistryPackage for representing module registry packages Previously we had a separation between ModuleSourceRemote and ModulePackage as a way to represent within the type system that there's an important difference between a module source address and a package address, because module packages often contain multiple modules and so a ModuleSourceRemote combines a ModulePackage with a subdirectory to represent one specific module. This commit applies that same strategy to ModuleSourceRegistry, creating a new type ModuleRegistryPackage to represent the different sort of package that we use for registry modules. Again, the main goal here is to try to reflect the conceptual modelling more directly in the type system so that we can more easily verify that uses of these different address types are correct. To make use of that, I've also lightly reworked initwd's module installer to use addrs.ModuleRegistryPackage directly, instead of a string representation thereof. This was in response to some earlier commits where I found myself accidentally mixing up package addresses and source addresses in the installRegistryModule method; with this new organization those bugs would've been caught at compile time, rather than only at unit and integration testing time. While in the area anyway, I also took this opportunity to fix some historical confusing names of fields in initwd.ModuleInstaller, to be clearer that they are only for registry packages and not for all module source address types.
2021-06-02 21:26:35 +02:00
if got, want := addr.PackageAddr.ForRegistryProtocol(), test.wantForProtocol; got != want {
t.Errorf("wrong ForRegistryProtocol() result\ngot: %s\nwant: %s", got, want)
}
})
}
}