-
Notifications
You must be signed in to change notification settings - Fork 82
/
referrer.go
161 lines (152 loc) · 4.8 KB
/
referrer.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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// Package referrer is used for responses to the referrers to a manifest
package referrer
import (
"bytes"
"fmt"
"sort"
"text/tabwriter"
"github.com/opencontainers/go-digest"
"github.com/regclient/regclient/types/descriptor"
"github.com/regclient/regclient/types/errs"
"github.com/regclient/regclient/types/manifest"
v1 "github.com/regclient/regclient/types/oci/v1"
"github.com/regclient/regclient/types/ref"
)
// ReferrerList contains the response to a request for referrers to a subject
type ReferrerList struct {
Subject ref.Ref `json:"subject"` // subject queried
Descriptors []descriptor.Descriptor `json:"descriptors"` // descriptors found in Index
Annotations map[string]string `json:"annotations,omitempty"` // annotations extracted from Index
Manifest manifest.Manifest `json:"-"` // returned OCI Index
Tags []string `json:"-"` // tags matched when fetching referrers
}
// Add appends an entry to rl.Manifest, used to modify the client managed Index
func (rl *ReferrerList) Add(m manifest.Manifest) error {
rlM, ok := rl.Manifest.GetOrig().(v1.Index)
if !ok {
return fmt.Errorf("referrer list manifest is not an OCI index for %s", rl.Subject.CommonName())
}
// if entry already exists, return
mDesc := m.GetDescriptor()
for _, d := range rlM.Manifests {
if d.Digest == mDesc.Digest {
return nil
}
}
// update descriptor, pulling up artifact type and annotations
switch mOrig := m.GetOrig().(type) {
case v1.ArtifactManifest:
mDesc.Annotations = mOrig.Annotations
mDesc.ArtifactType = mOrig.ArtifactType
case v1.Manifest:
mDesc.Annotations = mOrig.Annotations
if mOrig.ArtifactType != "" {
mDesc.ArtifactType = mOrig.ArtifactType
} else {
mDesc.ArtifactType = mOrig.Config.MediaType
}
case v1.Index:
mDesc.Annotations = mOrig.Annotations
mDesc.ArtifactType = mOrig.ArtifactType
default:
// other types are not supported
return fmt.Errorf("invalid manifest for referrer \"%t\": %w", m.GetOrig(), errs.ErrUnsupportedMediaType)
}
// append descriptor to index
rlM.Manifests = append(rlM.Manifests, mDesc)
rl.Descriptors = rlM.Manifests
err := rl.Manifest.SetOrig(rlM)
if err != nil {
return err
}
return nil
}
// Delete removes an entry from rl.Manifest, used to modify the client managed Index
func (rl *ReferrerList) Delete(m manifest.Manifest) error {
rlM, ok := rl.Manifest.GetOrig().(v1.Index)
if !ok {
return fmt.Errorf("referrer list manifest is not an OCI index for %s", rl.Subject.CommonName())
}
// delete matching entries from the list
mDesc := m.GetDescriptor()
found := false
for i := len(rlM.Manifests) - 1; i >= 0; i-- {
if rlM.Manifests[i].Digest == mDesc.Digest {
if i < len(rlM.Manifests)-1 {
rlM.Manifests = append(rlM.Manifests[:i], rlM.Manifests[i+1:]...)
} else {
rlM.Manifests = rlM.Manifests[:i]
}
found = true
}
}
if !found {
return fmt.Errorf("subject not found in referrer list%.0w", errs.ErrNotFound)
}
rl.Descriptors = rlM.Manifests
err := rl.Manifest.SetOrig(rlM)
if err != nil {
return err
}
return nil
}
// IsEmpty reports if the returned Index contains no manifests
func (rl ReferrerList) IsEmpty() bool {
rlM, ok := rl.Manifest.GetOrig().(v1.Index)
if !ok || len(rlM.Manifests) == 0 {
return true
}
return false
}
// MarshalPretty is used for printPretty template formatting
func (rl ReferrerList) MarshalPretty() ([]byte, error) {
buf := &bytes.Buffer{}
tw := tabwriter.NewWriter(buf, 0, 0, 1, ' ', 0)
if rl.Subject.Reference != "" {
fmt.Fprintf(tw, "Subject:\t%s\n", rl.Subject.Reference)
}
rRef := rl.Subject
rRef.Tag = ""
fmt.Fprintf(tw, "\t\n")
fmt.Fprintf(tw, "Referrers:\t\n")
for _, d := range rl.Descriptors {
fmt.Fprintf(tw, "\t\n")
if rRef.Reference != "" {
rRef.Digest = d.Digest.String()
fmt.Fprintf(tw, " Name:\t%s\n", rRef.CommonName())
}
err := d.MarshalPrettyTW(tw, " ")
if err != nil {
return []byte{}, err
}
}
if rl.Annotations != nil && len(rl.Annotations) > 0 {
fmt.Fprintf(tw, "Annotations:\t\n")
keys := make([]string, 0, len(rl.Annotations))
for k := range rl.Annotations {
keys = append(keys, k)
}
sort.Strings(keys)
for _, name := range keys {
val := rl.Annotations[name]
fmt.Fprintf(tw, " %s:\t%s\n", name, val)
}
}
err := tw.Flush()
return buf.Bytes(), err
}
// FallbackTag returns the ref that should be used when the registry does not support the referrers API
func FallbackTag(r ref.Ref) (ref.Ref, error) {
dig, err := digest.Parse(r.Digest)
if err != nil {
return r, fmt.Errorf("failed to parse digest for referrers: %w", err)
}
rr := r.SetTag(fmt.Sprintf("%s-%s", dig.Algorithm(), stringMax(dig.Hex(), 64)))
return rr, nil
}
func stringMax(s string, max int) string {
if len(s) <= max {
return s
}
return s[:max]
}