internal/modsdir: Factor out module manifest models to separate package

This commit is contained in:
Martin Atkins 2019-01-08 16:16:58 -08:00
parent 8ca1fcec51
commit 0ce2add59f
4 changed files with 87 additions and 43 deletions

View File

@ -1,6 +1,10 @@
package configload package configload
import ( import (
"os"
"path/filepath"
"github.com/hashicorp/terraform/internal/modsdir"
"github.com/hashicorp/terraform/registry" "github.com/hashicorp/terraform/registry"
"github.com/hashicorp/terraform/svchost/disco" "github.com/hashicorp/terraform/svchost/disco"
"github.com/spf13/afero" "github.com/spf13/afero"
@ -34,5 +38,39 @@ type moduleMgr struct {
// after a set of updates are completed the installer must call // after a set of updates are completed the installer must call
// writeModuleManifestSnapshot to persist a snapshot of the manifest // writeModuleManifestSnapshot to persist a snapshot of the manifest
// to disk for use on subsequent runs. // 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)
} }

3
internal/modsdir/doc.go Normal file
View File

@ -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

View File

@ -1,8 +1,10 @@
package configload package modsdir
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -11,9 +13,9 @@ import (
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
) )
// moduleRecord represents some metadata about an installed module, as part // Record represents some metadata about an installed module, as part
// of a moduleManifest. // of a ModuleManifest.
type moduleRecord struct { type Record struct {
// Key is a unique identifier for this particular module, based on its // Key is a unique identifier for this particular module, based on its
// position within the static module tree. // position within the static module tree.
Key string `json:"Key"` Key string `json:"Key"`
@ -37,83 +39,71 @@ type moduleRecord struct {
Dir string `json:"Dir"` 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. // and other metadata about installed modules.
// //
// The configuration loader refers to this, while the module installer updates // The configuration loader refers to this, while the module installer updates
// it to reflect any changes to the installed modules. // 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() return path.String()
} }
// manifestSnapshotFile is an internal struct used only to assist in our JSON // manifestSnapshotFile is an internal struct used only to assist in our JSON
// serializtion of manifest snapshots. It should not be used for any other // serialization of manifest snapshots. It should not be used for any other
// purposes. // purpose.
type manifestSnapshotFile struct { type manifestSnapshotFile struct {
Records []moduleRecord `json:"Modules"` Records []Record `json:"Modules"`
} }
const manifestFilename = "modules.json" func ReadManifestSnapshot(r io.Reader) (Manifest, error) {
src, err := ioutil.ReadAll(r)
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())
if err != nil { if err != nil {
if os.IsNotExist(err) { return nil, err
// We'll treat a missing file as an empty manifest
m.manifest = make(moduleManifest)
return nil
}
return err
} }
if len(src) == 0 { if len(src) == 0 {
// This should never happen, but we'll tolerate it as if it were // This should never happen, but we'll tolerate it as if it were
// a valid empty JSON object. // a valid empty JSON object.
m.manifest = make(moduleManifest) return make(Manifest), nil
return nil
} }
var read manifestSnapshotFile var read manifestSnapshotFile
err = json.Unmarshal(src, &read) err = json.Unmarshal(src, &read)
new := make(moduleManifest) new := make(Manifest)
for _, record := range read.Records { for _, record := range read.Records {
if record.VersionStr != "" { if record.VersionStr != "" {
record.Version, err = version.NewVersion(record.VersionStr) record.Version, err = version.NewVersion(record.VersionStr)
if err != nil { 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 { if _, exists := new[record.Key]; exists {
// This should never happen in any valid file, so we'll catch it // This should never happen in any valid file, so we'll catch it
// and report it to avoid confusing/undefined behavior if the // and report it to avoid confusing/undefined behavior if the
// snapshot file was edited incorrectly outside of Terraform. // 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 new[record.Key] = record
} }
return new, nil
m.manifest = new
return nil
} }
// writeModuleManifestSnapshot writes a snapshot of the current manifest func ReadManifestSnapshotForDir(dir string) (Manifest, error) {
// to the filesystem. fn := filepath.Join(dir, ManifestSnapshotFilename)
// r, err := os.Open(fn)
// The caller must guarantee no concurrent modifications of the manifest for if err != nil {
// the duration of a call to this function, or the behavior is undefined. return nil, err
func (m *moduleMgr) writeModuleManifestSnapshot() error { }
return ReadManifestSnapshot(r)
}
func (m Manifest) WriteSnapshot(w io.Writer) error {
var write manifestSnapshotFile var write manifestSnapshotFile
for _, record := range m.manifest { for _, record := range m {
// Make sure VersionStr is in sync with Version, since we encourage // Make sure VersionStr is in sync with Version, since we encourage
// callers to manipulate Version and ignore VersionStr. // callers to manipulate Version and ignore VersionStr.
if record.Version != nil { if record.Version != nil {
@ -129,5 +119,15 @@ func (m *moduleMgr) writeModuleManifestSnapshot() error {
return err 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)
} }

View File

@ -0,0 +1,3 @@
package modsdir
const ManifestSnapshotFilename = "modules.json"