github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/internal/modsdir/manifest.go (about) 1 package modsdir 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "log" 9 "os" 10 "path/filepath" 11 12 version "github.com/hashicorp/go-version" 13 14 "github.com/hashicorp/terraform/addrs" 15 ) 16 17 // Record represents some metadata about an installed module, as part 18 // of a ModuleManifest. 19 type Record struct { 20 // Key is a unique identifier for this particular module, based on its 21 // position within the static module tree. 22 Key string `json:"Key"` 23 24 // SourceAddr is the source address given for this module in configuration. 25 // This is used only to detect if the source was changed in configuration 26 // since the module was last installed, which means that the installer 27 // must re-install it. 28 SourceAddr string `json:"Source"` 29 30 // Version is the exact version of the module, which results from parsing 31 // VersionStr. nil for un-versioned modules. 32 Version *version.Version `json:"-"` 33 34 // VersionStr is the version specifier string. This is used only for 35 // serialization in snapshots and should not be accessed or updated 36 // by any other codepaths; use "Version" instead. 37 VersionStr string `json:"Version,omitempty"` 38 39 // Dir is the path to the local directory where the module is installed. 40 Dir string `json:"Dir"` 41 } 42 43 // Manifest is a map used to keep track of the filesystem locations 44 // and other metadata about installed modules. 45 // 46 // The configuration loader refers to this, while the module installer updates 47 // it to reflect any changes to the installed modules. 48 type Manifest map[string]Record 49 50 func (m Manifest) ModuleKey(path addrs.Module) string { 51 return path.String() 52 } 53 54 // manifestSnapshotFile is an internal struct used only to assist in our JSON 55 // serialization of manifest snapshots. It should not be used for any other 56 // purpose. 57 type manifestSnapshotFile struct { 58 Records []Record `json:"Modules"` 59 } 60 61 func ReadManifestSnapshot(r io.Reader) (Manifest, error) { 62 src, err := ioutil.ReadAll(r) 63 if err != nil { 64 return nil, err 65 } 66 67 if len(src) == 0 { 68 // This should never happen, but we'll tolerate it as if it were 69 // a valid empty JSON object. 70 return make(Manifest), nil 71 } 72 73 var read manifestSnapshotFile 74 err = json.Unmarshal(src, &read) 75 76 new := make(Manifest) 77 for _, record := range read.Records { 78 if record.VersionStr != "" { 79 record.Version, err = version.NewVersion(record.VersionStr) 80 if err != nil { 81 return nil, fmt.Errorf("invalid version %q for %s: %s", record.VersionStr, record.Key, err) 82 } 83 } 84 85 // Ensure Windows is using the proper modules path format after 86 // reading the modules manifest Dir records 87 record.Dir = filepath.FromSlash(record.Dir) 88 89 if _, exists := new[record.Key]; exists { 90 // This should never happen in any valid file, so we'll catch it 91 // and report it to avoid confusing/undefined behavior if the 92 // snapshot file was edited incorrectly outside of Terraform. 93 return nil, fmt.Errorf("snapshot file contains two records for path %s", record.Key) 94 } 95 new[record.Key] = record 96 } 97 return new, nil 98 } 99 100 func ReadManifestSnapshotForDir(dir string) (Manifest, error) { 101 fn := filepath.Join(dir, ManifestSnapshotFilename) 102 r, err := os.Open(fn) 103 if err != nil { 104 if os.IsNotExist(err) { 105 return make(Manifest), nil // missing file is okay and treated as empty 106 } 107 return nil, err 108 } 109 return ReadManifestSnapshot(r) 110 } 111 112 func (m Manifest) WriteSnapshot(w io.Writer) error { 113 var write manifestSnapshotFile 114 115 for _, record := range m { 116 // Make sure VersionStr is in sync with Version, since we encourage 117 // callers to manipulate Version and ignore VersionStr. 118 if record.Version != nil { 119 record.VersionStr = record.Version.String() 120 } else { 121 record.VersionStr = "" 122 } 123 124 // Ensure Dir is written in a format that can be read by Linux and 125 // Windows nodes for remote and apply compatibility 126 record.Dir = filepath.ToSlash(record.Dir) 127 write.Records = append(write.Records, record) 128 } 129 130 src, err := json.Marshal(write) 131 if err != nil { 132 return err 133 } 134 135 _, err = w.Write(src) 136 return err 137 } 138 139 func (m Manifest) WriteSnapshotToDir(dir string) error { 140 fn := filepath.Join(dir, ManifestSnapshotFilename) 141 log.Printf("[TRACE] modsdir: writing modules manifest to %s", fn) 142 w, err := os.Create(fn) 143 if err != nil { 144 return err 145 } 146 return m.WriteSnapshot(w) 147 }