...
1 package ocischema
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 var (
15
16
17 SchemaVersion = manifest.Versioned{
18 SchemaVersion: 2,
19 MediaType: v1.MediaTypeImageManifest,
20 }
21 )
22
23 func init() {
24 ocischemaFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
25 if err := validateManifest(b); err != nil {
26 return nil, distribution.Descriptor{}, err
27 }
28 m := new(DeserializedManifest)
29 err := m.UnmarshalJSON(b)
30 if err != nil {
31 return nil, distribution.Descriptor{}, err
32 }
33
34 dgst := digest.FromBytes(b)
35 return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: v1.MediaTypeImageManifest}, err
36 }
37 err := distribution.RegisterManifestSchema(v1.MediaTypeImageManifest, ocischemaFunc)
38 if err != nil {
39 panic(fmt.Sprintf("Unable to register manifest: %s", err))
40 }
41 }
42
43
44 type Manifest struct {
45 manifest.Versioned
46
47
48 Config distribution.Descriptor `json:"config"`
49
50
51
52 Layers []distribution.Descriptor `json:"layers"`
53
54
55 Annotations map[string]string `json:"annotations,omitempty"`
56 }
57
58
59 func (m Manifest) References() []distribution.Descriptor {
60 references := make([]distribution.Descriptor, 0, 1+len(m.Layers))
61 references = append(references, m.Config)
62 references = append(references, m.Layers...)
63 return references
64 }
65
66
67 func (m Manifest) Target() distribution.Descriptor {
68 return m.Config
69 }
70
71
72
73 type DeserializedManifest struct {
74 Manifest
75
76
77 canonical []byte
78 }
79
80
81
82 func FromStruct(m Manifest) (*DeserializedManifest, error) {
83 var deserialized DeserializedManifest
84 deserialized.Manifest = m
85
86 var err error
87 deserialized.canonical, err = json.MarshalIndent(&m, "", " ")
88 return &deserialized, err
89 }
90
91
92 func (m *DeserializedManifest) UnmarshalJSON(b []byte) error {
93 m.canonical = make([]byte, len(b))
94
95 copy(m.canonical, b)
96
97
98 var manifest Manifest
99 if err := json.Unmarshal(m.canonical, &manifest); err != nil {
100 return err
101 }
102
103 if manifest.MediaType != "" && manifest.MediaType != v1.MediaTypeImageManifest {
104 return fmt.Errorf("if present, mediaType in manifest should be '%s' not '%s'",
105 v1.MediaTypeImageManifest, manifest.MediaType)
106 }
107
108 m.Manifest = manifest
109
110 return nil
111 }
112
113
114
115 func (m *DeserializedManifest) MarshalJSON() ([]byte, error) {
116 if len(m.canonical) > 0 {
117 return m.canonical, nil
118 }
119
120 return nil, errors.New("JSON representation not initialized in DeserializedManifest")
121 }
122
123
124
125 func (m DeserializedManifest) Payload() (string, []byte, error) {
126 return v1.MediaTypeImageManifest, m.canonical, nil
127 }
128
129
130
131 type unknownDocument struct {
132 Manifests interface{} `json:"manifests,omitempty"`
133 }
134
135
136
137 func validateManifest(b []byte) error {
138 var doc unknownDocument
139 if err := json.Unmarshal(b, &doc); err != nil {
140 return err
141 }
142 if doc.Manifests != nil {
143 return errors.New("ocimanifest: expected manifest but found index")
144 }
145 return nil
146 }
147
View as plain text