-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathrepo.go
276 lines (237 loc) · 7 KB
/
repo.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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
package yum
import (
"encoding/hex"
"fmt"
"github.com/cavaliercoder/go-rpm"
"github.com/cavaliercoder/grab"
"github.com/pivotal-golang/bytefmt"
"golang.org/x/crypto/openpgp"
"io/ioutil"
"os"
"path/filepath"
"time"
)
// Repo is a package repository defined in a Yumfile
type Repo struct {
ID string
Name string
Architecture string
BaseURL string
CachePath string
Checksum string
DeleteRemoved bool
GPGCheck bool
GPGKey string
Groupfile string
IncludeSources bool
LocalPath string
MirrorURL string
NewOnly bool
MaxDate time.Time
MinDate time.Time
YumfileLineNo int
YumfilePath string
}
// NewRepo initializes a new Repo struct and returns a pointer to it.
func NewRepo() *Repo {
return &Repo{}
}
func (c Repo) String() string {
return c.ID
}
// Validate checks the syntax of a repo defined in a Yumfile and returns an
// on the first syntax error encountered. If no errors are found, nil is
// returned.
func (c *Repo) Validate() error {
if c.ID == "" {
return NewErrorf("Upstream repository has no ID specified (in %s:%d)", c.YumfilePath, c.YumfileLineNo)
}
if c.MirrorURL == "" && c.BaseURL == "" {
return NewErrorf("Upstream repository for '%s' has no mirror list or base URL (in %s:%d)", c.ID, c.YumfilePath, c.YumfileLineNo)
}
return nil
}
// CacheLocal caches a copy of a Repo's metadata and databases to the given
// cache directory. If the Repo is already cached, the cache is validated and
// updated if the source repository has been updated.
func (c *Repo) CacheLocal(path string) (*RepoCache, error) {
Dprintf("Caching %v to %s...\n", c, path)
// connect to cache
cache, err := NewCache(path)
if err != nil {
return nil, err
}
// get cache for this repo
repocache, err := cache.NewRepoCache(c)
if err != nil {
return nil, err
}
// update cache
if err := repocache.Update(); err != nil {
return nil, err
}
return repocache, nil
}
// Sync syncronizes a local package repository with an upstream repository using
// filter rules defined for the repository in its parent Yumfile. All repository
// metadata is cached in the given cache directory.
func (c *Repo) Sync(cachedir, packagedir string) error {
var err error
// load gpg keys
var keyring openpgp.KeyRing
if c.GPGCheck {
keyring, err = OpenKeyRing(c.GPGKey)
if err != nil {
return err
}
}
// cache repo metadata locally to TmpYumCachePath
repocache, err := c.CacheLocal(cachedir)
if err != nil {
return fmt.Errorf("Failed to cache metadata for repo %v: %v", c, err)
}
// get primary db from cache
primarydb, err := repocache.PrimaryDB()
if err != nil {
return err
}
// create package directory
if err := os.MkdirAll(packagedir, 0750); err != nil && !os.IsExist(err) {
return fmt.Errorf("Error creating local package path %s: %v", packagedir, err)
}
// list existing files
files, err := ioutil.ReadDir(packagedir)
if err != nil {
return fmt.Errorf("Error reading packages")
}
// load packages from primary_db
Dprintf("Loading package metadata from primary_db...\n")
packages, err := primarydb.Packages()
if err != nil {
return fmt.Errorf("Error reading packages from primary_db: %v", err)
}
// filter list
packages = FilterPackages(c, packages)
Dprintf("Found %d packages in primary_db\n", len(packages))
// build a list of missing packages
Dprintf("Checking for existing packages in %s...\n", packagedir)
missing := make([]PackageEntry, 0)
var totalsize uint64 = 0
for _, p := range packages {
package_filename := filepath.Base(p.LocationHref())
package_path := filepath.Join(packagedir, filepath.Base(p.LocationHref()))
// search local files
found := false
for _, fi := range files {
// find file for package
if fi.Name() == package_filename {
// check file size
if fi.Size() == p.PackageSize() {
// validate checksum
sum, err := p.Checksum()
if err != nil {
Errorf(err, "Failed to compute checksum for package %v", p)
break
}
err = ValidateFileChecksum(package_path, sum, p.ChecksumType())
if err == ErrChecksumMismatch {
Errorf(err, "Existing file failed checksum validation for package %v", p)
break
} else if err != nil {
Errorf(err, "Error validating checksum for package %v", p)
break
}
// valid package found
found = true
break
} else if fi.Size() > p.PackageSize() {
// existing file is too large (smaller is okay)
Errorf(err, "Existing file is larger (%s) than expected (%s) for package %v", bytefmt.ByteSize(uint64(fi.Size())), bytefmt.ByteSize(uint64(p.PackageSize())), p)
break
} else {
Dprintf("Existing file is incomplete for package %v\n", p)
}
}
}
// TODO: filter packages according to Yumfile rules
if !found {
missing = append(missing, p)
totalsize += uint64(p.PackageSize())
}
}
Dprintf("Scheduled %d packages for download (%s)\n", len(missing), bytefmt.ByteSize(totalsize))
// schedule download jobs
reqs := make([]*grab.Request, 0)
for i, p := range missing {
req, err := grab.NewRequest(urljoin(c.BaseURL, p.LocationHref()))
if err != nil {
Errorf(err, "Error requesting package %v", p)
} else {
req.Label = fmt.Sprintf("[ %d / %d ] %v", i+1, len(missing), p)
req.Filename = filepath.Join(packagedir, filepath.Base(p.LocationHref()))
req.Size = uint64(p.PackageSize())
sum, err := p.Checksum()
if err != nil {
Errorf(err, "Error reading checksum for package %v", p)
} else {
b, err := hex.DecodeString(sum)
if err != nil {
Errorf(err, "Error decoding checksum for package %v", p)
} else {
req.SetChecksum(p.ChecksumType(), b)
reqs = append(reqs, req)
}
}
}
}
// download missing packages
responses := download(reqs, DownloadThreads)
// handle each finished package
for resp := range responses {
if resp.Error != nil {
Errorf(resp.Error, "Error downloading %s", resp.Request.Label)
} else {
// gpg check
// TODO: create more gpgcheck threads
if c.GPGCheck {
// open downloaded package for reading
f, err := os.Open(resp.Filename)
if err != nil {
Errorf(err, "Error reading %s for GPG check", resp.Request.Label)
} else {
defer f.Close()
// gpg check
_, err = rpm.GPGCheck(f, keyring)
if err != nil {
Errorf(err, "GPG check validation failed for %s", resp.Request.Label)
// delete bad package
if err := os.Remove(resp.Filename); err != nil {
Errorf(err, "Error deleting %v", resp.Request.Label)
}
}
}
}
}
}
// TODO: createrepo
if w, err := createrepo(filepath.Join(packagedir, "/repodata")); err != nil {
PanicOn(err)
} else {
defer w.Close()
// enumerate package dir
files, err := filepath.Glob(filepath.Join(packagedir, "/*.rpm"))
if err != nil {
PanicOn(err)
}
// add to primary db
Dprintf("Inserting %v packages\n", len(files))
for _, f := range files {
p, err := rpm.OpenPackageFile(f)
if err != nil {
PanicOn(err)
}
w.Write(p)
}
}
return nil
}