diff --git a/configs/configload/module_mgr.go b/configs/configload/module_mgr.go index 6b2a5199f..3c410eeb7 100644 --- a/configs/configload/module_mgr.go +++ b/configs/configload/module_mgr.go @@ -1,6 +1,10 @@ package configload import ( + "os" + "path/filepath" + + "github.com/hashicorp/terraform/internal/modsdir" "github.com/hashicorp/terraform/registry" "github.com/hashicorp/terraform/svchost/disco" "github.com/spf13/afero" @@ -34,5 +38,39 @@ type moduleMgr struct { // after a set of updates are completed the installer must call // writeModuleManifestSnapshot to persist a snapshot of the manifest // to disk for use on subsequent runs. - manifest moduleManifest + manifest modsdir.Manifest +} + +func (m *moduleMgr) manifestSnapshotPath() string { + return filepath.Join(m.Dir, modsdir.ManifestSnapshotFilename) +} + +// readModuleManifestSnapshot loads a manifest snapshot from the filesystem. +func (m *moduleMgr) readModuleManifestSnapshot() error { + r, err := m.FS.Open(m.manifestSnapshotPath()) + if err != nil { + if os.IsNotExist(err) { + // We'll treat a missing file as an empty manifest + m.manifest = make(modsdir.Manifest) + return nil + } + return err + } + + m.manifest, err = modsdir.ReadManifestSnapshot(r) + return err +} + +// writeModuleManifestSnapshot writes a snapshot of the current manifest +// to the filesystem. +// +// The caller must guarantee no concurrent modifications of the manifest for +// the duration of a call to this function, or the behavior is undefined. +func (m *moduleMgr) writeModuleManifestSnapshot() error { + w, err := m.FS.Create(m.manifestSnapshotPath()) + if err != nil { + return err + } + + return m.manifest.WriteSnapshot(w) } diff --git a/internal/modsdir/doc.go b/internal/modsdir/doc.go new file mode 100644 index 000000000..0d7d664fc --- /dev/null +++ b/internal/modsdir/doc.go @@ -0,0 +1,3 @@ +// Package modsdir is an internal package containing the model types used to +// represent the manifest of modules in a local modules cache directory. +package modsdir diff --git a/configs/configload/module_manifest.go b/internal/modsdir/manifest.go similarity index 60% rename from configs/configload/module_manifest.go rename to internal/modsdir/manifest.go index 0a2348be8..09c45017c 100644 --- a/configs/configload/module_manifest.go +++ b/internal/modsdir/manifest.go @@ -1,8 +1,10 @@ -package configload +package modsdir import ( "encoding/json" "fmt" + "io" + "io/ioutil" "os" "path/filepath" @@ -11,9 +13,9 @@ import ( "github.com/hashicorp/terraform/addrs" ) -// moduleRecord represents some metadata about an installed module, as part -// of a moduleManifest. -type moduleRecord struct { +// Record represents some metadata about an installed module, as part +// of a ModuleManifest. +type Record struct { // Key is a unique identifier for this particular module, based on its // position within the static module tree. Key string `json:"Key"` @@ -37,83 +39,71 @@ type moduleRecord struct { Dir string `json:"Dir"` } -// moduleManifest is a map used to keep track of the filesystem locations +// Manifest is a map used to keep track of the filesystem locations // 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. -type moduleManifest map[string]moduleRecord +type Manifest map[string]Record -func manifestKey(path addrs.Module) string { +func (m Manifest) ModuleKey(path addrs.Module) string { return path.String() } // manifestSnapshotFile is an internal struct used only to assist in our JSON -// serializtion of manifest snapshots. It should not be used for any other -// purposes. +// serialization of manifest snapshots. It should not be used for any other +// purpose. type manifestSnapshotFile struct { - Records []moduleRecord `json:"Modules"` + Records []Record `json:"Modules"` } -const manifestFilename = "modules.json" - -func (m *moduleMgr) manifestSnapshotPath() string { - return filepath.Join(m.Dir, manifestFilename) -} - -// readModuleManifestSnapshot loads a manifest snapshot from the filesystem. -func (m *moduleMgr) readModuleManifestSnapshot() error { - src, err := m.FS.ReadFile(m.manifestSnapshotPath()) +func ReadManifestSnapshot(r io.Reader) (Manifest, error) { + src, err := ioutil.ReadAll(r) if err != nil { - if os.IsNotExist(err) { - // We'll treat a missing file as an empty manifest - m.manifest = make(moduleManifest) - return nil - } - return err + return nil, err } if len(src) == 0 { // This should never happen, but we'll tolerate it as if it were // a valid empty JSON object. - m.manifest = make(moduleManifest) - return nil + return make(Manifest), nil } var read manifestSnapshotFile err = json.Unmarshal(src, &read) - new := make(moduleManifest) + new := make(Manifest) for _, record := range read.Records { if record.VersionStr != "" { record.Version, err = version.NewVersion(record.VersionStr) if err != nil { - return fmt.Errorf("invalid version %q for %s: %s", record.VersionStr, record.Key, err) + return nil, fmt.Errorf("invalid version %q for %s: %s", record.VersionStr, record.Key, err) } } 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. - return fmt.Errorf("snapshot file contains two records for path %s", record.Key) + return nil, fmt.Errorf("snapshot file contains two records for path %s", record.Key) } new[record.Key] = record } - - m.manifest = new - - return nil + return new, nil } -// writeModuleManifestSnapshot writes a snapshot of the current manifest -// to the filesystem. -// -// The caller must guarantee no concurrent modifications of the manifest for -// the duration of a call to this function, or the behavior is undefined. -func (m *moduleMgr) writeModuleManifestSnapshot() error { +func ReadManifestSnapshotForDir(dir string) (Manifest, error) { + fn := filepath.Join(dir, ManifestSnapshotFilename) + r, err := os.Open(fn) + if err != nil { + return nil, err + } + return ReadManifestSnapshot(r) +} + +func (m Manifest) WriteSnapshot(w io.Writer) error { var write manifestSnapshotFile - for _, record := range m.manifest { + for _, record := range m { // Make sure VersionStr is in sync with Version, since we encourage // callers to manipulate Version and ignore VersionStr. if record.Version != nil { @@ -129,5 +119,15 @@ func (m *moduleMgr) writeModuleManifestSnapshot() error { return err } - return m.FS.WriteFile(m.manifestSnapshotPath(), src, os.ModePerm) + _, err = w.Write(src) + return err +} + +func (m Manifest) WriteSnapshotToDir(dir string) error { + fn := filepath.Join(dir, ManifestSnapshotFilename) + w, err := os.Create(fn) + if err != nil { + return err + } + return m.WriteSnapshot(w) } diff --git a/internal/modsdir/paths.go b/internal/modsdir/paths.go new file mode 100644 index 000000000..9ebb52431 --- /dev/null +++ b/internal/modsdir/paths.go @@ -0,0 +1,3 @@ +package modsdir + +const ManifestSnapshotFilename = "modules.json"