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