forked from gomods/athens
-
Notifications
You must be signed in to change notification settings - Fork 0
/
merge_db.go
108 lines (99 loc) · 4.01 KB
/
merge_db.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
package actions
import (
"context"
"log"
"time"
"github.com/gomods/athens/pkg/cdn"
"github.com/gomods/athens/pkg/eventlog"
"github.com/gomods/athens/pkg/storage"
multierror "github.com/hashicorp/go-multierror"
)
// mergeDB merges diff into the module database.
//
// TODO: this is racey if multiple processes are running mergeDB (they will be!) in a few ways:
//
// 1. CDN updates that race to change the /list endpoint
// 2. races between CDN updates and module metadata updates. For example:
// - Delete operation deletes from the CDN
// - Add operation adds to the CDN and saves to the module metadata DB
// - Delete operation adds tombstone to module metadata k/v store
//
// Both could be fixed by putting each 'for' loop into a (global) critical section
func mergeDB(ctx context.Context, originURL string, diff dbDiff, eLog eventlog.Eventlog, storage storage.Backend) error {
var errors error
for _, added := range diff.Added {
if err := add(ctx, added, originURL, eLog, storage); err != nil {
errors = multierror.Append(errors, err)
}
}
for _, deprecated := range diff.Deprecated {
if err := deprecate(ctx, deprecated, originURL, eLog, storage); err != nil {
errors = multierror.Append(errors, err)
}
}
for _, deleted := range diff.Deleted {
if err := delete(deleted, eLog, storage); err != nil {
errors = multierror.Append(errors, err)
}
}
return errors
}
func add(ctx context.Context, event eventlog.Event, originURL string, eLog eventlog.Eventlog, storage storage.Backend) error {
if _, err := eLog.ReadSingle(event.Module, event.Version); err != nil {
// the module/version already exists, is deprecated, or is
// tombstoned, so nothing to do
return err
}
// download code from the origin
data, err := cdn.Download(originURL, event.Module, event.Version)
if err != nil {
log.Printf("error downloading new module %s/%s from %s (%s)", event.Module, event.Version, originURL, err)
return err
}
defer data.Zip.Close()
// save module data to the CDN
if err := storage.Save(ctx, event.Module, event.Version, data.Mod, data.Zip, data.Info); err != nil {
log.Printf("error saving new module %s/%s to CDN (%s)", event.Module, event.Version, err)
return err
}
// save module metadata to the key/value store
if _, err := eLog.Append(eventlog.Event{Module: event.Module, Version: event.Version, Time: time.Now(), Op: eventlog.OpAdd}); err != nil {
log.Printf("error saving metadata for new module %s/%s (%s)", event.Module, event.Version, err)
return err
}
return nil
}
func deprecate(ctx context.Context, event eventlog.Event, originURL string, eLog eventlog.Eventlog, storage storage.Backend) error {
fromDB, err := eLog.ReadSingle(event.Module, event.Version)
if err != nil {
log.Printf("error getting event module %s/%s (%s)", event.Module, event.Version, err)
return err
}
if fromDB.Op == eventlog.OpDel {
return err // can't deprecate something that's already deleted
}
// delete from the CDN
if err := storage.Delete(event.Module, event.Version); err != nil {
log.Printf("error deleting event module %s/%s from CDN (%s)", event.Module, event.Version, err)
return err
}
// add the tombstone to module metadata
if _, err := eLog.Append(eventlog.Event{Module: event.Module, Version: event.Version, Time: time.Now(), Op: eventlog.OpDel}); err != nil {
log.Printf("error saving metadata for deprecated module %s/%s from CDN (%s)", event.Module, event.Version, err)
return err
}
return nil
}
func delete(event eventlog.Event, eLog eventlog.Eventlog, storage storage.Backend) error {
// delete in the CDN
if err := storage.Delete(event.Module, event.Version); err != nil {
log.Printf("error deleting event module %s/%s from CDN (%s)", event.Module, event.Version, err)
return err
}
// add tombstone to module metadata
if _, err := eLog.Append(eventlog.Event{Module: event.Module, Version: event.Version, Time: time.Now(), Op: eventlog.OpDel}); err != nil {
log.Printf("error inserting tombstone for deleted module %s/%s (%s)", event.Module, event.Version, err)
return err
}
return nil
}