1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package validate
16
17 import (
18 "bytes"
19 "errors"
20 "fmt"
21 "strings"
22
23 "github.com/google/go-cmp/cmp"
24 "github.com/google/go-containerregistry/pkg/logs"
25 v1 "github.com/google/go-containerregistry/pkg/v1"
26 "github.com/google/go-containerregistry/pkg/v1/types"
27 )
28
29
30 func Index(idx v1.ImageIndex, opt ...Option) error {
31 errs := []string{}
32
33 if err := validateChildren(idx, opt...); err != nil {
34 errs = append(errs, fmt.Sprintf("validating children: %v", err))
35 }
36
37 if err := validateIndexManifest(idx); err != nil {
38 errs = append(errs, fmt.Sprintf("validating index manifest: %v", err))
39 }
40
41 if len(errs) != 0 {
42 return errors.New(strings.Join(errs, "\n\n"))
43 }
44 return nil
45 }
46
47 type withLayer interface {
48 Layer(v1.Hash) (v1.Layer, error)
49 }
50
51 func validateChildren(idx v1.ImageIndex, opt ...Option) error {
52 manifest, err := idx.IndexManifest()
53 if err != nil {
54 return err
55 }
56
57 errs := []string{}
58 for i, desc := range manifest.Manifests {
59 switch desc.MediaType {
60 case types.OCIImageIndex, types.DockerManifestList:
61 idx, err := idx.ImageIndex(desc.Digest)
62 if err != nil {
63 return err
64 }
65 if err := Index(idx, opt...); err != nil {
66 errs = append(errs, fmt.Sprintf("failed to validate index Manifests[%d](%s): %v", i, desc.Digest, err))
67 }
68 if err := validateMediaType(idx, desc.MediaType); err != nil {
69 errs = append(errs, fmt.Sprintf("failed to validate index MediaType[%d](%s): %v", i, desc.Digest, err))
70 }
71 case types.OCIManifestSchema1, types.DockerManifestSchema2:
72 img, err := idx.Image(desc.Digest)
73 if err != nil {
74 return err
75 }
76 if err := Image(img, opt...); err != nil {
77 errs = append(errs, fmt.Sprintf("failed to validate image Manifests[%d](%s): %v", i, desc.Digest, err))
78 }
79 if err := validateMediaType(img, desc.MediaType); err != nil {
80 errs = append(errs, fmt.Sprintf("failed to validate image MediaType[%d](%s): %v", i, desc.Digest, err))
81 }
82 if err := validatePlatform(img, desc.Platform); err != nil {
83 errs = append(errs, fmt.Sprintf("failed to validate image platform[%d](%s): %v", i, desc.Digest, err))
84 }
85 default:
86
87 if wl, ok := idx.(withLayer); ok {
88 layer, err := wl.Layer(desc.Digest)
89 if err != nil {
90 return fmt.Errorf("failed to get layer Manifests[%d]: %w", i, err)
91 }
92 if err := Layer(layer, opt...); err != nil {
93 lerr := fmt.Sprintf("failed to validate layer Manifests[%d](%s): %v", i, desc.Digest, err)
94 if desc.MediaType.IsDistributable() {
95 errs = append(errs, lerr)
96 } else {
97 logs.Warn.Printf("nondistributable layer failure: %v", lerr)
98 }
99 }
100 } else {
101 logs.Warn.Printf("Unexpected manifest: %s", desc.MediaType)
102 }
103 }
104 }
105
106 if len(errs) != 0 {
107 return errors.New(strings.Join(errs, "\n"))
108 }
109
110 return nil
111 }
112
113 type withMediaType interface {
114 MediaType() (types.MediaType, error)
115 }
116
117 func validateMediaType(i withMediaType, want types.MediaType) error {
118 got, err := i.MediaType()
119 if err != nil {
120 return err
121 }
122 if want != got {
123 return fmt.Errorf("mismatched mediaType: MediaType() = %v != %v", got, want)
124 }
125
126 return nil
127 }
128
129 func validateIndexManifest(idx v1.ImageIndex) error {
130 digest, err := idx.Digest()
131 if err != nil {
132 return err
133 }
134
135 size, err := idx.Size()
136 if err != nil {
137 return err
138 }
139
140 rm, err := idx.RawManifest()
141 if err != nil {
142 return err
143 }
144
145 hash, _, err := v1.SHA256(bytes.NewReader(rm))
146 if err != nil {
147 return err
148 }
149
150 m, err := idx.IndexManifest()
151 if err != nil {
152 return err
153 }
154
155 pm, err := v1.ParseIndexManifest(bytes.NewReader(rm))
156 if err != nil {
157 return err
158 }
159
160 errs := []string{}
161 if digest != hash {
162 errs = append(errs, fmt.Sprintf("mismatched manifest digest: Digest()=%s, SHA256(RawManifest())=%s", digest, hash))
163 }
164
165 if diff := cmp.Diff(pm, m); diff != "" {
166 errs = append(errs, fmt.Sprintf("mismatched manifest content: (-ParseIndexManifest(RawManifest()) +Manifest()) %s", diff))
167 }
168
169 if size != int64(len(rm)) {
170 errs = append(errs, fmt.Sprintf("mismatched manifest size: Size()=%d, len(RawManifest())=%d", size, len(rm)))
171 }
172
173 if len(errs) != 0 {
174 return errors.New(strings.Join(errs, "\n"))
175 }
176
177 return nil
178 }
179
180 func validatePlatform(img v1.Image, want *v1.Platform) error {
181 if want == nil {
182 return nil
183 }
184
185 cf, err := img.ConfigFile()
186 if err != nil {
187 return err
188 }
189
190 got := cf.Platform()
191
192 if got == nil {
193 return fmt.Errorf("config file missing platform fields")
194 }
195
196 if got.Equals(*want) {
197 return nil
198 }
199
200 errs := []string{}
201
202 if got.OS != want.OS {
203 errs = append(errs, fmt.Sprintf("mismatched OS: %s != %s", got.OS, want.OS))
204 }
205
206 if got.Architecture != want.Architecture {
207 errs = append(errs, fmt.Sprintf("mismatched Architecture: %s != %s", got.Architecture, want.Architecture))
208 }
209
210 if got.OSVersion != want.OSVersion {
211 errs = append(errs, fmt.Sprintf("mismatched OSVersion: %s != %s", got.OSVersion, want.OSVersion))
212 }
213
214 if got.OSVersion != want.OSVersion {
215 errs = append(errs, fmt.Sprintf("mismatched OSVersion: %s != %s", got.OSVersion, want.OSVersion))
216 }
217
218 if len(errs) == 0 {
219
220 if len(got.Features) != 0 || len(want.Features) != 0 {
221 errs = append(errs, fmt.Sprintf("mismatched Features: %v, %v", got.Features, want.Features))
222 }
223 if len(got.OSFeatures) != 0 || len(want.OSFeatures) != 0 {
224 errs = append(errs, fmt.Sprintf("mismatched OSFeatures: %v, %v", got.OSFeatures, want.OSFeatures))
225 }
226 }
227
228 return errors.New(strings.Join(errs, "\n"))
229 }
230
View as plain text