]>
Commit | Line | Data |
---|---|---|
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 | if _, exists := new[record.Key]; exists { | |
85 | // This should never happen in any valid file, so we'll catch it | |
86 | // and report it to avoid confusing/undefined behavior if the | |
87 | // snapshot file was edited incorrectly outside of Terraform. | |
88 | return nil, fmt.Errorf("snapshot file contains two records for path %s", record.Key) | |
89 | } | |
90 | new[record.Key] = record | |
91 | } | |
92 | return new, nil | |
93 | } | |
94 | ||
95 | func ReadManifestSnapshotForDir(dir string) (Manifest, error) { | |
96 | fn := filepath.Join(dir, ManifestSnapshotFilename) | |
97 | r, err := os.Open(fn) | |
98 | if err != nil { | |
99 | if os.IsNotExist(err) { | |
100 | return make(Manifest), nil // missing file is okay and treated as empty | |
101 | } | |
102 | return nil, err | |
103 | } | |
104 | return ReadManifestSnapshot(r) | |
105 | } | |
106 | ||
107 | func (m Manifest) WriteSnapshot(w io.Writer) error { | |
108 | var write manifestSnapshotFile | |
109 | ||
110 | for _, record := range m { | |
111 | // Make sure VersionStr is in sync with Version, since we encourage | |
112 | // callers to manipulate Version and ignore VersionStr. | |
113 | if record.Version != nil { | |
114 | record.VersionStr = record.Version.String() | |
115 | } else { | |
116 | record.VersionStr = "" | |
117 | } | |
118 | write.Records = append(write.Records, record) | |
119 | } | |
120 | ||
121 | src, err := json.Marshal(write) | |
122 | if err != nil { | |
123 | return err | |
124 | } | |
125 | ||
126 | _, err = w.Write(src) | |
127 | return err | |
128 | } | |
129 | ||
130 | func (m Manifest) WriteSnapshotToDir(dir string) error { | |
131 | fn := filepath.Join(dir, ManifestSnapshotFilename) | |
132 | log.Printf("[TRACE] modsdir: writing modules manifest to %s", fn) | |
133 | w, err := os.Create(fn) | |
134 | if err != nil { | |
135 | return err | |
136 | } | |
137 | return m.WriteSnapshot(w) | |
138 | } |