mirror of
https://github.com/hashicorp/terraform.git
synced 2026-03-21 10:00:09 -04:00
194 lines
7.4 KiB
Go
194 lines
7.4 KiB
Go
// Copyright IBM Corp. 2014, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package addrs
|
|
|
|
import (
|
|
"path"
|
|
"strings"
|
|
|
|
tfaddr "github.com/hashicorp/terraform-registry-address"
|
|
)
|
|
|
|
// ModuleSource is the general type for all three of the possible module source
|
|
// 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.
|
|
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.
|
|
//
|
|
// 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.
|
|
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 {
|
|
return string(s)
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// 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.
|
|
type ModuleSourceRegistry tfaddr.Module
|
|
|
|
// DefaultModuleRegistryHost is the hostname used for registry-based module
|
|
// source addresses that do not have an explicit hostname.
|
|
const DefaultModuleRegistryHost = tfaddr.DefaultModuleRegistryHost
|
|
|
|
func (s ModuleSourceRegistry) moduleSource() {}
|
|
|
|
func (s ModuleSourceRegistry) String() string {
|
|
if s.Subdir != "" {
|
|
return s.Package.String() + "//" + s.Subdir
|
|
}
|
|
return s.Package.String()
|
|
}
|
|
|
|
func (s ModuleSourceRegistry) ForDisplay() string {
|
|
if s.Subdir != "" {
|
|
return s.Package.ForDisplay() + "//" + s.Subdir
|
|
}
|
|
return s.Package.ForDisplay()
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// 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.
|
|
type ModuleSourceRemote struct {
|
|
// Package is the address of the remote package that the requested
|
|
// module belongs to.
|
|
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.
|
|
//
|
|
// 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 {
|
|
base := s.Package.String()
|
|
|
|
if s.Subdir != "" {
|
|
// Address contains query string
|
|
if strings.Contains(base, "?") {
|
|
parts := strings.SplitN(base, "?", 2)
|
|
return parts[0] + "//" + s.Subdir + "?" + parts[1]
|
|
}
|
|
return base + "//" + s.Subdir
|
|
}
|
|
return base
|
|
}
|
|
|
|
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
|
|
}
|