]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
1 | package module |
2 | ||
3 | import ( | |
4 | "encoding/json" | |
5 | "fmt" | |
6 | "io/ioutil" | |
7 | "log" | |
8 | "os" | |
9 | "path/filepath" | |
15c0b25d AP |
10 | |
11 | getter "github.com/hashicorp/go-getter" | |
12 | "github.com/hashicorp/terraform/registry" | |
13 | "github.com/hashicorp/terraform/registry/regsrc" | |
14 | "github.com/hashicorp/terraform/svchost/disco" | |
15 | "github.com/mitchellh/cli" | |
16 | ) | |
17 | ||
18 | const manifestName = "modules.json" | |
19 | ||
20 | // moduleManifest is the serialization structure used to record the stored | |
21 | // module's metadata. | |
22 | type moduleManifest struct { | |
23 | Modules []moduleRecord | |
24 | } | |
25 | ||
26 | // moduleRecords represents the stored module's metadata. | |
27 | // This is compared for equality using '==', so all fields needs to remain | |
28 | // comparable. | |
29 | type moduleRecord struct { | |
30 | // Source is the module source string from the config, minus any | |
31 | // subdirectory. | |
32 | Source string | |
33 | ||
34 | // Key is the locally unique identifier for this module. | |
35 | Key string | |
36 | ||
37 | // Version is the exact version string for the stored module. | |
38 | Version string | |
39 | ||
40 | // Dir is the directory name returned by the FileStorage. This is what | |
41 | // allows us to correlate a particular module version with the location on | |
42 | // disk. | |
43 | Dir string | |
44 | ||
45 | // Root is the root directory containing the module. If the module is | |
46 | // unpacked from an archive, and not located in the root directory, this is | |
47 | // used to direct the loader to the correct subdirectory. This is | |
48 | // independent from any subdirectory in the original source string, which | |
49 | // may traverse further into the module tree. | |
50 | Root string | |
51 | ||
52 | // url is the location of the module source | |
53 | url string | |
54 | ||
55 | // Registry is true if this module is sourced from a registry | |
56 | registry bool | |
57 | } | |
58 | ||
59 | // Storage implements methods to manage the storage of modules. | |
60 | // This is used by Tree.Load to query registries, authenticate requests, and | |
61 | // store modules locally. | |
62 | type Storage struct { | |
63 | // StorageDir is the full path to the directory where all modules will be | |
64 | // stored. | |
65 | StorageDir string | |
66 | ||
67 | // Ui is an optional cli.Ui for user output | |
68 | Ui cli.Ui | |
69 | ||
70 | // Mode is the GetMode that will be used for various operations. | |
71 | Mode GetMode | |
72 | ||
73 | registry *registry.Client | |
74 | } | |
75 | ||
76 | // NewStorage returns a new initialized Storage object. | |
77 | func NewStorage(dir string, services *disco.Disco) *Storage { | |
78 | regClient := registry.NewClient(services, nil) | |
79 | ||
80 | return &Storage{ | |
81 | StorageDir: dir, | |
82 | registry: regClient, | |
83 | } | |
84 | } | |
85 | ||
86 | // loadManifest returns the moduleManifest file from the parent directory. | |
87 | func (s Storage) loadManifest() (moduleManifest, error) { | |
88 | manifest := moduleManifest{} | |
89 | ||
90 | manifestPath := filepath.Join(s.StorageDir, manifestName) | |
91 | data, err := ioutil.ReadFile(manifestPath) | |
92 | if err != nil && !os.IsNotExist(err) { | |
93 | return manifest, err | |
94 | } | |
95 | ||
96 | if len(data) == 0 { | |
97 | return manifest, nil | |
98 | } | |
99 | ||
100 | if err := json.Unmarshal(data, &manifest); err != nil { | |
101 | return manifest, err | |
102 | } | |
15c0b25d AP |
103 | return manifest, nil |
104 | } | |
105 | ||
106 | // Store the location of the module, along with the version used and the module | |
107 | // root directory. The storage method loads the entire file and rewrites it | |
108 | // each time. This is only done a few times during init, so efficiency is | |
109 | // not a concern. | |
110 | func (s Storage) recordModule(rec moduleRecord) error { | |
111 | manifest, err := s.loadManifest() | |
112 | if err != nil { | |
113 | // if there was a problem with the file, we will attempt to write a new | |
114 | // one. Any non-data related error should surface there. | |
115 | log.Printf("[WARN] error reading module manifest: %s", err) | |
116 | } | |
117 | ||
118 | // do nothing if we already have the exact module | |
119 | for i, stored := range manifest.Modules { | |
120 | if rec == stored { | |
121 | return nil | |
122 | } | |
123 | ||
124 | // they are not equal, but if the storage path is the same we need to | |
125 | // remove this rec to be replaced. | |
126 | if rec.Dir == stored.Dir { | |
127 | manifest.Modules[i] = manifest.Modules[len(manifest.Modules)-1] | |
128 | manifest.Modules = manifest.Modules[:len(manifest.Modules)-1] | |
129 | break | |
130 | } | |
131 | } | |
132 | ||
15c0b25d AP |
133 | manifest.Modules = append(manifest.Modules, rec) |
134 | ||
135 | js, err := json.Marshal(manifest) | |
136 | if err != nil { | |
137 | panic(err) | |
138 | } | |
139 | ||
140 | manifestPath := filepath.Join(s.StorageDir, manifestName) | |
141 | return ioutil.WriteFile(manifestPath, js, 0644) | |
142 | } | |
143 | ||
144 | // load the manifest from dir, and return all module versions matching the | |
145 | // provided source. Records with no version info will be skipped, as they need | |
146 | // to be uniquely identified by other means. | |
147 | func (s Storage) moduleVersions(source string) ([]moduleRecord, error) { | |
148 | manifest, err := s.loadManifest() | |
149 | if err != nil { | |
150 | return manifest.Modules, err | |
151 | } | |
152 | ||
153 | var matching []moduleRecord | |
154 | ||
155 | for _, m := range manifest.Modules { | |
156 | if m.Source == source && m.Version != "" { | |
157 | log.Printf("[DEBUG] found local version %q for module %s", m.Version, m.Source) | |
158 | matching = append(matching, m) | |
159 | } | |
160 | } | |
161 | ||
162 | return matching, nil | |
163 | } | |
164 | ||
165 | func (s Storage) moduleDir(key string) (string, error) { | |
166 | manifest, err := s.loadManifest() | |
167 | if err != nil { | |
168 | return "", err | |
169 | } | |
170 | ||
171 | for _, m := range manifest.Modules { | |
172 | if m.Key == key { | |
173 | return m.Dir, nil | |
174 | } | |
175 | } | |
176 | ||
177 | return "", nil | |
178 | } | |
179 | ||
180 | // return only the root directory of the module stored in dir. | |
181 | func (s Storage) getModuleRoot(dir string) (string, error) { | |
182 | manifest, err := s.loadManifest() | |
183 | if err != nil { | |
184 | return "", err | |
185 | } | |
186 | ||
187 | for _, mod := range manifest.Modules { | |
188 | if mod.Dir == dir { | |
189 | return mod.Root, nil | |
190 | } | |
191 | } | |
192 | return "", nil | |
193 | } | |
194 | ||
195 | // record only the Root directory for the module stored at dir. | |
196 | func (s Storage) recordModuleRoot(dir, root string) error { | |
197 | rec := moduleRecord{ | |
198 | Dir: dir, | |
199 | Root: root, | |
200 | } | |
201 | ||
202 | return s.recordModule(rec) | |
203 | } | |
204 | ||
205 | func (s Storage) output(msg string) { | |
206 | if s.Ui == nil || s.Mode == GetModeNone { | |
207 | return | |
208 | } | |
209 | s.Ui.Output(msg) | |
210 | } | |
211 | ||
212 | func (s Storage) getStorage(key string, src string) (string, bool, error) { | |
213 | storage := &getter.FolderStorage{ | |
214 | StorageDir: s.StorageDir, | |
215 | } | |
216 | ||
217 | log.Printf("[DEBUG] fetching module from %s", src) | |
218 | ||
219 | // Get the module with the level specified if we were told to. | |
220 | if s.Mode > GetModeNone { | |
221 | log.Printf("[DEBUG] fetching %q with key %q", src, key) | |
222 | if err := storage.Get(key, src, s.Mode == GetModeUpdate); err != nil { | |
223 | return "", false, err | |
224 | } | |
225 | } | |
226 | ||
227 | // Get the directory where the module is. | |
228 | dir, found, err := storage.Dir(key) | |
229 | log.Printf("[DEBUG] found %q in %q: %t", src, dir, found) | |
230 | return dir, found, err | |
231 | } | |
232 | ||
233 | // find a stored module that's not from a registry | |
234 | func (s Storage) findModule(key string) (string, error) { | |
235 | if s.Mode == GetModeUpdate { | |
236 | return "", nil | |
237 | } | |
238 | ||
239 | return s.moduleDir(key) | |
240 | } | |
241 | ||
242 | // GetModule fetches a module source into the specified directory. This is used | |
243 | // as a convenience function by the CLI to initialize a configuration. | |
244 | func (s Storage) GetModule(dst, src string) error { | |
245 | // reset this in case the caller was going to re-use it | |
246 | mode := s.Mode | |
247 | s.Mode = GetModeUpdate | |
248 | defer func() { | |
249 | s.Mode = mode | |
250 | }() | |
251 | ||
252 | rec, err := s.findRegistryModule(src, anyVersion) | |
253 | if err != nil { | |
254 | return err | |
255 | } | |
256 | ||
257 | pwd, err := os.Getwd() | |
258 | if err != nil { | |
259 | return err | |
260 | } | |
261 | ||
262 | source := rec.url | |
263 | if source == "" { | |
264 | source, err = getter.Detect(src, pwd, getter.Detectors) | |
265 | if err != nil { | |
266 | return fmt.Errorf("module %s: %s", src, err) | |
267 | } | |
268 | } | |
269 | ||
270 | if source == "" { | |
271 | return fmt.Errorf("module %q not found", src) | |
272 | } | |
273 | ||
274 | return GetCopy(dst, source) | |
275 | } | |
276 | ||
277 | // find a registry module | |
278 | func (s Storage) findRegistryModule(mSource, constraint string) (moduleRecord, error) { | |
279 | rec := moduleRecord{ | |
280 | Source: mSource, | |
281 | } | |
282 | // detect if we have a registry source | |
283 | mod, err := regsrc.ParseModuleSource(mSource) | |
284 | switch err { | |
285 | case nil: | |
286 | //ok | |
287 | case regsrc.ErrInvalidModuleSource: | |
288 | return rec, nil | |
289 | default: | |
290 | return rec, err | |
291 | } | |
292 | rec.registry = true | |
293 | ||
294 | log.Printf("[TRACE] %q is a registry module", mod.Display()) | |
295 | ||
296 | versions, err := s.moduleVersions(mod.String()) | |
297 | if err != nil { | |
298 | log.Printf("[ERROR] error looking up versions for %q: %s", mod.Display(), err) | |
299 | return rec, err | |
300 | } | |
301 | ||
302 | match, err := newestRecord(versions, constraint) | |
303 | if err != nil { | |
304 | log.Printf("[INFO] no matching version for %q<%s>, %s", mod.Display(), constraint, err) | |
305 | } | |
306 | log.Printf("[DEBUG] matched %q version %s for %s", mod, match.Version, constraint) | |
307 | ||
308 | rec.Dir = match.Dir | |
309 | rec.Version = match.Version | |
310 | found := rec.Dir != "" | |
311 | ||
312 | // we need to lookup available versions | |
313 | // Only on Get if it's not found, on unconditionally on Update | |
314 | if (s.Mode == GetModeGet && !found) || (s.Mode == GetModeUpdate) { | |
107c1cdb | 315 | resp, err := s.registry.ModuleVersions(mod) |
15c0b25d AP |
316 | if err != nil { |
317 | return rec, err | |
318 | } | |
319 | ||
320 | if len(resp.Modules) == 0 { | |
321 | return rec, fmt.Errorf("module %q not found in registry", mod.Display()) | |
322 | } | |
323 | ||
324 | match, err := newestVersion(resp.Modules[0].Versions, constraint) | |
325 | if err != nil { | |
326 | return rec, err | |
327 | } | |
328 | ||
329 | if match == nil { | |
330 | return rec, fmt.Errorf("no versions for %q found matching %q", mod.Display(), constraint) | |
331 | } | |
332 | ||
333 | rec.Version = match.Version | |
334 | ||
107c1cdb | 335 | rec.url, err = s.registry.ModuleLocation(mod, rec.Version) |
15c0b25d AP |
336 | if err != nil { |
337 | return rec, err | |
338 | } | |
339 | ||
340 | // we've already validated this by now | |
341 | host, _ := mod.SvcHost() | |
342 | s.output(fmt.Sprintf(" Found version %s of %s on %s", rec.Version, mod.Module(), host.ForDisplay())) | |
343 | ||
344 | } | |
345 | return rec, nil | |
346 | } |