README
¶
terraform-registry-address
This module enables parsing, comparison and canonical representation of
Terraform Registry provider addresses
(such as registry.terraform.io/grafana/grafana
or hashicorp/aws
)
and module addresses (such as hashicorp/subnets/cidr
).
Provider addresses can be found in
terraform show -json <FILE>
(full_name
)terraform version -json
(provider_selections
)terraform providers schema -json
(keys ofprovider_schemas
)- within
required_providers
block in Terraform configuration (*.tf
) - Terraform CLI configuration file
- Plugin reattach configurations
Module addresses can be found within source
argument
of module
block in Terraform configuration (*.tf
)
and parts of the address (namespace and name) in the Registry API.
Compatibility
The module assumes compatibility with Terraform v0.12 and later, which have the mentioned JSON output produced by corresponding CLI flags.
We recommend carefully reading the ambigouous provider addresses
section below which may impact versions 0.12
and 0.13
.
Related Libraries
Other libraries which may help with consuming most of the above Terraform outputs in automation:
Usage
Provider
pAddr, err := ParseProviderSource("hashicorp/aws")
if err != nil {
// deal with error
}
// pAddr == Provider{
// Type: "aws",
// Namespace: "hashicorp",
// Hostname: DefaultProviderRegistryHost,
// }
Module
mAddr, err := ParseModuleSource("hashicorp/consul/aws//modules/consul-cluster")
if err != nil {
// deal with error
}
// mAddr == Module{
// Package: ModulePackage{
// Host: DefaultProviderRegistryHost,
// Namespace: "hashicorp",
// Name: "consul",
// TargetSystem: "aws",
// },
// Subdir: "modules/consul-cluster",
// },
Other Module Address Formats
Modules can also be sourced from other sources
and these other sources (outside of Terraform Registry)
have different address formats, such as ./local
or
github.com/hashicorp/example
.
This library does not recognize such other address formats and it will return error upon parsing these.
Ambiguous Provider Addresses
Qualified addresses with namespace (such as hashicorp/aws
)
are used exclusively in all recent versions (0.14+
) of Terraform.
If you only work with Terraform v0.14.0+
configuration/output, you may
safely ignore the rest of this section and related part of the API.
There are a few types of ambiguous addresses you may comes accross:
- Terraform
v0.12
uses "namespace-less address", such asaws
. - Terraform
v0.13
may use-
as a placeholder for the unknown namespace, resulting in address such as-/aws
. - Terraform
v0.14+
configuration still allows ambiguous providers throughprovider "<NAME>" {}
block without corresponding entry insiderequired_providers
, but these providers are always resolved ashashicorp/<NAME>
and all JSON outputs only use that resolved address.
Both ambiguous address formats are accepted by ParseProviderSource()
pAddr, err := ParseProviderSource("aws")
if err != nil {
// deal with error
}
// pAddr == Provider{
// Type: "aws",
// Namespace: UnknownProviderNamespace, // "?"
// Hostname: DefaultProviderRegistryHost, // "registry.terraform.io"
// }
pAddr.HasKnownNamespace() // == false
pAddr.IsLegacy() // == false
pAddr, err := ParseProviderSource("-/aws")
if err != nil {
// deal with error
}
// pAddr == Provider{
// Type: "aws",
// Namespace: LegacyProviderNamespace, // "-"
// Hostname: DefaultProviderRegistryHost, // "registry.terraform.io"
// }
pAddr.HasKnownNamespace() // == true
pAddr.IsLegacy() // == true
However NewProvider()
will panic if you pass an empty namespace
or any placeholder indicating unknown namespace.
NewProvider(DefaultProviderRegistryHost, "", "aws") // panic
NewProvider(DefaultProviderRegistryHost, "-", "aws") // panic
NewProvider(DefaultProviderRegistryHost, "?", "aws") // panic
If you come across an ambiguous address, you should resolve it to a fully qualified one and use that one instead.
Resolving Ambiguous Address
The Registry API provides the safest way of resolving an ambiguous address.
# grafana (redirected to its own namespace)
$ curl -s https://registry.terraform.io/v1/providers/-/grafana/versions | jq '(.id, .moved_to)'
"terraform-providers/grafana"
"grafana/grafana"
# aws (provider without redirection)
$ curl -s https://registry.terraform.io/v1/providers/-/aws/versions | jq '(.id, .moved_to)'
"hashicorp/aws"
null
When you cache results, ensure you have invalidation mechanism in place as target (migrated) namespace may change.
terraform
provider
Like any other legacy address terraform
is also ambiguous. Such address may
(most unlikely) represent a custom-built provider called terraform
,
or the now archived hashicorp/terraform
provider in the registry,
or (most likely) the terraform
provider built into 0.11+, which is
represented via a dedicated FQN of terraform.io/builtin/terraform
in 0.13+.
You may be able to differentiate between these different providers if you know the version of Terraform.
Alternatively you may just treat the address as the builtin provider, i.e. assume all of its logic including schema is contained within Terraform Core.
In such case you should construct the address in the following way
pAddr := NewProvider(BuiltInProviderHost, BuiltInProviderNamespace, "terraform")
Documentation
¶
Index ¶
- Constants
- func MustParseProviderPart(given string) string
- func ParseProviderPart(given string) (string, error)
- func ValidateProviderAddress(raw string) error
- type Module
- type ModulePackage
- type ParserError
- type Provider
- func (pt Provider) Equals(other Provider) bool
- func (pt Provider) ForDisplay() string
- func (pt Provider) HasKnownNamespace() bool
- func (pt Provider) IsBuiltIn() bool
- func (pt Provider) IsLegacy() bool
- func (pt Provider) IsZero() bool
- func (pt Provider) LegacyString() string
- func (pt Provider) LessThan(other Provider) bool
- func (pt Provider) String() string
Examples ¶
Constants ¶
const BuiltInProviderHost = svchost.Hostname("terraform.io")
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 BuiltInProviderNamespace = "builtin"
BuiltInProviderNamespace is the provider namespace used for "built-in" providers. Built-in provider addresses must also have their hostname set to BuiltInProviderHost in order to be considered as built-in.
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 DefaultModuleRegistryHost = svchost.Hostname("registry.terraform.io")
DefaultModuleRegistryHost is the hostname used for registry-based module source addresses that do not have an explicit hostname.
const DefaultProviderRegistryHost = svchost.Hostname("registry.terraform.io")
DefaultProviderRegistryHost is the hostname used for provider addresses that do not have an explicit hostname.
const LegacyProviderNamespace = "-"
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 DefaultProviderRegistryHost because that host owns the mapping from legacy name to FQN. This may be produced by Terraform 0.13.
const UnknownProviderNamespace = "?"
UnknownProviderNamespace is the special string used to indicate unknown namespace, e.g. in "aws". This is equivalent to LegacyProviderNamespace for <0.12 style address. This namespace would never be produced by Terraform itself explicitly, it is only an internal placeholder.
Variables ¶
This section is empty.
Functions ¶
func MustParseProviderPart ¶
MustParseProviderPart is a wrapper around ParseProviderPart that panics if it returns an error.
func ParseProviderPart ¶
ParseProviderPart processes an addrs.Provider namespace or type string provided by an end-user, producing a normalized version if possible or an error if the string contains invalid characters.
A provider part is processed in the same way as an individual label in a DNS domain name: it is transformed to lowercase per the usual DNS case mapping and normalization rules and may contain only letters, digits, and dashes. Additionally, dashes may not appear at the start or end of the string.
These restrictions are intended to allow these names to appear in fussy contexts such as directory/file names on case-insensitive filesystems, repository names on GitHub, etc. We're using the DNS rules in particular, rather than some similar rules defined locally, because the hostname part of an addrs.Provider is already a hostname and it's ideal to use exactly the same case folding and normalization rules for all of the parts.
In practice a provider type string conventionally does not contain dashes either. Such names are permitted, but providers with such type names will be hard to use because their resource type names will not be able to contain the provider type name and thus each resource will need an explicit provider address specified. (A real-world example of such a provider is the "google-beta" variant of the GCP provider, which has resource types that start with the "google_" prefix instead.)
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 ValidateProviderAddress ¶
ValidateProviderAddress returns error if the given address is not FQN, that is if it is missing any of the three components from hostname/namespace/name.
Types ¶
type Module ¶
type Module struct { // Package 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 // Package in order to find the actual package location. Package ModulePackage // 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 }
Module is representing a module listed in a Terraform module registry.
func MustParseModuleSource ¶
MustParseModuleSource is a wrapper around ParseModuleSource that panics if it returns an error.
func ParseModuleSource ¶
ParseModuleSource only accepts module registry addresses, and will reject any other address type.
Example ¶
Output: tfaddr.Module{Package:tfaddr.ModulePackage{Host:svchost.Hostname("registry.terraform.io"), Namespace:"hashicorp", Name:"consul", TargetSystem:"aws"}, Subdir:"modules/consul-cluster"}
func (Module) ForDisplay ¶
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.
func (Module) String ¶
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.
type ModulePackage ¶
type ModulePackage struct { Host svchost.Hostname Namespace string Name string TargetSystem string }
A ModulePackage is an extra indirection over a ModulePackage where we use a module registry to translate a more symbolic address (and associated version constraint given out of band) into a physical source location.
ModulePackage is distinct from ModulePackage because they have disjoint use-cases: registry package addresses are only used to query a 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.
func (ModulePackage) ForDisplay ¶
func (s ModulePackage) ForDisplay() string
func (ModulePackage) ForRegistryProtocol ¶
func (s ModulePackage) ForRegistryProtocol() string
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 (ModulePackage) String ¶
func (s ModulePackage) String() string
type ParserError ¶
func (*ParserError) Error ¶
func (pe *ParserError) Error() string
type Provider ¶
Provider encapsulates a single provider type. In the future this will be extended to include additional fields including Namespace and SourceHost
func MustParseProviderSource ¶
MustParseProviderSource is a wrapper around ParseProviderSource that panics if it returns an error.
func NewProvider ¶
NewProvider constructs a provider address from its parts, and normalizes the namespace and type parts to lowercase using unicode case folding rules so that resulting addrs.Provider values can be compared using standard Go equality rules (==).
The hostname is given as a svchost.Hostname, which is required by the contract of that type to have already been normalized for equality testing.
This function will panic if the given namespace or type name are not valid. When accepting namespace or type values from outside the program, use ParseProviderPart first to check that the given value is valid.
func ParseProviderSource ¶
ParseProviderSource parses the source attribute and returns a provider. This is intended primarily to parse the FQN-like strings returned by terraform-config-inspect.
The following are valid source string formats:
name namespace/name hostname/namespace/name
"name"-only format is parsed as -/name (i.e. legacy namespace) requiring further identification of the namespace via Registry API
Example ¶
Output: tfaddr.Provider{Type:"aws", Namespace:"hashicorp", Hostname:svchost.Hostname("registry.terraform.io")}
func (Provider) Equals ¶
Equals returns true if the receiver and other provider have the same attributes.
func (Provider) ForDisplay ¶
ForDisplay returns a user-friendly FQN string, simplified for readability. If the provider is using the default hostname, the hostname is omitted.
func (Provider) HasKnownNamespace ¶
HasKnownNamespace returns true if the provider namespace is known (also if it is legacy namespace)
func (Provider) IsBuiltIn ¶
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 (Provider) IsZero ¶
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 (Provider) LegacyString ¶
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 (Provider) LessThan ¶
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.