/
archive.go
182 lines (150 loc) · 4.4 KB
/
archive.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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package cmd
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/TierMobility/boring-registry/pkg/module"
"github.com/go-kit/kit/log/level"
"github.com/hashicorp/go-version"
"github.com/pkg/errors"
)
const (
moduleSpecFileName = "boring-registry.hcl"
)
func archiveModules(root string, storage module.Storage) error {
var err error
if flagRecursive {
err = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if fi.Name() != moduleSpecFileName {
return nil
}
return processModule(path, storage)
})
} else {
err = processModule(filepath.Join(root, moduleSpecFileName), storage)
}
return err
}
func processModule(path string, storage module.Storage) error {
spec, err := module.ParseFile(path)
if err != nil {
return err
}
_ = level.Debug(logger).Log(
"msg", "parsed module spec",
"path", path,
"name", spec.Name(),
)
// Check if the module meets version constraints
if versionConstraintsSemver != nil {
ok, err := meetsSemverConstraints(spec)
if err != nil {
return err
} else if !ok {
// Skip the module, as it didn't pass the version constraints
_ = level.Info(logger).Log("msg", "module doesn't meet semver version constraints, skipped", "name", spec.Name())
return nil
}
}
if versionConstraintsRegex != nil {
if !meetsRegexConstraints(spec) {
// Skip the module, as it didn't pass the regex version constraints
_ = level.Info(logger).Log("msg", "module doesn't meet regex version constraints, skipped", "name", spec.Name())
return nil
}
}
ctx := context.Background()
if res, err := storage.GetModule(ctx, spec.Metadata.Namespace, spec.Metadata.Name, spec.Metadata.Provider, spec.Metadata.Version); err == nil {
if flagIgnoreExistingModule {
_ = level.Info(logger).Log(
"msg", "module already exists",
"download_url", res.DownloadURL,
)
return nil
} else {
_ = level.Error(logger).Log(
"msg", "module already exists",
"download_url", res.DownloadURL,
)
return errors.New("module already exists")
}
}
moduleRoot := filepath.Dir(path)
buf, err := archiveModule(moduleRoot)
if err != nil {
return err
}
res, err := storage.UploadModule(ctx, spec.Metadata.Namespace, spec.Metadata.Name, spec.Metadata.Provider, spec.Metadata.Version, buf)
if err != nil {
return err
}
_ = level.Info(logger).Log(
"msg", "module successfully uploaded",
"download_url", res.DownloadURL,
)
return nil
}
func archiveModule(root string) (io.Reader, error) {
buf := new(bytes.Buffer)
// ensure the src actually exists before trying to tar it
if _, err := os.Stat(root); err != nil {
return buf, fmt.Errorf("unable to tar files - %v", err.Error())
}
gw := gzip.NewWriter(buf)
defer gw.Close()
tw := tar.NewWriter(gw)
defer tw.Close()
err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
// return on any error
if err != nil {
return err
}
// return on non-regular files
if !fi.Mode().IsRegular() {
return nil
}
// create a new dir/file header
header, err := tar.FileInfoHeader(fi, fi.Name())
if err != nil {
return err
}
// update the name to correctly reflect the desired destination when untaring
header.Name = strings.TrimPrefix(strings.Replace(path, root, "", -1), string(filepath.Separator))
if err := tw.WriteHeader(header); err != nil {
return err
}
data, err := os.Open(path)
if err != nil {
return err
}
if _, err := io.Copy(tw, data); err != nil {
return err
}
// manually close here after each file operation; deferring would cause each file close
// to wait until all operations have completed.
data.Close()
return nil
})
return buf, err
}
// meetsSemverConstraints checks whether a module version matches the semver version constraints.
// Returns an unrecoverable error if there's an internal error.
// Otherwise, it returns a boolean indicating if the module meets the constraints
func meetsSemverConstraints(spec *module.Spec) (bool, error) {
v, err := version.NewSemver(spec.Metadata.Version)
if err != nil {
return false, err
}
return versionConstraintsSemver.Check(v), nil
}
// meetsRegexConstraints checks whether a module version matches the regex.
// Returns a boolean indicating if the module meets the constraints
func meetsRegexConstraints(spec *module.Spec) bool {
return versionConstraintsRegex.MatchString(spec.Metadata.Version)
}