2023-05-02 11:33:06 -04:00
|
|
|
// Copyright IBM Corp. 2014, 2026
|
2023-08-10 18:43:27 -04:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
2023-05-02 11:33:06 -04:00
|
|
|
|
2021-05-27 15:24:45 -04:00
|
|
|
package addrs
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"path"
|
|
|
|
|
"strings"
|
|
|
|
|
|
2022-07-08 09:46:29 -04:00
|
|
|
tfaddr "github.com/hashicorp/terraform-registry-address"
|
2021-05-27 15:24:45 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ModuleSource is the general type for all three of the possible module source
|
moduleaddrs: Split module source address parsing from package addrs
Our package addrs ends up getting imported from just about ever other
package in Terraform, because it contains the types we use to talk about
various different kinds of objects. Therefore we typically try to keep its
transitive dependency graph relatively small, because anything it depends
on becomes an indirect dependency of nearly everything else.
A while back we moved the module source address models into package addrs,
which also brought with them the code for parsing strings to produce those
addresses. Unfortunately, remote module source addresses are defined using
the external dependency go-getter, which is pretty heavy itself and also
brings with it numerous other external dependencies such as the AWS SDK,
the Google Cloud Platform SDK, etc.
Since only relatively few packages actually need to _parse_ source
addresses -- with most either not caring about them at all or only
consuming addresses that were already parsed by someone else -- we'll
move the parser functions into their own package, while keeping the
resulting address types in package addrs.
This does still retain the package addrs dependency on external module
github.com/hashicorp/terraform-registry-address, which is not ideal but
that one at least has a relatively shallow dependency subgraph, so there's
not so much urgency to tidy that one.
2024-03-13 18:44:36 -04:00
|
|
|
// address types. The concrete implementations of this are [ModuleSourceLocal],
|
|
|
|
|
// [ModuleSourceRegistry], and [ModuleSourceRemote].
|
|
|
|
|
//
|
|
|
|
|
// The parser for this address type lives in package moduleaddrs, because remote
|
|
|
|
|
// module source address parsing depends on go-getter and that's too heavy a
|
|
|
|
|
// dependency to impose on everything that imports this package addrs.
|
2021-05-27 15:24:45 -04:00
|
|
|
type ModuleSource interface {
|
|
|
|
|
// String returns a full representation of the address, including any
|
|
|
|
|
// additional components that are typically implied by omission in
|
|
|
|
|
// user-written addresses.
|
|
|
|
|
//
|
|
|
|
|
// We typically use this longer representation in error message, in case
|
|
|
|
|
// the inclusion of normally-omitted components is helpful in debugging
|
|
|
|
|
// unexpected behavior.
|
|
|
|
|
String() string
|
|
|
|
|
|
|
|
|
|
// ForDisplay is similar to String but instead returns a representation of
|
|
|
|
|
// the idiomatic way to write the address in configuration, omitting
|
|
|
|
|
// components that are commonly just implied in addresses written by
|
|
|
|
|
// users.
|
|
|
|
|
//
|
|
|
|
|
// We typically use this shorter representation in informational messages,
|
|
|
|
|
// such as the note that we're about to start downloading a package.
|
|
|
|
|
ForDisplay() string
|
|
|
|
|
|
|
|
|
|
moduleSource()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _ ModuleSource = ModuleSourceLocal("")
|
|
|
|
|
var _ ModuleSource = ModuleSourceRegistry{}
|
|
|
|
|
var _ ModuleSource = ModuleSourceRemote{}
|
|
|
|
|
|
|
|
|
|
// ModuleSourceLocal is a ModuleSource representing a local path reference
|
|
|
|
|
// from the caller's directory to the callee's directory within the same
|
|
|
|
|
// module package.
|
|
|
|
|
//
|
|
|
|
|
// A "module package" here means a set of modules distributed together in
|
|
|
|
|
// the same archive, repository, or similar. That's a significant distinction
|
|
|
|
|
// because we always download and cache entire module packages at once,
|
|
|
|
|
// and then create relative references within the same directory in order
|
|
|
|
|
// to ensure all modules in the package are looking at a consistent filesystem
|
|
|
|
|
// layout. We also assume that modules within a package are maintained together,
|
|
|
|
|
// which means that cross-cutting maintenence across all of them would be
|
|
|
|
|
// possible.
|
|
|
|
|
//
|
|
|
|
|
// The actual value of a ModuleSourceLocal is a normalized relative path using
|
|
|
|
|
// forward slashes, even on operating systems that have other conventions,
|
|
|
|
|
// because we're representing traversal within the logical filesystem
|
|
|
|
|
// represented by the containing package, not actually within the physical
|
|
|
|
|
// filesystem we unpacked the package into. We should typically not construct
|
|
|
|
|
// ModuleSourceLocal values directly, except in tests where we can ensure
|
|
|
|
|
// the value meets our assumptions. Use ParseModuleSource instead if the
|
|
|
|
|
// input string is not hard-coded in the program.
|
moduleaddrs: Split module source address parsing from package addrs
Our package addrs ends up getting imported from just about ever other
package in Terraform, because it contains the types we use to talk about
various different kinds of objects. Therefore we typically try to keep its
transitive dependency graph relatively small, because anything it depends
on becomes an indirect dependency of nearly everything else.
A while back we moved the module source address models into package addrs,
which also brought with them the code for parsing strings to produce those
addresses. Unfortunately, remote module source addresses are defined using
the external dependency go-getter, which is pretty heavy itself and also
brings with it numerous other external dependencies such as the AWS SDK,
the Google Cloud Platform SDK, etc.
Since only relatively few packages actually need to _parse_ source
addresses -- with most either not caring about them at all or only
consuming addresses that were already parsed by someone else -- we'll
move the parser functions into their own package, while keeping the
resulting address types in package addrs.
This does still retain the package addrs dependency on external module
github.com/hashicorp/terraform-registry-address, which is not ideal but
that one at least has a relatively shallow dependency subgraph, so there's
not so much urgency to tidy that one.
2024-03-13 18:44:36 -04:00
|
|
|
//
|
|
|
|
|
// The parser for this address type lives in package moduleaddrs. It doesn't
|
|
|
|
|
// really need to because it doesn't have any special dependencies, but the
|
|
|
|
|
// remote source address parser needs to live over there and so it's clearer
|
|
|
|
|
// to just have all of the parsers live together in that other package.
|
2021-05-27 15:24:45 -04:00
|
|
|
type ModuleSourceLocal string
|
|
|
|
|
|
|
|
|
|
func (s ModuleSourceLocal) moduleSource() {}
|
|
|
|
|
|
|
|
|
|
func (s ModuleSourceLocal) String() string {
|
|
|
|
|
// We assume that our underlying string was already normalized at
|
|
|
|
|
// construction, so we just return it verbatim.
|
|
|
|
|
return string(s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s ModuleSourceLocal) ForDisplay() string {
|
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 22:24:59 -04:00
|
|
|
return string(s)
|
2021-05-27 15:24:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ModuleSourceRegistry is a ModuleSource representing a module listed in a
|
|
|
|
|
// Terraform module registry.
|
|
|
|
|
//
|
|
|
|
|
// A registry source isn't a direct source location but rather an indirection
|
|
|
|
|
// over a ModuleSourceRemote. The job of a registry is to translate the
|
|
|
|
|
// combination of a ModuleSourceRegistry and a module version number into
|
|
|
|
|
// a concrete ModuleSourceRemote that Terraform will then download and
|
|
|
|
|
// install.
|
moduleaddrs: Split module source address parsing from package addrs
Our package addrs ends up getting imported from just about ever other
package in Terraform, because it contains the types we use to talk about
various different kinds of objects. Therefore we typically try to keep its
transitive dependency graph relatively small, because anything it depends
on becomes an indirect dependency of nearly everything else.
A while back we moved the module source address models into package addrs,
which also brought with them the code for parsing strings to produce those
addresses. Unfortunately, remote module source addresses are defined using
the external dependency go-getter, which is pretty heavy itself and also
brings with it numerous other external dependencies such as the AWS SDK,
the Google Cloud Platform SDK, etc.
Since only relatively few packages actually need to _parse_ source
addresses -- with most either not caring about them at all or only
consuming addresses that were already parsed by someone else -- we'll
move the parser functions into their own package, while keeping the
resulting address types in package addrs.
This does still retain the package addrs dependency on external module
github.com/hashicorp/terraform-registry-address, which is not ideal but
that one at least has a relatively shallow dependency subgraph, so there's
not so much urgency to tidy that one.
2024-03-13 18:44:36 -04:00
|
|
|
//
|
|
|
|
|
// The parser for this address type lives in package moduleaddrs. It doesn't
|
|
|
|
|
// really need to because it doesn't have any special dependencies, but the
|
|
|
|
|
// remote source address parser needs to live over there and so it's clearer
|
|
|
|
|
// to just have all of the parsers live together in that other package.
|
2022-07-08 09:46:29 -04:00
|
|
|
type ModuleSourceRegistry tfaddr.Module
|
2021-05-27 15:24:45 -04:00
|
|
|
|
|
|
|
|
// DefaultModuleRegistryHost is the hostname used for registry-based module
|
|
|
|
|
// source addresses that do not have an explicit hostname.
|
2022-07-08 09:46:29 -04:00
|
|
|
const DefaultModuleRegistryHost = tfaddr.DefaultModuleRegistryHost
|
2021-05-27 15:24:45 -04:00
|
|
|
|
|
|
|
|
func (s ModuleSourceRegistry) moduleSource() {}
|
|
|
|
|
|
|
|
|
|
func (s ModuleSourceRegistry) String() string {
|
|
|
|
|
if s.Subdir != "" {
|
2022-07-08 09:46:29 -04:00
|
|
|
return s.Package.String() + "//" + s.Subdir
|
2021-05-27 15:24:45 -04:00
|
|
|
}
|
2022-07-08 09:46:29 -04:00
|
|
|
return s.Package.String()
|
2021-05-27 15:24:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s ModuleSourceRegistry) ForDisplay() string {
|
|
|
|
|
if s.Subdir != "" {
|
2022-07-08 09:46:29 -04:00
|
|
|
return s.Package.ForDisplay() + "//" + s.Subdir
|
2021-05-27 15:24:45 -04:00
|
|
|
}
|
2022-07-08 09:46:29 -04:00
|
|
|
return s.Package.ForDisplay()
|
2021-05-27 15:24:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ModuleSourceRemote is a ModuleSource representing a remote location from
|
|
|
|
|
// which we can retrieve a module package.
|
|
|
|
|
//
|
|
|
|
|
// A ModuleSourceRemote can optionally include a "subdirectory" path, which
|
|
|
|
|
// means that it's selecting a sub-directory of the given package to use as
|
|
|
|
|
// the entry point into the package.
|
moduleaddrs: Split module source address parsing from package addrs
Our package addrs ends up getting imported from just about ever other
package in Terraform, because it contains the types we use to talk about
various different kinds of objects. Therefore we typically try to keep its
transitive dependency graph relatively small, because anything it depends
on becomes an indirect dependency of nearly everything else.
A while back we moved the module source address models into package addrs,
which also brought with them the code for parsing strings to produce those
addresses. Unfortunately, remote module source addresses are defined using
the external dependency go-getter, which is pretty heavy itself and also
brings with it numerous other external dependencies such as the AWS SDK,
the Google Cloud Platform SDK, etc.
Since only relatively few packages actually need to _parse_ source
addresses -- with most either not caring about them at all or only
consuming addresses that were already parsed by someone else -- we'll
move the parser functions into their own package, while keeping the
resulting address types in package addrs.
This does still retain the package addrs dependency on external module
github.com/hashicorp/terraform-registry-address, which is not ideal but
that one at least has a relatively shallow dependency subgraph, so there's
not so much urgency to tidy that one.
2024-03-13 18:44:36 -04:00
|
|
|
//
|
|
|
|
|
// The parser for this address type lives in package moduleaddrs, because remote
|
|
|
|
|
// module source address parsing depends on go-getter and that's too heavy a
|
|
|
|
|
// dependency to impose on everything that imports this package addrs.
|
2021-05-27 15:24:45 -04:00
|
|
|
type ModuleSourceRemote struct {
|
2022-07-08 09:46:29 -04:00
|
|
|
// Package is the address of the remote package that the requested
|
2021-05-27 15:24:45 -04:00
|
|
|
// module belongs to.
|
2022-07-08 09:46:29 -04:00
|
|
|
Package ModulePackage
|
2021-05-27 15:24:45 -04:00
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
//
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s ModuleSourceRemote) moduleSource() {}
|
|
|
|
|
|
|
|
|
|
func (s ModuleSourceRemote) String() string {
|
2022-08-31 12:13:24 -04:00
|
|
|
base := s.Package.String()
|
|
|
|
|
|
2021-05-27 15:24:45 -04:00
|
|
|
if s.Subdir != "" {
|
2022-08-31 12:13:24 -04:00
|
|
|
// Address contains query string
|
|
|
|
|
if strings.Contains(base, "?") {
|
|
|
|
|
parts := strings.SplitN(base, "?", 2)
|
|
|
|
|
return parts[0] + "//" + s.Subdir + "?" + parts[1]
|
|
|
|
|
}
|
|
|
|
|
return base + "//" + s.Subdir
|
2021-05-27 15:24:45 -04:00
|
|
|
}
|
2022-08-31 12:13:24 -04:00
|
|
|
return base
|
2021-05-27 15:24:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s ModuleSourceRemote) ForDisplay() string {
|
|
|
|
|
// The two string representations are identical for this address type.
|
|
|
|
|
// This isn't really entirely true to the idea of "ForDisplay" since
|
|
|
|
|
// it'll often include some additional components added in by the
|
|
|
|
|
// go-getter detectors, but we don't have any function to turn a
|
|
|
|
|
// "detected" string back into an idiomatic shorthand the user might've
|
|
|
|
|
// entered.
|
|
|
|
|
return s.String()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FromRegistry can be called on a remote source address that was returned
|
|
|
|
|
// from a module registry, passing in the original registry source address
|
|
|
|
|
// that the registry was asked about, in order to get the effective final
|
|
|
|
|
// remote source address.
|
|
|
|
|
//
|
|
|
|
|
// Specifically, this method handles the situations where one or both of
|
|
|
|
|
// the two addresses contain subdirectory paths, combining both when necessary
|
|
|
|
|
// in order to ensure that both the registry's given path and the user's
|
|
|
|
|
// given path are both respected.
|
|
|
|
|
//
|
|
|
|
|
// This will return nonsense if given a registry address other than the one
|
|
|
|
|
// that generated the reciever via a registry lookup.
|
|
|
|
|
func (s ModuleSourceRemote) FromRegistry(given ModuleSourceRegistry) ModuleSourceRemote {
|
|
|
|
|
ret := s // not a pointer, so this is a shallow copy
|
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
case s.Subdir != "" && given.Subdir != "":
|
|
|
|
|
ret.Subdir = path.Join(s.Subdir, given.Subdir)
|
|
|
|
|
case given.Subdir != "":
|
|
|
|
|
ret.Subdir = given.Subdir
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
}
|