Skip to content

Commit 7b08bcd

Browse files
committed
Add support for label storage in local content store
Allows running tests which require labels on the content store Signed-off-by: Derek McGowan <derek@mcgstyle.net>
1 parent f2ae8a0 commit 7b08bcd

File tree

8 files changed

+178
-16
lines changed

8 files changed

+178
-16
lines changed

content/local/store.go

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os"
99
"path/filepath"
1010
"strconv"
11+
"strings"
1112
"sync"
1213
"time"
1314

@@ -27,23 +28,47 @@ var (
2728
}
2829
)
2930

31+
// LabelStore is used to store mutable labels for digests
32+
type LabelStore interface {
33+
// Get returns all the labels for the given digest
34+
Get(digest.Digest) (map[string]string, error)
35+
36+
// Set sets all the labels for a given digest
37+
Set(digest.Digest, map[string]string) error
38+
39+
// Update replaces the given labels for a digest,
40+
// a key with an empty value removes a label.
41+
Update(digest.Digest, map[string]string) (map[string]string, error)
42+
}
43+
3044
// Store is digest-keyed store for content. All data written into the store is
3145
// stored under a verifiable digest.
3246
//
3347
// Store can generally support multi-reader, single-writer ingest of data,
3448
// including resumable ingest.
3549
type store struct {
3650
root string
51+
ls LabelStore
3752
}
3853

3954
// NewStore returns a local content store
4055
func NewStore(root string) (content.Store, error) {
56+
return NewLabeledStore(root, nil)
57+
}
58+
59+
// NewLabeledStore returns a new content store using the provided label store
60+
//
61+
// Note: content stores which are used underneath a metadata store may not
62+
// require labels and should use `NewStore`. `NewLabeledStore` is primarily
63+
// useful for tests or standalone implementations.
64+
func NewLabeledStore(root string, ls LabelStore) (content.Store, error) {
4165
if err := os.MkdirAll(filepath.Join(root, "ingest"), 0777); err != nil && !os.IsExist(err) {
4266
return nil, err
4367
}
4468

4569
return &store{
4670
root: root,
71+
ls: ls,
4772
}, nil
4873
}
4974

@@ -57,16 +82,23 @@ func (s *store) Info(ctx context.Context, dgst digest.Digest) (content.Info, err
5782

5883
return content.Info{}, err
5984
}
60-
61-
return s.info(dgst, fi), nil
85+
var labels map[string]string
86+
if s.ls != nil {
87+
labels, err = s.ls.Get(dgst)
88+
if err != nil {
89+
return content.Info{}, err
90+
}
91+
}
92+
return s.info(dgst, fi, labels), nil
6293
}
6394

64-
func (s *store) info(dgst digest.Digest, fi os.FileInfo) content.Info {
95+
func (s *store) info(dgst digest.Digest, fi os.FileInfo, labels map[string]string) content.Info {
6596
return content.Info{
6697
Digest: dgst,
6798
Size: fi.Size(),
6899
CreatedAt: fi.ModTime(),
69-
UpdatedAt: fi.ModTime(),
100+
UpdatedAt: getATime(fi),
101+
Labels: labels,
70102
}
71103
}
72104

@@ -111,8 +143,66 @@ func (s *store) Delete(ctx context.Context, dgst digest.Digest) error {
111143
}
112144

113145
func (s *store) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) {
114-
// TODO: Support persisting and updating mutable content data
115-
return content.Info{}, errors.Wrapf(errdefs.ErrFailedPrecondition, "update not supported on immutable content store")
146+
if s.ls == nil {
147+
return content.Info{}, errors.Wrapf(errdefs.ErrFailedPrecondition, "update not supported on immutable content store")
148+
}
149+
150+
p := s.blobPath(info.Digest)
151+
fi, err := os.Stat(p)
152+
if err != nil {
153+
if os.IsNotExist(err) {
154+
err = errors.Wrapf(errdefs.ErrNotFound, "content %v", info.Digest)
155+
}
156+
157+
return content.Info{}, err
158+
}
159+
160+
var (
161+
all bool
162+
labels map[string]string
163+
)
164+
if len(fieldpaths) > 0 {
165+
for _, path := range fieldpaths {
166+
if strings.HasPrefix(path, "labels.") {
167+
if labels == nil {
168+
labels = map[string]string{}
169+
}
170+
171+
key := strings.TrimPrefix(path, "labels.")
172+
labels[key] = info.Labels[key]
173+
continue
174+
}
175+
176+
switch path {
177+
case "labels":
178+
all = true
179+
labels = info.Labels
180+
default:
181+
return content.Info{}, errors.Wrapf(errdefs.ErrInvalidArgument, "cannot update %q field on content info %q", path, info.Digest)
182+
}
183+
}
184+
} else {
185+
all = true
186+
labels = info.Labels
187+
}
188+
189+
if all {
190+
err = s.ls.Set(info.Digest, labels)
191+
} else {
192+
labels, err = s.ls.Update(info.Digest, labels)
193+
}
194+
if err != nil {
195+
return content.Info{}, err
196+
}
197+
198+
info = s.info(info.Digest, fi, labels)
199+
info.UpdatedAt = time.Now()
200+
201+
if err := os.Chtimes(p, info.UpdatedAt, info.CreatedAt); err != nil {
202+
log.G(ctx).WithError(err).Warnf("could not change access time for %s", info.Digest)
203+
}
204+
205+
return info, nil
116206
}
117207

118208
func (s *store) Walk(ctx context.Context, fn content.WalkFunc, filters ...string) error {
@@ -154,7 +244,14 @@ func (s *store) Walk(ctx context.Context, fn content.WalkFunc, filters ...string
154244
// store or extra paths not expected previously.
155245
}
156246

157-
return fn(s.info(dgst, fi))
247+
var labels map[string]string
248+
if s.ls != nil {
249+
labels, err = s.ls.Get(dgst)
250+
if err != nil {
251+
return err
252+
}
253+
}
254+
return fn(s.info(dgst, fi, labels))
158255
})
159256
}
160257

content/local/store_test.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"path/filepath"
1515
"reflect"
1616
"runtime"
17+
"sync"
1718
"testing"
1819
"time"
1920

@@ -23,9 +24,55 @@ import (
2324
"github.com/opencontainers/go-digest"
2425
)
2526

27+
type memoryLabelStore struct {
28+
l sync.Mutex
29+
labels map[digest.Digest]map[string]string
30+
}
31+
32+
func newMemoryLabelStore() LabelStore {
33+
return &memoryLabelStore{
34+
labels: map[digest.Digest]map[string]string{},
35+
}
36+
}
37+
38+
func (mls *memoryLabelStore) Get(d digest.Digest) (map[string]string, error) {
39+
mls.l.Lock()
40+
labels := mls.labels[d]
41+
mls.l.Unlock()
42+
43+
return labels, nil
44+
}
45+
46+
func (mls *memoryLabelStore) Set(d digest.Digest, labels map[string]string) error {
47+
mls.l.Lock()
48+
mls.labels[d] = labels
49+
mls.l.Unlock()
50+
51+
return nil
52+
}
53+
54+
func (mls *memoryLabelStore) Update(d digest.Digest, update map[string]string) (map[string]string, error) {
55+
mls.l.Lock()
56+
labels, ok := mls.labels[d]
57+
if !ok {
58+
labels = map[string]string{}
59+
}
60+
for k, v := range update {
61+
if v == "" {
62+
delete(labels, k)
63+
} else {
64+
labels[k] = v
65+
}
66+
}
67+
mls.labels[d] = labels
68+
mls.l.Unlock()
69+
70+
return labels, nil
71+
}
72+
2673
func TestContent(t *testing.T) {
2774
testsuite.ContentSuite(t, "fs", func(ctx context.Context, root string) (content.Store, func() error, error) {
28-
cs, err := NewStore(root)
75+
cs, err := NewLabeledStore(root, newMemoryLabelStore())
2976
if err != nil {
3077
return nil, nil, err
3178
}

content/local/store_unix.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,12 @@ func getStartTime(fi os.FileInfo) time.Time {
1818

1919
return fi.ModTime()
2020
}
21+
22+
func getATime(fi os.FileInfo) time.Time {
23+
if st, ok := fi.Sys().(*syscall.Stat_t); ok {
24+
return time.Unix(int64(sys.StatAtime(st).Sec),
25+
int64(sys.StatAtime(st).Nsec))
26+
}
27+
28+
return fi.ModTime()
29+
}

content/local/store_windows.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ import (
88
func getStartTime(fi os.FileInfo) time.Time {
99
return fi.ModTime()
1010
}
11+
12+
func getATime(fi os.FileInfo) time.Time {
13+
return fi.ModTime()
14+
}

content/local/writer.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ func (w *writer) Write(p []byte) (n int, err error) {
5656
}
5757

5858
func (w *writer) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error {
59+
var base content.Info
60+
for _, opt := range opts {
61+
if err := opt(&base); err != nil {
62+
return err
63+
}
64+
}
65+
5966
if w.fp == nil {
6067
return errors.Wrap(errdefs.ErrFailedPrecondition, "cannot commit on closed writer")
6168
}
@@ -123,6 +130,12 @@ func (w *writer) Commit(ctx context.Context, size int64, expected digest.Digest,
123130
w.fp = nil
124131
unlock(w.ref)
125132

133+
if w.s.ls != nil && base.Labels != nil {
134+
if err := w.s.ls.Set(dgst, base.Labels); err != nil {
135+
return err
136+
}
137+
}
138+
126139
return nil
127140
}
128141

content/testsuite/testsuite.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,6 @@ import (
2121
func ContentSuite(t *testing.T, name string, storeFn func(ctx context.Context, root string) (content.Store, func() error, error)) {
2222
t.Run("Writer", makeTest(t, name, storeFn, checkContentStoreWriter))
2323
t.Run("UploadStatus", makeTest(t, name, storeFn, checkUploadStatus))
24-
}
25-
26-
// ContentLabelSuite runs a test suite for the content store supporting
27-
// labels.
28-
// TODO: Merge this with ContentSuite once all content stores support labels
29-
func ContentLabelSuite(t *testing.T, name string, storeFn func(ctx context.Context, root string) (content.Store, func() error, error)) {
3024
t.Run("Labels", makeTest(t, name, storeFn, checkLabels))
3125
}
3226

content_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,4 @@ func TestContentClient(t *testing.T) {
3939
t.Skip()
4040
}
4141
testsuite.ContentSuite(t, "ContentClient", newContentStore)
42-
testsuite.ContentLabelSuite(t, "ContentClient", newContentStore)
4342
}

metadata/content_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,4 @@ func createContentStore(ctx context.Context, root string) (content.Store, func()
3030

3131
func TestContent(t *testing.T) {
3232
testsuite.ContentSuite(t, "metadata", createContentStore)
33-
testsuite.ContentLabelSuite(t, "metadata", createContentStore)
3433
}

0 commit comments

Comments
 (0)