2019-01-09 01:16:58 +01:00
|
|
|
package modsdir
|
2018-02-10 00:32:49 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2019-01-09 01:16:58 +01:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2019-01-09 03:39:14 +01:00
|
|
|
"log"
|
2018-02-10 00:32:49 +01:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2020-02-24 23:42:32 +01:00
|
|
|
"strings"
|
2018-02-10 00:32:49 +01:00
|
|
|
|
|
|
|
version "github.com/hashicorp/go-version"
|
2018-05-01 20:53:07 +02:00
|
|
|
|
2021-05-17 21:00:50 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
2018-02-10 00:32:49 +01:00
|
|
|
)
|
|
|
|
|
2019-01-09 01:16:58 +01:00
|
|
|
// Record represents some metadata about an installed module, as part
|
|
|
|
// of a ModuleManifest.
|
|
|
|
type Record struct {
|
2018-02-10 00:32:49 +01:00
|
|
|
// Key is a unique identifier for this particular module, based on its
|
|
|
|
// position within the static module tree.
|
|
|
|
Key string `json:"Key"`
|
|
|
|
|
|
|
|
// SourceAddr is the source address given for this module in configuration.
|
|
|
|
// This is used only to detect if the source was changed in configuration
|
|
|
|
// since the module was last installed, which means that the installer
|
|
|
|
// must re-install it.
|
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
|
|
|
//
|
|
|
|
// This should always be the result of calling method String on an
|
|
|
|
// addrs.ModuleSource value, to get a suitably-normalized result.
|
2018-02-10 00:32:49 +01:00
|
|
|
SourceAddr string `json:"Source"`
|
|
|
|
|
|
|
|
// Version is the exact version of the module, which results from parsing
|
|
|
|
// VersionStr. nil for un-versioned modules.
|
|
|
|
Version *version.Version `json:"-"`
|
|
|
|
|
|
|
|
// VersionStr is the version specifier string. This is used only for
|
|
|
|
// serialization in snapshots and should not be accessed or updated
|
|
|
|
// by any other codepaths; use "Version" instead.
|
2018-02-13 23:40:53 +01:00
|
|
|
VersionStr string `json:"Version,omitempty"`
|
2018-02-10 00:32:49 +01:00
|
|
|
|
|
|
|
// Dir is the path to the local directory where the module is installed.
|
|
|
|
Dir string `json:"Dir"`
|
|
|
|
}
|
|
|
|
|
2019-01-09 01:16:58 +01:00
|
|
|
// Manifest is a map used to keep track of the filesystem locations
|
2018-02-10 00:32:49 +01:00
|
|
|
// and other metadata about installed modules.
|
|
|
|
//
|
|
|
|
// The configuration loader refers to this, while the module installer updates
|
|
|
|
// it to reflect any changes to the installed modules.
|
2019-01-09 01:16:58 +01:00
|
|
|
type Manifest map[string]Record
|
2018-02-10 00:32:49 +01:00
|
|
|
|
2019-01-09 01:16:58 +01:00
|
|
|
func (m Manifest) ModuleKey(path addrs.Module) string {
|
2020-02-24 23:42:32 +01:00
|
|
|
if len(path) == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return strings.Join([]string(path), ".")
|
|
|
|
|
2018-02-10 00:32:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// manifestSnapshotFile is an internal struct used only to assist in our JSON
|
2019-01-09 01:16:58 +01:00
|
|
|
// serialization of manifest snapshots. It should not be used for any other
|
|
|
|
// purpose.
|
2018-02-10 00:32:49 +01:00
|
|
|
type manifestSnapshotFile struct {
|
2019-01-09 01:16:58 +01:00
|
|
|
Records []Record `json:"Modules"`
|
2018-02-10 00:32:49 +01:00
|
|
|
}
|
|
|
|
|
2019-01-09 01:16:58 +01:00
|
|
|
func ReadManifestSnapshot(r io.Reader) (Manifest, error) {
|
|
|
|
src, err := ioutil.ReadAll(r)
|
2018-02-10 00:32:49 +01:00
|
|
|
if err != nil {
|
2019-01-09 01:16:58 +01:00
|
|
|
return nil, err
|
2018-02-10 00:32:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(src) == 0 {
|
|
|
|
// This should never happen, but we'll tolerate it as if it were
|
|
|
|
// a valid empty JSON object.
|
2019-01-09 01:16:58 +01:00
|
|
|
return make(Manifest), nil
|
2018-02-10 00:32:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var read manifestSnapshotFile
|
|
|
|
err = json.Unmarshal(src, &read)
|
2020-06-04 16:26:26 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error unmarshalling snapshot: %v", err)
|
|
|
|
}
|
2019-01-09 01:16:58 +01:00
|
|
|
new := make(Manifest)
|
2018-02-10 00:32:49 +01:00
|
|
|
for _, record := range read.Records {
|
|
|
|
if record.VersionStr != "" {
|
|
|
|
record.Version, err = version.NewVersion(record.VersionStr)
|
|
|
|
if err != nil {
|
2019-01-09 01:16:58 +01:00
|
|
|
return nil, fmt.Errorf("invalid version %q for %s: %s", record.VersionStr, record.Key, err)
|
2018-02-10 00:32:49 +01:00
|
|
|
}
|
|
|
|
}
|
2020-01-15 05:06:42 +01:00
|
|
|
|
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
|
|
|
// Historically we didn't normalize the module source addresses when
|
|
|
|
// writing them into the manifest, and so we'll make a best effort
|
|
|
|
// to normalize them back in on read so that we can just gracefully
|
|
|
|
// upgrade on the next "terraform init".
|
|
|
|
if record.SourceAddr != "" {
|
|
|
|
if addr, err := addrs.ParseModuleSource(record.SourceAddr); err == nil {
|
|
|
|
// This is a best effort sort of thing. If the source
|
|
|
|
// address isn't valid then we'll just leave it as-is
|
|
|
|
// and let another component detect that downstream,
|
|
|
|
// to preserve the old behavior in that case.
|
|
|
|
record.SourceAddr = addr.String()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-17 20:14:17 +01:00
|
|
|
// Ensure Windows is using the proper modules path format after
|
|
|
|
// reading the modules manifest Dir records
|
|
|
|
record.Dir = filepath.FromSlash(record.Dir)
|
2020-01-15 05:06:42 +01:00
|
|
|
|
2018-02-10 00:32:49 +01:00
|
|
|
if _, exists := new[record.Key]; exists {
|
|
|
|
// This should never happen in any valid file, so we'll catch it
|
|
|
|
// and report it to avoid confusing/undefined behavior if the
|
|
|
|
// snapshot file was edited incorrectly outside of Terraform.
|
2019-01-09 01:16:58 +01:00
|
|
|
return nil, fmt.Errorf("snapshot file contains two records for path %s", record.Key)
|
2018-02-10 00:32:49 +01:00
|
|
|
}
|
|
|
|
new[record.Key] = record
|
|
|
|
}
|
2019-01-09 01:16:58 +01:00
|
|
|
return new, nil
|
|
|
|
}
|
2018-02-10 00:32:49 +01:00
|
|
|
|
2019-01-09 01:16:58 +01:00
|
|
|
func ReadManifestSnapshotForDir(dir string) (Manifest, error) {
|
|
|
|
fn := filepath.Join(dir, ManifestSnapshotFilename)
|
|
|
|
r, err := os.Open(fn)
|
|
|
|
if err != nil {
|
2019-01-09 03:39:14 +01:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return make(Manifest), nil // missing file is okay and treated as empty
|
|
|
|
}
|
2019-01-09 01:16:58 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return ReadManifestSnapshot(r)
|
2018-02-10 00:32:49 +01:00
|
|
|
}
|
|
|
|
|
2019-01-09 01:16:58 +01:00
|
|
|
func (m Manifest) WriteSnapshot(w io.Writer) error {
|
2018-02-10 00:32:49 +01:00
|
|
|
var write manifestSnapshotFile
|
|
|
|
|
2019-01-09 01:16:58 +01:00
|
|
|
for _, record := range m {
|
2018-02-10 00:32:49 +01:00
|
|
|
// Make sure VersionStr is in sync with Version, since we encourage
|
|
|
|
// callers to manipulate Version and ignore VersionStr.
|
2018-02-13 23:40:53 +01:00
|
|
|
if record.Version != nil {
|
|
|
|
record.VersionStr = record.Version.String()
|
|
|
|
} else {
|
|
|
|
record.VersionStr = ""
|
|
|
|
}
|
2020-01-15 04:42:00 +01:00
|
|
|
|
|
|
|
// Ensure Dir is written in a format that can be read by Linux and
|
2020-01-17 20:14:17 +01:00
|
|
|
// Windows nodes for remote and apply compatibility
|
|
|
|
record.Dir = filepath.ToSlash(record.Dir)
|
2018-02-10 00:32:49 +01:00
|
|
|
write.Records = append(write.Records, record)
|
|
|
|
}
|
|
|
|
|
|
|
|
src, err := json.Marshal(write)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-01-09 01:16:58 +01:00
|
|
|
_, err = w.Write(src)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m Manifest) WriteSnapshotToDir(dir string) error {
|
|
|
|
fn := filepath.Join(dir, ManifestSnapshotFilename)
|
2019-01-09 03:39:14 +01:00
|
|
|
log.Printf("[TRACE] modsdir: writing modules manifest to %s", fn)
|
2019-01-09 01:16:58 +01:00
|
|
|
w, err := os.Create(fn)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return m.WriteSnapshot(w)
|
2018-02-10 00:32:49 +01:00
|
|
|
}
|