Use hashicorp/terraform-registry-address as a decoupled library (#28338)

* refactor: Use tfaddr for provider address parsing

* refactor: Use tfaddr for module address parsing

* deps: introduce hashicorp/terraform-registry-address
pull/31286/head
Radek Simko 4 years ago committed by GitHub
parent de8eef1da5
commit 7feef1c4aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -24,7 +24,7 @@ require (
github.com/dylanmei/winrmtest v0.0.0-20210303004826-fbc9ae56efb6
github.com/go-test/deep v1.0.3
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.5.6
github.com/google/go-cmp v0.5.8
github.com/google/uuid v1.2.0
github.com/gophercloud/gophercloud v0.10.1-0.20200424014253-c3bfe50899e5
github.com/gophercloud/utils v0.0.0-20200423144003-7c72efc7435d
@ -46,6 +46,7 @@ require (
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f
github.com/hashicorp/hcl/v2 v2.13.0
github.com/hashicorp/terraform-config-inspect v0.0.0-20210209133302-4fd17a0faac2
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734
github.com/jmespath/go-jmespath v0.4.0
github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926
@ -177,7 +178,6 @@ require (
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

@ -282,8 +282,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
@ -403,6 +404,8 @@ github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM=
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/hashicorp/terraform-config-inspect v0.0.0-20210209133302-4fd17a0faac2 h1:l+bLFvHjqtgNQwWxwrFX9PemGAAO2P1AGZM7zlMNvCs=
github.com/hashicorp/terraform-config-inspect v0.0.0-20210209133302-4fd17a0faac2/go.mod h1:Z0Nnk4+3Cy89smEbrq+sl1bxc9198gIP4I7wcQF6Kqs=
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg=
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c/go.mod h1:Wn3Na71knbXc1G8Lh+yu/dQWWJeFQEpDeJMtWMtlmNI=
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0=
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=

@ -1,9 +1,7 @@
package addrs
import (
"strings"
svchost "github.com/hashicorp/terraform-svchost"
tfaddr "github.com/hashicorp/terraform-registry-address"
)
// A ModulePackage represents a physical location where Terraform can retrieve
@ -45,45 +43,4 @@ func (p ModulePackage) String() string {
// registry in order to find a real module package address. These being
// distinct is intended to help future maintainers more easily follow the
// series of steps in the module installer, with the help of the type checker.
type ModuleRegistryPackage struct {
Host svchost.Hostname
Namespace string
Name string
TargetSystem string
}
func (s ModuleRegistryPackage) String() string {
var buf strings.Builder
// Note: we're using the "display" form of the hostname here because
// for our service hostnames "for display" means something different:
// it means to render non-ASCII characters directly as Unicode
// characters, rather than using the "punycode" representation we
// use for internal processing, and so the "display" representation
// is actually what users would write in their configurations.
return s.Host.ForDisplay() + "/" + s.ForRegistryProtocol()
return buf.String()
}
func (s ModuleRegistryPackage) ForDisplay() string {
if s.Host == DefaultModuleRegistryHost {
return s.ForRegistryProtocol()
}
return s.Host.ForDisplay() + "/" + s.ForRegistryProtocol()
}
// ForRegistryProtocol returns a string representation of just the namespace,
// name, and target system portions of the address, always omitting the
// registry hostname and the subdirectory portion, if any.
//
// This is primarily intended for generating addresses to send to the
// registry in question via the registry protocol, since the protocol
// skips sending the registry its own hostname as part of identifiers.
func (s ModuleRegistryPackage) ForRegistryProtocol() string {
var buf strings.Builder
buf.WriteString(s.Namespace)
buf.WriteByte('/')
buf.WriteString(s.Name)
buf.WriteByte('/')
buf.WriteString(s.TargetSystem)
return buf.String()
}
type ModuleRegistryPackage = tfaddr.ModulePackage

@ -3,10 +3,9 @@ package addrs
import (
"fmt"
"path"
"regexp"
"strings"
svchost "github.com/hashicorp/terraform-svchost"
tfaddr "github.com/hashicorp/terraform-registry-address"
"github.com/hashicorp/terraform/internal/getmodules"
)
@ -197,30 +196,11 @@ func (s ModuleSourceLocal) ForDisplay() string {
// combination of a ModuleSourceRegistry and a module version number into
// a concrete ModuleSourceRemote that Terraform will then download and
// install.
type ModuleSourceRegistry struct {
// PackageAddr is the registry package that the target module belongs to.
// The module installer must translate this into a ModuleSourceRemote
// using the registry API and then take that underlying address's
// PackageAddr in order to find the actual package location.
PackageAddr ModuleRegistryPackage
// If Subdir is non-empty then it represents a sub-directory within the
// remote package that the registry address eventually resolves to.
// This will ultimately become the suffix of the Subdir of the
// ModuleSourceRemote that the registry address translates to.
//
// Subdir uses a normalized forward-slash-based path syntax within the
// virtual filesystem represented by the final package. It will never
// include `../` or `./` sequences.
Subdir string
}
type ModuleSourceRegistry tfaddr.Module
// DefaultModuleRegistryHost is the hostname used for registry-based module
// source addresses that do not have an explicit hostname.
const DefaultModuleRegistryHost = svchost.Hostname("registry.terraform.io")
var moduleRegistryNamePattern = regexp.MustCompile("^[0-9A-Za-z](?:[0-9A-Za-z-_]{0,62}[0-9A-Za-z])?$")
var moduleRegistryTargetSystemPattern = regexp.MustCompile("^[0-9a-z]{1,64}$")
const DefaultModuleRegistryHost = tfaddr.DefaultModuleRegistryHost
// ParseModuleSourceRegistry is a variant of ParseModuleSource which only
// accepts module registry addresses, and will reject any other address type.
@ -237,147 +217,30 @@ func ParseModuleSourceRegistry(raw string) (ModuleSource, error) {
return ModuleSourceRegistry{}, fmt.Errorf("can't use local directory %q as a module registry address", raw)
}
ret, err := parseModuleSourceRegistry(raw)
src, err := tfaddr.ParseModuleSource(raw)
if err != nil {
// This is to make sure we return a nil ModuleSource, rather than
// a non-nil ModuleSource containing a zero-value ModuleSourceRegistry.
return nil, err
}
return ret, nil
}
func parseModuleSourceRegistry(raw string) (ModuleSourceRegistry, error) {
var err error
var subDir string
raw, subDir = getmodules.SplitPackageSubdir(raw)
if strings.HasPrefix(subDir, "../") {
return ModuleSourceRegistry{}, fmt.Errorf("subdirectory path %q leads outside of the module package", subDir)
}
parts := strings.Split(raw, "/")
// A valid registry address has either three or four parts, because the
// leading hostname part is optional.
if len(parts) != 3 && len(parts) != 4 {
return ModuleSourceRegistry{}, fmt.Errorf("a module registry source address must have either three or four slash-separated components")
}
host := DefaultModuleRegistryHost
if len(parts) == 4 {
host, err = svchost.ForComparison(parts[0])
if err != nil {
// The svchost library doesn't produce very good error messages to
// return to an end-user, so we'll use some custom ones here.
switch {
case strings.Contains(parts[0], "--"):
// Looks like possibly punycode, which we don't allow here
// to ensure that source addresses are written readably.
return ModuleSourceRegistry{}, fmt.Errorf("invalid module registry hostname %q; internationalized domain names must be given as direct unicode characters, not in punycode", parts[0])
default:
return ModuleSourceRegistry{}, fmt.Errorf("invalid module registry hostname %q", parts[0])
}
}
if !strings.Contains(host.String(), ".") {
return ModuleSourceRegistry{}, fmt.Errorf("invalid module registry hostname: must contain at least one dot")
}
// Discard the hostname prefix now that we've processed it
parts = parts[1:]
}
ret := ModuleSourceRegistry{
PackageAddr: ModuleRegistryPackage{
Host: host,
},
Subdir: subDir,
}
if host == svchost.Hostname("github.com") || host == svchost.Hostname("bitbucket.org") {
return ret, fmt.Errorf("can't use %q as a module registry host, because it's reserved for installing directly from version control repositories", host)
}
if ret.PackageAddr.Namespace, err = parseModuleRegistryName(parts[0]); err != nil {
if strings.Contains(parts[0], ".") {
// Seems like the user omitted one of the latter components in
// an address with an explicit hostname.
return ret, fmt.Errorf("source address must have three more components after the hostname: the namespace, the name, and the target system")
}
return ret, fmt.Errorf("invalid namespace %q: %s", parts[0], err)
}
if ret.PackageAddr.Name, err = parseModuleRegistryName(parts[1]); err != nil {
return ret, fmt.Errorf("invalid module name %q: %s", parts[1], err)
}
if ret.PackageAddr.TargetSystem, err = parseModuleRegistryTargetSystem(parts[2]); err != nil {
if strings.Contains(parts[2], "?") {
// The user was trying to include a query string, probably?
return ret, fmt.Errorf("module registry addresses may not include a query string portion")
}
return ret, fmt.Errorf("invalid target system %q: %s", parts[2], err)
}
return ret, nil
}
// parseModuleRegistryName validates and normalizes a string in either the
// "namespace" or "name" position of a module registry source address.
func parseModuleRegistryName(given string) (string, error) {
// Similar to the names in provider source addresses, we defined these
// to be compatible with what filesystems and typical remote systems
// like GitHub allow in names. Unfortunately we didn't end up defining
// these exactly equivalently: provider names can only use dashes as
// punctuation, whereas module names can use underscores. So here we're
// using some regular expressions from the original module source
// implementation, rather than using the IDNA rules as we do in
// ParseProviderPart.
if !moduleRegistryNamePattern.MatchString(given) {
return "", fmt.Errorf("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")
}
// We also skip normalizing the name to lowercase, because we historically
// didn't do that and so existing module registries might be doing
// case-sensitive matching.
return given, nil
}
// parseModuleRegistryTargetSystem validates and normalizes a string in the
// "target system" position of a module registry source address. This is
// what we historically called "provider" but never actually enforced as
// being a provider address, and now _cannot_ be a provider address because
// provider addresses have three slash-separated components of their own.
func parseModuleRegistryTargetSystem(given string) (string, error) {
// Similar to the names in provider source addresses, we defined these
// to be compatible with what filesystems and typical remote systems
// like GitHub allow in names. Unfortunately we didn't end up defining
// these exactly equivalently: provider names can't use dashes or
// underscores. So here we're using some regular expressions from the
// original module source implementation, rather than using the IDNA rules
// as we do in ParseProviderPart.
if !moduleRegistryTargetSystemPattern.MatchString(given) {
return "", fmt.Errorf("must be between one and 64 ASCII letters or digits")
}
// We also skip normalizing the name to lowercase, because we historically
// didn't do that and so existing module registries might be doing
// case-sensitive matching.
return given, nil
return ModuleSourceRegistry{
Package: src.Package,
Subdir: src.Subdir,
}, nil
}
func (s ModuleSourceRegistry) moduleSource() {}
func (s ModuleSourceRegistry) String() string {
if s.Subdir != "" {
return s.PackageAddr.String() + "//" + s.Subdir
return s.Package.String() + "//" + s.Subdir
}
return s.PackageAddr.String()
return s.Package.String()
}
func (s ModuleSourceRegistry) ForDisplay() string {
if s.Subdir != "" {
return s.PackageAddr.ForDisplay() + "//" + s.Subdir
return s.Package.ForDisplay() + "//" + s.Subdir
}
return s.PackageAddr.ForDisplay()
return s.Package.ForDisplay()
}
// ModuleSourceRemote is a ModuleSource representing a remote location from
@ -387,9 +250,9 @@ func (s ModuleSourceRegistry) ForDisplay() string {
// means that it's selecting a sub-directory of the given package to use as
// the entry point into the package.
type ModuleSourceRemote struct {
// PackageAddr is the address of the remote package that the requested
// Package is the address of the remote package that the requested
// module belongs to.
PackageAddr ModulePackage
Package ModulePackage
// If Subdir is non-empty then it represents a sub-directory within the
// remote package which will serve as the entry-point for the package.
@ -445,8 +308,8 @@ func parseModuleSourceRemote(raw string) (ModuleSourceRemote, error) {
}
return ModuleSourceRemote{
PackageAddr: ModulePackage(norm),
Subdir: subDir,
Package: ModulePackage(norm),
Subdir: subDir,
}, nil
}
@ -454,9 +317,9 @@ func (s ModuleSourceRemote) moduleSource() {}
func (s ModuleSourceRemote) String() string {
if s.Subdir != "" {
return s.PackageAddr.String() + "//" + s.Subdir
return s.Package.String() + "//" + s.Subdir
}
return s.PackageAddr.String()
return s.Package.String()
}
func (s ModuleSourceRemote) ForDisplay() string {

@ -59,7 +59,7 @@ func TestParseModuleSource(t *testing.T) {
"main registry implied": {
input: "hashicorp/subnets/cidr",
want: ModuleSourceRegistry{
PackageAddr: ModuleRegistryPackage{
Package: ModuleRegistryPackage{
Host: svchost.Hostname("registry.terraform.io"),
Namespace: "hashicorp",
Name: "subnets",
@ -71,7 +71,7 @@ func TestParseModuleSource(t *testing.T) {
"main registry implied, subdir": {
input: "hashicorp/subnets/cidr//examples/foo",
want: ModuleSourceRegistry{
PackageAddr: ModuleRegistryPackage{
Package: ModuleRegistryPackage{
Host: svchost.Hostname("registry.terraform.io"),
Namespace: "hashicorp",
Name: "subnets",
@ -92,7 +92,7 @@ func TestParseModuleSource(t *testing.T) {
"custom registry": {
input: "example.com/awesomecorp/network/happycloud",
want: ModuleSourceRegistry{
PackageAddr: ModuleRegistryPackage{
Package: ModuleRegistryPackage{
Host: svchost.Hostname("example.com"),
Namespace: "awesomecorp",
Name: "network",
@ -104,7 +104,7 @@ func TestParseModuleSource(t *testing.T) {
"custom registry, subdir": {
input: "example.com/awesomecorp/network/happycloud//examples/foo",
want: ModuleSourceRegistry{
PackageAddr: ModuleRegistryPackage{
Package: ModuleRegistryPackage{
Host: svchost.Hostname("example.com"),
Namespace: "awesomecorp",
Name: "network",
@ -118,68 +118,68 @@ func TestParseModuleSource(t *testing.T) {
"github.com shorthand": {
input: "github.com/hashicorp/terraform-cidr-subnets",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("git::https://github.com/hashicorp/terraform-cidr-subnets.git"),
Package: 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",
Package: 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"),
Package: 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",
Package: 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"),
Package: 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",
Package: 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"),
Package: 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",
Package: 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"),
Package: 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",
Package: ModulePackage("git::ssh://git@example.com/code/baz.git"),
Subdir: "bleep/bloop",
},
},
@ -190,63 +190,63 @@ func TestParseModuleSource(t *testing.T) {
"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"),
Package: 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"),
Package: 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"),
Package: 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"),
Package: 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"),
Package: 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"),
Package: 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"),
Package: ModulePackage("http://example.com/module"),
},
},
"HTTPS URL": {
input: "https://example.com/module",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("https://example.com/module"),
Package: ModulePackage("https://example.com/module"),
},
},
"HTTPS URL, archive file": {
input: "https://example.com/module.zip",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("https://example.com/module.zip"),
Package: 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"),
Package: ModulePackage("https://example.com/module?archive=tar"),
},
},
"HTTPS URL, forced archive file and checksum": {
@ -255,7 +255,7 @@ func TestParseModuleSource(t *testing.T) {
// 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"),
Package: ModulePackage("https://example.com/module?archive=tar&checksum=blah"),
},
},
@ -266,7 +266,7 @@ func TestParseModuleSource(t *testing.T) {
// is replaced by a deep filesystem copy instead.
input: "/tmp/foo/example",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("file:///tmp/foo/example"),
Package: ModulePackage("file:///tmp/foo/example"),
},
},
"absolute filesystem path, subdir": {
@ -277,8 +277,8 @@ func TestParseModuleSource(t *testing.T) {
// syntax to move the package root higher in the real filesystem.
input: "/tmp/foo//example",
want: ModuleSourceRemote{
PackageAddr: ModulePackage("file:///tmp/foo"),
Subdir: "example",
Package: ModulePackage("file:///tmp/foo"),
Subdir: "example",
},
},
@ -310,7 +310,7 @@ func TestParseModuleSource(t *testing.T) {
// Unfortunately go-getter doesn't actually reject a totally
// invalid address like this until getting time, as long as
// it looks somewhat like a URL.
PackageAddr: ModulePackage("dfgdfgsd:dgfhdfghdfghdfg/dfghdfghdfg"),
Package: ModulePackage("dfgdfgsd:dgfhdfghdfghdfg/dfghdfghdfg"),
},
},
}
@ -344,8 +344,8 @@ func TestParseModuleSource(t *testing.T) {
func TestModuleSourceRemoteFromRegistry(t *testing.T) {
t.Run("both have subdir", func(t *testing.T) {
remote := ModuleSourceRemote{
PackageAddr: ModulePackage("boop"),
Subdir: "foo",
Package: ModulePackage("boop"),
Subdir: "foo",
}
registry := ModuleSourceRegistry{
Subdir: "bar",
@ -363,8 +363,8 @@ func TestModuleSourceRemoteFromRegistry(t *testing.T) {
})
t.Run("only remote has subdir", func(t *testing.T) {
remote := ModuleSourceRemote{
PackageAddr: ModulePackage("boop"),
Subdir: "foo",
Package: ModulePackage("boop"),
Subdir: "foo",
}
registry := ModuleSourceRegistry{
Subdir: "",
@ -382,8 +382,8 @@ func TestModuleSourceRemoteFromRegistry(t *testing.T) {
})
t.Run("only registry has subdir", func(t *testing.T) {
remote := ModuleSourceRemote{
PackageAddr: ModulePackage("boop"),
Subdir: "",
Package: ModulePackage("boop"),
Subdir: "",
}
registry := ModuleSourceRegistry{
Subdir: "bar",
@ -565,7 +565,7 @@ func TestParseModuleSourceRegistry(t *testing.T) {
if got, want := addr.ForDisplay(), test.wantForDisplay; got != want {
t.Errorf("wrong ForDisplay() result\ngot: %s\nwant: %s", got, want)
}
if got, want := addr.PackageAddr.ForRegistryProtocol(), test.wantForProtocol; got != want {
if got, want := addr.Package.ForRegistryProtocol(), test.wantForProtocol; got != want {
t.Errorf("wrong ForRegistryProtocol() result\ngot: %s\nwant: %s", got, want)
}
})

@ -1,32 +1,24 @@
package addrs
import (
"fmt"
"strings"
"golang.org/x/net/idna"
"github.com/hashicorp/hcl/v2"
tfaddr "github.com/hashicorp/terraform-registry-address"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// Provider encapsulates a single provider type. In the future this will be
// extended to include additional fields including Namespace and SourceHost
type Provider struct {
Type string
Namespace string
Hostname svchost.Hostname
}
type Provider = tfaddr.Provider
// DefaultProviderRegistryHost is the hostname used for provider addresses that do
// not have an explicit hostname.
const DefaultProviderRegistryHost = svchost.Hostname("registry.terraform.io")
const DefaultProviderRegistryHost = tfaddr.DefaultProviderRegistryHost
// BuiltInProviderHost is the pseudo-hostname used for the "built-in" provider
// namespace. Built-in provider addresses must also have their namespace set
// to BuiltInProviderNamespace in order to be considered as built-in.
const BuiltInProviderHost = svchost.Hostname("terraform.io")
const BuiltInProviderHost = tfaddr.BuiltInProviderHost
// BuiltInProviderNamespace is the provider namespace used for "built-in"
// providers. Built-in provider addresses must also have their hostname
@ -35,34 +27,17 @@ const BuiltInProviderHost = svchost.Hostname("terraform.io")
// The this namespace is literally named "builtin", in the hope that users
// who see FQNs containing this will be able to infer the way in which they are
// special, even if they haven't encountered the concept formally yet.
const BuiltInProviderNamespace = "builtin"
const BuiltInProviderNamespace = tfaddr.BuiltInProviderNamespace
// LegacyProviderNamespace is the special string used in the Namespace field
// of type Provider to mark a legacy provider address. This special namespace
// value would normally be invalid, and can be used only when the hostname is
// DefaultRegistryHost because that host owns the mapping from legacy name to
// FQN.
const LegacyProviderNamespace = "-"
const LegacyProviderNamespace = tfaddr.LegacyProviderNamespace
// String returns an FQN string, indended for use in machine-readable output.
func (pt Provider) String() string {
if pt.IsZero() {
panic("called String on zero-value addrs.Provider")
}
return pt.Hostname.ForDisplay() + "/" + pt.Namespace + "/" + pt.Type
}
// ForDisplay returns a user-friendly FQN string, simplified for readability. If
// the provider is using the default hostname, the hostname is omitted.
func (pt Provider) ForDisplay() string {
if pt.IsZero() {
panic("called ForDisplay on zero-value addrs.Provider")
}
if pt.Hostname == DefaultProviderRegistryHost {
return pt.Namespace + "/" + pt.Type
}
return pt.Hostname.ForDisplay() + "/" + pt.Namespace + "/" + pt.Type
func IsDefaultProvider(addr Provider) bool {
return addr.Hostname == DefaultProviderRegistryHost && addr.Namespace == "hashicorp"
}
// NewProvider constructs a provider address from its parts, and normalizes
@ -77,18 +52,7 @@ func (pt Provider) ForDisplay() string {
// When accepting namespace or type values from outside the program, use
// ParseProviderPart first to check that the given value is valid.
func NewProvider(hostname svchost.Hostname, namespace, typeName string) Provider {
if namespace == LegacyProviderNamespace {
// Legacy provider addresses must always be created via
// NewLegacyProvider so that we can use static analysis to find
// codepaths still working with those.
panic("attempt to create legacy provider address using NewProvider; use NewLegacyProvider instead")
}
return Provider{
Type: MustParseProviderPart(typeName),
Namespace: MustParseProviderPart(namespace),
Hostname: hostname,
}
return tfaddr.NewProvider(hostname, namespace, typeName)
}
// ImpliedProviderForUnqualifiedType represents the rules for inferring what
@ -118,7 +82,7 @@ func ImpliedProviderForUnqualifiedType(typeName string) Provider {
// NewDefaultProvider returns the default address of a HashiCorp-maintained,
// Registry-hosted provider.
func NewDefaultProvider(name string) Provider {
return Provider{
return tfaddr.Provider{
Type: MustParseProviderPart(name),
Namespace: "hashicorp",
Hostname: DefaultProviderRegistryHost,
@ -128,7 +92,7 @@ func NewDefaultProvider(name string) Provider {
// NewBuiltInProvider returns the address of a "built-in" provider. See
// the docs for Provider.IsBuiltIn for more information.
func NewBuiltInProvider(name string) Provider {
return Provider{
return tfaddr.Provider{
Type: MustParseProviderPart(name),
Namespace: BuiltInProviderNamespace,
Hostname: BuiltInProviderHost,
@ -148,80 +112,6 @@ func NewLegacyProvider(name string) Provider {
}
}
// LegacyString returns the provider type, which is frequently used
// interchangeably with provider name. This function can and should be removed
// when provider type is fully integrated. As a safeguard for future
// refactoring, this function panics if the Provider is not a legacy provider.
func (pt Provider) LegacyString() string {
if pt.IsZero() {
panic("called LegacyString on zero-value addrs.Provider")
}
if pt.Namespace != LegacyProviderNamespace && pt.Namespace != BuiltInProviderNamespace {
panic(pt.String() + " cannot be represented as a legacy string")
}
return pt.Type
}
// IsZero returns true if the receiver is the zero value of addrs.Provider.
//
// The zero value is not a valid addrs.Provider and calling other methods on
// such a value is likely to either panic or otherwise misbehave.
func (pt Provider) IsZero() bool {
return pt == Provider{}
}
// IsBuiltIn returns true if the receiver is the address of a "built-in"
// provider. That is, a provider under terraform.io/builtin/ which is
// included as part of the Terraform binary itself rather than one to be
// installed from elsewhere.
//
// These are ignored by the provider installer because they are assumed to
// already be available without any further installation.
func (pt Provider) IsBuiltIn() bool {
return pt.Hostname == BuiltInProviderHost && pt.Namespace == BuiltInProviderNamespace
}
// LessThan returns true if the receiver should sort before the other given
// address in an ordered list of provider addresses.
//
// This ordering is an arbitrary one just to allow deterministic results from
// functions that would otherwise have no natural ordering. It's subject
// to change in future.
func (pt Provider) LessThan(other Provider) bool {
switch {
case pt.Hostname != other.Hostname:
return pt.Hostname < other.Hostname
case pt.Namespace != other.Namespace:
return pt.Namespace < other.Namespace
default:
return pt.Type < other.Type
}
}
// IsLegacy returns true if the provider is a legacy-style provider
func (pt Provider) IsLegacy() bool {
if pt.IsZero() {
panic("called IsLegacy() on zero-value addrs.Provider")
}
return pt.Hostname == DefaultProviderRegistryHost && pt.Namespace == LegacyProviderNamespace
}
// IsDefault returns true if the provider is a default hashicorp provider
func (pt Provider) IsDefault() bool {
if pt.IsZero() {
panic("called IsDefault() on zero-value addrs.Provider")
}
return pt.Hostname == DefaultProviderRegistryHost && pt.Namespace == "hashicorp"
}
// Equals returns true if the receiver and other provider have the same attributes.
func (pt Provider) Equals(other Provider) bool {
return pt == other
}
// ParseProviderSourceString parses the source attribute and returns a provider.
// This is intended primarily to parse the FQN-like strings returned by
// terraform-config-inspect.
@ -230,146 +120,24 @@ func (pt Provider) Equals(other Provider) bool {
// name
// namespace/name
// hostname/namespace/name
func ParseProviderSourceString(str string) (Provider, tfdiags.Diagnostics) {
var ret Provider
func ParseProviderSourceString(str string) (tfaddr.Provider, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// split the source string into individual components
parts := strings.Split(str, "/")
if len(parts) == 0 || len(parts) > 3 {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider source string",
Detail: `The "source" attribute must be in the format "[hostname/][namespace/]name"`,
})
return ret, diags
}
// check for an invalid empty string in any part
for i := range parts {
if parts[i] == "" {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider source string",
Detail: `The "source" attribute must be in the format "[hostname/][namespace/]name"`,
})
return ret, diags
}
}
// check the 'name' portion, which is always the last part
givenName := parts[len(parts)-1]
name, err := ParseProviderPart(givenName)
if err != nil {
ret, err := tfaddr.ParseProviderSource(str)
if pe, ok := err.(*tfaddr.ParserError); ok {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider type",
Detail: fmt.Sprintf(`Invalid provider type %q in source %q: %s"`, givenName, str, err),
Summary: pe.Summary,
Detail: pe.Detail,
})
return ret, diags
}
ret.Type = name
ret.Hostname = DefaultProviderRegistryHost
if len(parts) == 1 {
return NewDefaultProvider(parts[0]), diags
}
if len(parts) >= 2 {
// the namespace is always the second-to-last part
givenNamespace := parts[len(parts)-2]
if givenNamespace == LegacyProviderNamespace {
// For now we're tolerating legacy provider addresses until we've
// finished updating the rest of the codebase to no longer use them,
// or else we'd get errors round-tripping through legacy subsystems.
ret.Namespace = LegacyProviderNamespace
} else {
namespace, err := ParseProviderPart(givenNamespace)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider namespace",
Detail: fmt.Sprintf(`Invalid provider namespace %q in source %q: %s"`, namespace, str, err),
})
return Provider{}, diags
}
ret.Namespace = namespace
}
if !ret.HasKnownNamespace() {
ret.Namespace = "hashicorp"
}
// Final Case: 3 parts
if len(parts) == 3 {
// the namespace is always the first part in a three-part source string
hn, err := svchost.ForComparison(parts[0])
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider source hostname",
Detail: fmt.Sprintf(`Invalid provider source hostname namespace %q in source %q: %s"`, hn, str, err),
})
return Provider{}, diags
}
ret.Hostname = hn
}
if ret.Namespace == LegacyProviderNamespace && ret.Hostname != DefaultProviderRegistryHost {
// Legacy provider addresses must always be on the default registry
// host, because the default registry host decides what actual FQN
// each one maps to.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider namespace",
Detail: "The legacy provider namespace \"-\" can be used only with hostname " + DefaultProviderRegistryHost.ForDisplay() + ".",
})
return Provider{}, diags
}
// Due to how plugin executables are named and provider git repositories
// are conventionally named, it's a reasonable and
// apparently-somewhat-common user error to incorrectly use the
// "terraform-provider-" prefix in a provider source address. There is
// no good reason for a provider to have the prefix "terraform-" anyway,
// so we've made that invalid from the start both so we can give feedback
// to provider developers about the terraform- prefix being redundant
// and give specialized feedback to folks who incorrectly use the full
// terraform-provider- prefix to help them self-correct.
const redundantPrefix = "terraform-"
const userErrorPrefix = "terraform-provider-"
if strings.HasPrefix(ret.Type, redundantPrefix) {
if strings.HasPrefix(ret.Type, userErrorPrefix) {
// Likely user error. We only return this specialized error if
// whatever is after the prefix would otherwise be a
// syntactically-valid provider type, so we don't end up advising
// the user to try something that would be invalid for another
// reason anyway.
// (This is mainly just for robustness, because the validation
// we already did above should've rejected most/all ways for
// the suggestedType to end up invalid here.)
suggestedType := ret.Type[len(userErrorPrefix):]
if _, err := ParseProviderPart(suggestedType); err == nil {
suggestedAddr := ret
suggestedAddr.Type = suggestedType
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider type",
fmt.Sprintf("Provider source %q has a type with the prefix %q, which isn't valid. Although that prefix is often used in the names of version control repositories for Terraform providers, provider source strings should not include it.\n\nDid you mean %q?", ret.ForDisplay(), userErrorPrefix, suggestedAddr.ForDisplay()),
))
return Provider{}, diags
}
}
// Otherwise, probably instead an incorrectly-named provider, perhaps
// arising from a similar instinct to what causes there to be
// thousands of Python packages on PyPI with "python-"-prefixed
// names.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider type",
fmt.Sprintf("Provider source %q has a type with the prefix %q, which isn't allowed because it would be redundant to name a Terraform provider with that prefix. If you are the author of this provider, rename it to not include the prefix.", ret, redundantPrefix),
))
return Provider{}, diags
}
return ret, diags
return ret, nil
}
// MustParseProviderSourceString is a wrapper around ParseProviderSourceString that panics if
@ -409,36 +177,7 @@ func MustParseProviderSourceString(str string) Provider {
// It's valid to pass the result of this function as the argument to a
// subsequent call, in which case the result will be identical.
func ParseProviderPart(given string) (string, error) {
if len(given) == 0 {
return "", fmt.Errorf("must have at least one character")
}
// We're going to process the given name using the same "IDNA" library we
// use for the hostname portion, since it already implements the case
// folding rules we want.
//
// The idna library doesn't expose individual label parsing directly, but
// once we've verified it doesn't contain any dots we can just treat it
// like a top-level domain for this library's purposes.
if strings.ContainsRune(given, '.') {
return "", fmt.Errorf("dots are not allowed")
}
// We don't allow names containing multiple consecutive dashes, just as
// a matter of preference: they look weird, confusing, or incorrect.
// This also, as a side-effect, prevents the use of the "punycode"
// indicator prefix "xn--" that would cause the IDNA library to interpret
// the given name as punycode, because that would be weird and unexpected.
if strings.Contains(given, "--") {
return "", fmt.Errorf("cannot use multiple consecutive dashes")
}
result, err := idna.Lookup.ToUnicode(given)
if err != nil {
return "", fmt.Errorf("must contain only letters, digits, and dashes, and may not use leading or trailing dashes")
}
return result, nil
return tfaddr.ParseProviderPart(given)
}
// MustParseProviderPart is a wrapper around ParseProviderPart that panics if

@ -124,7 +124,7 @@ func TestProviderDisplay(t *testing.T) {
}
}
func TestProviderIsDefault(t *testing.T) {
func TestProviderIsDefaultProvider(t *testing.T) {
tests := []struct {
Input Provider
Want bool
@ -156,7 +156,7 @@ func TestProviderIsDefault(t *testing.T) {
}
for _, test := range tests {
got := test.Input.IsDefault()
got := IsDefaultProvider(test.Input)
if got != test.Want {
t.Errorf("wrong result for %s\n", test.Input.String())
}

@ -317,7 +317,7 @@ func (c *TestCommand) prepareSuiteDir(ctx context.Context, suiteName string) (te
locks := depsfile.NewLocks()
evts := &providercache.InstallerEvents{
QueryPackagesFailure: func(provider addrs.Provider, err error) {
if err != nil && provider.IsDefault() && provider.Type == "test" {
if err != nil && addrs.IsDefaultProvider(provider) && provider.Type == "test" {
// This is some additional context for the failure error
// we'll generate afterwards. Not the most ideal UX but
// good enough for this prototype implementation, to help

@ -45,7 +45,7 @@ func TestLoadModuleCall(t *testing.T) {
{
Name: "bar",
SourceAddr: addrs.ModuleSourceRegistry{
PackageAddr: addrs.ModuleRegistryPackage{
Package: addrs.ModuleRegistryPackage{
Host: addrs.DefaultModuleRegistryHost,
Namespace: "hashicorp",
Name: "bar",
@ -68,7 +68,7 @@ func TestLoadModuleCall(t *testing.T) {
{
Name: "baz",
SourceAddr: addrs.ModuleSourceRemote{
PackageAddr: addrs.ModulePackage("git::https://example.com/"),
Package: addrs.ModulePackage("git::https://example.com/"),
},
SourceAddrRaw: "git::https://example.com/",
SourceSet: true,

@ -106,7 +106,7 @@ func validateProviderConfigs(parentCall *ModuleCall, cfg *Config, noProviderConf
),
Subject: &req.DeclRange,
})
} else if req.Type.IsDefault() {
} else if addrs.IsDefaultProvider(req.Type) {
// Now check for possible implied duplicates, where a provider
// block uses a default namespaced provider, but that provider
// was required via a different name.
@ -345,7 +345,7 @@ func validateProviderConfigs(parentCall *ModuleCall, cfg *Config, noProviderConf
if !(localName || configAlias || emptyConfig) {
// we still allow default configs, so switch to a warning if the incoming provider is a default
if providerAddr.Provider.IsDefault() {
if addrs.IsDefaultProvider(providerAddr.Provider) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Reference to undefined provider",

@ -40,7 +40,7 @@ import (
// renaming suggestion even if one would've been available for a completed
// request.
func MissingProviderSuggestion(ctx context.Context, addr addrs.Provider, source Source, reqs Requirements) addrs.Provider {
if !addr.IsDefault() {
if !addrs.IsDefaultProvider(addr) {
return addr
}

@ -306,7 +306,7 @@ func (i *ModuleInstaller) installLocalModule(req *earlyconfig.ModuleRequest, key
func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyconfig.ModuleRequest, key string, instPath string, addr addrs.ModuleSourceRegistry, manifest modsdir.Manifest, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
hostname := addr.PackageAddr.Host
hostname := addr.Package.Host
reg := i.reg
var resp *response.ModuleVersions
var exists bool
@ -314,7 +314,7 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
// A registry entry isn't _really_ a module package, but we'll pretend it's
// one for the sake of this reporting by just trimming off any source
// directory.
packageAddr := addr.PackageAddr
packageAddr := addr.Package
// Our registry client is still using the legacy model of addresses, so
// we'll shim it here for now.
@ -469,9 +469,9 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
dlAddr := i.registryPackageSources[moduleAddr]
log.Printf("[TRACE] ModuleInstaller: %s %s %s is available at %q", key, packageAddr, latestMatch, dlAddr.PackageAddr)
log.Printf("[TRACE] ModuleInstaller: %s %s %s is available at %q", key, packageAddr, latestMatch, dlAddr.Package)
err := fetcher.FetchPackage(ctx, instPath, dlAddr.PackageAddr.String())
err := fetcher.FetchPackage(ctx, instPath, dlAddr.Package.String())
if errors.Is(err, context.Canceled) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
@ -494,7 +494,7 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
return nil, nil, diags
}
log.Printf("[TRACE] ModuleInstaller: %s %q was downloaded to %s", key, dlAddr.PackageAddr, instPath)
log.Printf("[TRACE] ModuleInstaller: %s %q was downloaded to %s", key, dlAddr.Package, instPath)
// Incorporate any subdir information from the original path into the
// address returned by the registry in order to find the final directory
@ -540,7 +540,7 @@ func (i *ModuleInstaller) installGoGetterModule(ctx context.Context, req *earlyc
// Report up to the caller that we're about to start downloading.
addr := req.SourceAddr.(addrs.ModuleSourceRemote)
packageAddr := addr.PackageAddr
packageAddr := addr.Package
hooks.Download(key, packageAddr.String(), nil)
if len(req.VersionConstraints) != 0 {
@ -758,7 +758,7 @@ func splitAddrSubdir(addr addrs.ModuleSource) (string, string) {
addr.Subdir = ""
return addr.String(), subDir
case addrs.ModuleSourceRemote:
return addr.PackageAddr.String(), addr.Subdir
return addr.Package.String(), addr.Subdir
case nil:
panic("splitAddrSubdir on nil addrs.ModuleSource")
default:

@ -702,5 +702,5 @@ func makeTestImpliedMoveStmt(t *testing.T, moduleStr, fromStr, toStr string) Mov
}
var fakeExternalModuleSource = addrs.ModuleSourceRemote{
PackageAddr: addrs.ModulePackage("fake-external:///"),
Package: addrs.ModulePackage("fake-external:///"),
}

@ -105,7 +105,7 @@ func NewModule(host, namespace, name, provider, submodule string) (*Module, erro
// use addrs.ModuleSourceRegistry instead, and then package regsrc can be
// removed altogether.
func ModuleFromModuleSourceAddr(addr addrs.ModuleSourceRegistry) *Module {
ret := ModuleFromRegistryPackageAddr(addr.PackageAddr)
ret := ModuleFromRegistryPackageAddr(addr.Package)
ret.RawSubmodule = addr.Subdir
return ret
}

Loading…
Cancel
Save