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.
3549type store struct {
3650 root string
51+ ls LabelStore
3752}
3853
3954// NewStore returns a local content store
4055func 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
113145func (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
118208func (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
0 commit comments