1
15 package content
16
17 import (
18 "context"
19 "encoding/json"
20 "errors"
21 "fmt"
22 "io"
23 "io/ioutil"
24 "os"
25 "path/filepath"
26 "strings"
27
28 "github.com/containerd/containerd/content"
29 "github.com/containerd/containerd/content/local"
30 "github.com/containerd/containerd/remotes"
31 "github.com/opencontainers/go-digest"
32 specs "github.com/opencontainers/image-spec/specs-go"
33 ocispec "github.com/opencontainers/image-spec/specs-go/v1"
34 )
35
36
37
38 type OCI struct {
39 content.Store
40
41 root string
42 index *ocispec.Index
43 nameMap map[string]ocispec.Descriptor
44 }
45
46
47 func NewOCI(rootPath string) (*OCI, error) {
48 fileStore, err := local.NewStore(rootPath)
49 if err != nil {
50 return nil, err
51 }
52
53 store := &OCI{
54 Store: fileStore,
55 root: rootPath,
56 }
57 if err := store.validateOCILayoutFile(); err != nil {
58 return nil, err
59 }
60 if err := store.LoadIndex(); err != nil {
61 return nil, err
62 }
63
64 return store, nil
65 }
66
67
68 func (s *OCI) LoadIndex() error {
69 path := filepath.Join(s.root, OCIImageIndexFile)
70 indexFile, err := os.Open(path)
71 if err != nil {
72 if !os.IsNotExist(err) {
73 return err
74 }
75 s.index = &ocispec.Index{
76 Versioned: specs.Versioned{
77 SchemaVersion: 2,
78 },
79 }
80 s.nameMap = make(map[string]ocispec.Descriptor)
81
82 return nil
83 }
84 defer indexFile.Close()
85
86 if err := json.NewDecoder(indexFile).Decode(&s.index); err != nil {
87 return err
88 }
89
90 s.nameMap = make(map[string]ocispec.Descriptor)
91 for _, desc := range s.index.Manifests {
92 if name := desc.Annotations[ocispec.AnnotationRefName]; name != "" {
93 s.nameMap[name] = desc
94 }
95 }
96
97 return nil
98 }
99
100
101 func (s *OCI) SaveIndex() error {
102
103 var descs []ocispec.Descriptor
104 for name, desc := range s.nameMap {
105 if desc.Annotations == nil {
106 desc.Annotations = map[string]string{}
107 }
108 desc.Annotations[ocispec.AnnotationRefName] = name
109 descs = append(descs, desc)
110 }
111 s.index.Manifests = descs
112 indexJSON, err := json.Marshal(s.index)
113 if err != nil {
114 return err
115 }
116
117 path := filepath.Join(s.root, OCIImageIndexFile)
118 return ioutil.WriteFile(path, indexJSON, 0644)
119 }
120
121 func (s *OCI) Resolver() remotes.Resolver {
122 return s
123 }
124
125 func (s *OCI) Resolve(ctx context.Context, ref string) (name string, desc ocispec.Descriptor, err error) {
126 if err := s.LoadIndex(); err != nil {
127 return "", ocispec.Descriptor{}, err
128 }
129 desc, ok := s.nameMap[ref]
130 if !ok {
131 return "", ocispec.Descriptor{}, fmt.Errorf("reference %s not in store", ref)
132 }
133 return ref, desc, nil
134 }
135
136 func (s *OCI) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) {
137 if err := s.LoadIndex(); err != nil {
138 return nil, err
139 }
140 if _, ok := s.nameMap[ref]; !ok {
141 return nil, fmt.Errorf("reference %s not in store", ref)
142 }
143 return s, nil
144 }
145
146
147 func (s *OCI) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) {
148 readerAt, err := s.Store.ReaderAt(ctx, desc)
149 if err != nil {
150 return nil, err
151 }
152
153 return ioutil.NopCloser(&ReaderAtWrapper{readerAt: readerAt}), nil
154 }
155
156
157 func (s *OCI) Pusher(ctx context.Context, ref string) (remotes.Pusher, error) {
158
159 var (
160 baseRef, hash string
161 )
162 parts := strings.SplitN(ref, "@", 2)
163 baseRef = parts[0]
164 if len(parts) > 1 {
165 hash = parts[1]
166 }
167 return &ociPusher{oci: s, ref: baseRef, digest: hash}, nil
168 }
169
170
171 func (s *OCI) AddReference(name string, desc ocispec.Descriptor) {
172 if desc.Annotations == nil {
173 desc.Annotations = map[string]string{
174 ocispec.AnnotationRefName: name,
175 }
176 } else {
177 desc.Annotations[ocispec.AnnotationRefName] = name
178 }
179
180 if _, ok := s.nameMap[name]; ok {
181 s.nameMap[name] = desc
182
183 for i, ref := range s.index.Manifests {
184 if name == ref.Annotations[ocispec.AnnotationRefName] {
185 s.index.Manifests[i] = desc
186 return
187 }
188 }
189
190
191
192 s.index.Manifests = append(s.index.Manifests, desc)
193 return
194 }
195
196 s.index.Manifests = append(s.index.Manifests, desc)
197 s.nameMap[name] = desc
198 }
199
200
201 func (s *OCI) DeleteReference(name string) {
202 if _, ok := s.nameMap[name]; !ok {
203 return
204 }
205
206 delete(s.nameMap, name)
207 for i, desc := range s.index.Manifests {
208 if name == desc.Annotations[ocispec.AnnotationRefName] {
209 s.index.Manifests[i] = s.index.Manifests[len(s.index.Manifests)-1]
210 s.index.Manifests = s.index.Manifests[:len(s.index.Manifests)-1]
211 return
212 }
213 }
214 }
215
216
217 func (s *OCI) ListReferences() map[string]ocispec.Descriptor {
218 return s.nameMap
219 }
220
221
222 func (s *OCI) validateOCILayoutFile() error {
223 layoutFilePath := filepath.Join(s.root, ocispec.ImageLayoutFile)
224 layoutFile, err := os.Open(layoutFilePath)
225 if err != nil {
226 if !os.IsNotExist(err) {
227 return err
228 }
229
230 layout := ocispec.ImageLayout{
231 Version: ocispec.ImageLayoutVersion,
232 }
233 layoutJSON, err := json.Marshal(layout)
234 if err != nil {
235 return err
236 }
237
238 return ioutil.WriteFile(layoutFilePath, layoutJSON, 0644)
239 }
240 defer layoutFile.Close()
241
242 var layout *ocispec.ImageLayout
243 err = json.NewDecoder(layoutFile).Decode(&layout)
244 if err != nil {
245 return err
246 }
247 if layout.Version != ocispec.ImageLayoutVersion {
248 return ErrUnsupportedVersion
249 }
250
251 return nil
252 }
253
254
255
256 func (s *OCI) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {
257 return s.Store.Info(ctx, dgst)
258 }
259
260
261
262
263
264
265
266 func (s *OCI) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) {
267 return content.Info{}, errors.New("not yet implemented: Update (content.Store interface)")
268 }
269
270
271
272
273
274 func (s *OCI) Walk(ctx context.Context, fn content.WalkFunc, filters ...string) error {
275 return errors.New("not yet implemented: Walk (content.Store interface)")
276 }
277
278
279 func (s *OCI) Delete(ctx context.Context, dgst digest.Digest) error {
280 return s.Store.Delete(ctx, dgst)
281 }
282
283
284 func (s *OCI) Status(ctx context.Context, ref string) (content.Status, error) {
285
286 return content.Status{}, errors.New("not yet implemented: Status (content.Store interface)")
287 }
288
289
290
291
292
293 func (s *OCI) ListStatuses(ctx context.Context, filters ...string) ([]content.Status, error) {
294 return []content.Status{}, errors.New("not yet implemented: ListStatuses (content.Store interface)")
295 }
296
297
298
299 func (s *OCI) Abort(ctx context.Context, ref string) error {
300 return errors.New("not yet implemented: Abort (content.Store interface)")
301 }
302
303
304 func (s *OCI) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {
305 return s.Store.ReaderAt(ctx, desc)
306 }
307
308
309
310
311 type ociPusher struct {
312 oci *OCI
313 ref string
314 digest string
315 }
316
317
318 func (p *ociPusher) Push(ctx context.Context, desc ocispec.Descriptor) (content.Writer, error) {
319
320 switch desc.MediaType {
321 case ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex:
322
323 if p.digest != "" && p.digest == desc.Digest.String() {
324 if err := p.oci.LoadIndex(); err != nil {
325 return nil, err
326 }
327 p.oci.nameMap[p.ref] = desc
328 if err := p.oci.SaveIndex(); err != nil {
329 return nil, err
330 }
331 }
332 }
333
334 return p.oci.Store.Writer(ctx, content.WithDescriptor(desc), content.WithRef(p.ref))
335 }
336
View as plain text