1 package manifestlist
2
3 import (
4 "encoding/json"
5 "errors"
6 "fmt"
7
8 "github.com/docker/distribution"
9 "github.com/docker/distribution/manifest"
10 "github.com/opencontainers/go-digest"
11 v1 "github.com/opencontainers/image-spec/specs-go/v1"
12 )
13
14 const (
15
16 MediaTypeManifestList = "application/vnd.docker.distribution.manifest.list.v2+json"
17 )
18
19
20
21 var SchemaVersion = manifest.Versioned{
22 SchemaVersion: 2,
23 MediaType: MediaTypeManifestList,
24 }
25
26
27
28 var OCISchemaVersion = manifest.Versioned{
29 SchemaVersion: 2,
30 MediaType: v1.MediaTypeImageIndex,
31 }
32
33 func init() {
34 manifestListFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
35 m := new(DeserializedManifestList)
36 err := m.UnmarshalJSON(b)
37 if err != nil {
38 return nil, distribution.Descriptor{}, err
39 }
40
41 if m.MediaType != MediaTypeManifestList {
42 err = fmt.Errorf("mediaType in manifest list should be '%s' not '%s'",
43 MediaTypeManifestList, m.MediaType)
44
45 return nil, distribution.Descriptor{}, err
46 }
47
48 dgst := digest.FromBytes(b)
49 return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: MediaTypeManifestList}, err
50 }
51 err := distribution.RegisterManifestSchema(MediaTypeManifestList, manifestListFunc)
52 if err != nil {
53 panic(fmt.Sprintf("Unable to register manifest: %s", err))
54 }
55
56 imageIndexFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
57 if err := validateIndex(b); err != nil {
58 return nil, distribution.Descriptor{}, err
59 }
60 m := new(DeserializedManifestList)
61 err := m.UnmarshalJSON(b)
62 if err != nil {
63 return nil, distribution.Descriptor{}, err
64 }
65
66 if m.MediaType != "" && m.MediaType != v1.MediaTypeImageIndex {
67 err = fmt.Errorf("if present, mediaType in image index should be '%s' not '%s'",
68 v1.MediaTypeImageIndex, m.MediaType)
69
70 return nil, distribution.Descriptor{}, err
71 }
72
73 dgst := digest.FromBytes(b)
74 return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: v1.MediaTypeImageIndex}, err
75 }
76 err = distribution.RegisterManifestSchema(v1.MediaTypeImageIndex, imageIndexFunc)
77 if err != nil {
78 panic(fmt.Sprintf("Unable to register OCI Image Index: %s", err))
79 }
80 }
81
82
83
84 type PlatformSpec struct {
85
86
87 Architecture string `json:"architecture"`
88
89
90 OS string `json:"os"`
91
92
93
94 OSVersion string `json:"os.version,omitempty"`
95
96
97
98 OSFeatures []string `json:"os.features,omitempty"`
99
100
101
102 Variant string `json:"variant,omitempty"`
103
104
105
106 Features []string `json:"features,omitempty"`
107 }
108
109
110 type ManifestDescriptor struct {
111 distribution.Descriptor
112
113
114
115 Platform PlatformSpec `json:"platform"`
116 }
117
118
119 type ManifestList struct {
120 manifest.Versioned
121
122
123 Manifests []ManifestDescriptor `json:"manifests"`
124 }
125
126
127
128 func (m ManifestList) References() []distribution.Descriptor {
129 dependencies := make([]distribution.Descriptor, len(m.Manifests))
130 for i := range m.Manifests {
131 dependencies[i] = m.Manifests[i].Descriptor
132 }
133
134 return dependencies
135 }
136
137
138
139 type DeserializedManifestList struct {
140 ManifestList
141
142
143 canonical []byte
144 }
145
146
147
148
149 func FromDescriptors(descriptors []ManifestDescriptor) (*DeserializedManifestList, error) {
150 var mediaType string
151 if len(descriptors) > 0 && descriptors[0].Descriptor.MediaType == v1.MediaTypeImageManifest {
152 mediaType = v1.MediaTypeImageIndex
153 } else {
154 mediaType = MediaTypeManifestList
155 }
156
157 return FromDescriptorsWithMediaType(descriptors, mediaType)
158 }
159
160
161 func FromDescriptorsWithMediaType(descriptors []ManifestDescriptor, mediaType string) (*DeserializedManifestList, error) {
162 m := ManifestList{
163 Versioned: manifest.Versioned{
164 SchemaVersion: 2,
165 MediaType: mediaType,
166 },
167 }
168
169 m.Manifests = make([]ManifestDescriptor, len(descriptors))
170 copy(m.Manifests, descriptors)
171
172 deserialized := DeserializedManifestList{
173 ManifestList: m,
174 }
175
176 var err error
177 deserialized.canonical, err = json.MarshalIndent(&m, "", " ")
178 return &deserialized, err
179 }
180
181
182 func (m *DeserializedManifestList) UnmarshalJSON(b []byte) error {
183 m.canonical = make([]byte, len(b))
184
185 copy(m.canonical, b)
186
187
188 var manifestList ManifestList
189 if err := json.Unmarshal(m.canonical, &manifestList); err != nil {
190 return err
191 }
192
193 m.ManifestList = manifestList
194
195 return nil
196 }
197
198
199
200 func (m *DeserializedManifestList) MarshalJSON() ([]byte, error) {
201 if len(m.canonical) > 0 {
202 return m.canonical, nil
203 }
204
205 return nil, errors.New("JSON representation not initialized in DeserializedManifestList")
206 }
207
208
209
210 func (m DeserializedManifestList) Payload() (string, []byte, error) {
211 var mediaType string
212 if m.MediaType == "" {
213 mediaType = v1.MediaTypeImageIndex
214 } else {
215 mediaType = m.MediaType
216 }
217
218 return mediaType, m.canonical, nil
219 }
220
221
222
223 type unknownDocument struct {
224 Config interface{} `json:"config,omitempty"`
225 Layers interface{} `json:"layers,omitempty"`
226 }
227
228
229
230 func validateIndex(b []byte) error {
231 var doc unknownDocument
232 if err := json.Unmarshal(b, &doc); err != nil {
233 return err
234 }
235 if doc.Config != nil || doc.Layers != nil {
236 return errors.New("index: expected index but found manifest")
237 }
238 return nil
239 }
240
View as plain text