-
Notifications
You must be signed in to change notification settings - Fork 241
/
reader.go
124 lines (107 loc) · 3.48 KB
/
reader.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package bundle
import (
"archive/tar"
"fmt"
"io"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/tektoncd/pipeline/pkg/client/clientset/versioned/scheme"
tkremote "github.com/tektoncd/pipeline/pkg/remote/oci"
"k8s.io/apimachinery/pkg/runtime"
)
// ObjectVisitor is an input function that callers of this file's methods can implement to act on the read contents of a
// Tekton bundle. The `version`, `kind`, and `name` fields map 1:1 with the same named fields in the Tekton Bundle spec.
type ObjectVisitor func(version, kind, name string, element runtime.Object, raw []byte)
// List will call visitor for every single layer in the img.
func List(img v1.Image, visitor ObjectVisitor) error {
manifest, err := img.Manifest()
if err != nil {
return err
}
layers, err := img.Layers()
if err != nil {
return err
}
layerMap := map[string]v1.Layer{}
for _, l := range layers {
digest, err := l.Digest()
if err != nil {
return err
}
layerMap[digest.String()] = l
}
for _, l := range manifest.Layers {
rawLayer, ok := layerMap[l.Digest.String()]
if !ok {
return fmt.Errorf("no image layer with digest %s exists in the bundle", l.Digest.String())
}
contents, err := readTarLayer(rawLayer)
if err != nil {
return fmt.Errorf("failed to read layer %s: %w", l.Digest, err)
}
obj, _, err := scheme.Codecs.UniversalDeserializer().Decode(contents, nil, nil)
if err != nil {
return fmt.Errorf("failed to decode layer %s to a Tekton object: %w", l.Digest, err)
}
visitor(
l.Annotations[tkremote.APIVersionAnnotation],
l.Annotations[tkremote.KindAnnotation],
l.Annotations[tkremote.TitleAnnotation],
obj,
contents,
)
}
return nil
}
// ListKind is like #List but only returns elements of a single kind. Kind will be lowercased to conform to the Tekton
// Bundle spec.
func ListKind(img v1.Image, pkind string, visitor ObjectVisitor) error {
listedItems := 0
if err := List(img, func(version, kind string, name string, element runtime.Object, raw []byte) {
if kind == pkind {
listedItems++
visitor(version, kind, name, element, raw)
}
}); err != nil {
return err
}
if listedItems == 0 {
return fmt.Errorf("no objects of kind %q found in img", pkind)
}
return nil
}
// Get returns a single named element of a specific kind from the Tekton Bundle.
func Get(img v1.Image, pkind, name string, visitor ObjectVisitor) error {
objectFound := false
if err := ListKind(img, pkind, func(version, kind, foundName string, element runtime.Object, raw []byte) {
if foundName == name {
objectFound = true
visitor(version, kind, foundName, element, raw)
}
}); err != nil {
return err
}
if !objectFound {
return fmt.Errorf("no objects of kind %q named %q found in img", pkind, name)
}
return nil
}
// readTarLayer is a helper function to read the contents of a tar'ed layer.
func readTarLayer(l v1.Layer) ([]byte, error) {
rc, err := l.Uncompressed()
if err != nil {
return nil, fmt.Errorf("Failed to read image layer: %w", err)
}
defer rc.Close()
// If the user bundled this up as a tar file then we need to untar it.
treader := tar.NewReader(rc)
header, err := treader.Next()
if err != nil {
return nil, fmt.Errorf("layer is not a tarball")
}
contents := make([]byte, header.Size)
if _, err := treader.Read(contents); err != nil && err != io.EOF {
// We only allow 1 resource per layer so this tar bundle should have one and only one file.
return nil, fmt.Errorf("failed to read tar bundle: %w", err)
}
return contents, nil
}