-
Notifications
You must be signed in to change notification settings - Fork 287
/
registry.go
136 lines (125 loc) · 4.24 KB
/
registry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package load
import (
"fmt"
"io"
"net/http"
"os"
"path"
"path/filepath"
"golang.org/x/mod/module"
"golang.org/x/mod/zip"
"cuelang.org/go/cue"
"cuelang.org/go/cue/errors"
"cuelang.org/go/cue/parser"
"cuelang.org/go/cue/token"
"cuelang.org/go/internal/core/runtime"
)
// registryClient implements the protocol for talking to
// the registry server.
type registryClient struct {
// TODO caching
registryURL string
cacheDir string
}
// newRegistryClient returns a registry client that talks to
// the given base URL and stores downloaded module information
// in the given cache directory. It assumes that information
// in the registry is immutable, so if it's in the cache, a module
// will not be downloaded again.
func newRegistryClient(registryURL, cacheDir string) *registryClient {
return ®istryClient{
registryURL: registryURL,
cacheDir: cacheDir,
}
}
// fetchModFile returns the parsed contents of the cue.mod/module.cue file
// for the given module.
func (c *registryClient) fetchModFile(m module.Version) (*modFile, error) {
data, err := c.fetchRawModFile(m)
if err != nil {
return nil, err
}
mf, err := parseModuleFile(data, path.Join(m.Path, "cue.mod/module.cue"))
if err != nil {
return nil, err
}
return mf, nil
}
// fetchModFile returns the contents of the cue.mod/module.cue file
// for the given module without parsing it.
func (c *registryClient) fetchRawModFile(m module.Version) ([]byte, error) {
resp, err := http.Get(c.registryURL + "/" + m.Path + "/@v/" + m.Version + ".mod")
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("cannot get HTTP response body: %v", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("module.cue HTTP GET request failed: %s", body)
}
return body, nil
}
// getModContents downloads the module with the given version
// and returns the directory where it's stored.
func (c *registryClient) getModContents(m module.Version) (string, error) {
modPath := filepath.Join(c.cacheDir, fmt.Sprintf("%s@%s", m.Path, m.Version))
if _, err := os.Stat(modPath); err == nil {
return modPath, nil
}
// TODO synchronize parallel invocations
resp, err := http.Get(c.registryURL + "/" + m.Path + "/@v/" + m.Version + ".zip")
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("module.cue HTTP GET request failed: %s", body)
}
zipfile := filepath.Join(c.cacheDir, m.String()+".zip")
if err := os.MkdirAll(filepath.Dir(zipfile), 0o777); err != nil {
return "", fmt.Errorf("cannot create parent directory for zip file: %v", err)
}
f, err := os.Create(zipfile)
if err != nil {
return "", fmt.Errorf("cannot create zipfile: %v", err)
}
defer f.Close() // TODO check error on close
if _, err := io.Copy(f, resp.Body); err != nil {
return "", fmt.Errorf("cannot copy data to zip file %q: %v", zipfile, err)
}
if err := zip.Unzip(modPath, m, zipfile); err != nil {
return "", fmt.Errorf("cannot unzip %v: %v", m, err)
}
return modPath, nil
}
// parseModuleFile parses a cue.mod/module.cue file.
// TODO move this to be closer to the modFile type definition.
func parseModuleFile(data []byte, filename string) (*modFile, error) {
file, err := parser.ParseFile(filename, data)
if err != nil {
return nil, errors.Wrapf(err, token.NoPos, "invalid module.cue file %q", data)
}
// TODO disallow non-data-mode CUE.
ctx := (*cue.Context)(runtime.New())
schemav := ctx.CompileBytes(moduleSchema, cue.Filename("$cueroot/cue/load/moduleschema.cue"))
if err := schemav.Validate(); err != nil {
return nil, errors.Wrapf(err, token.NoPos, "internal error: invalid CUE module.cue schema")
}
v := ctx.BuildFile(file)
if err := v.Validate(cue.Concrete(true)); err != nil {
return nil, errors.Wrapf(err, token.NoPos, "invalid module.cue file")
}
v = v.Unify(schemav)
if err := v.Validate(); err != nil {
return nil, errors.Wrapf(err, token.NoPos, "invalid module.cue file")
}
var mf modFile
if err := v.Decode(&mf); err != nil {
return nil, errors.Wrapf(err, token.NoPos, "internal error: cannot decode into modFile struct (\nfile %q\ncontents %q\nvalue %#v\n)", filename, data, v)
}
return &mf, nil
}