@@ -3,10 +3,12 @@ package ociindex
3
3
import (
4
4
"encoding/json"
5
5
"io"
6
+ "maps"
6
7
"os"
7
8
"path"
8
9
"syscall"
9
10
11
+ "github.com/containerd/containerd/reference"
10
12
"github.com/gofrs/flock"
11
13
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
12
14
"github.com/pkg/errors"
@@ -15,6 +17,8 @@ import (
15
17
const (
16
18
// lockFileSuffix is the suffix of the lock file
17
19
lockFileSuffix = ".lock"
20
+
21
+ annotationImageName = "io.containerd.image.name"
18
22
)
19
23
20
24
type StoreIndex struct {
@@ -23,6 +27,19 @@ type StoreIndex struct {
23
27
layoutPath string
24
28
}
25
29
30
+ type NameOrTag struct {
31
+ isTag bool
32
+ value string
33
+ }
34
+
35
+ func Name (name string ) NameOrTag {
36
+ return NameOrTag {value : name }
37
+ }
38
+
39
+ func Tag (tag string ) NameOrTag {
40
+ return NameOrTag {isTag : true , value : tag }
41
+ }
42
+
26
43
func NewStoreIndex (storePath string ) StoreIndex {
27
44
indexPath := path .Join (storePath , ocispecs .ImageIndexFile )
28
45
layoutPath := path .Join (storePath , ocispecs .ImageLayoutFile )
@@ -61,7 +78,7 @@ func (s StoreIndex) Read() (*ocispecs.Index, error) {
61
78
return & idx , nil
62
79
}
63
80
64
- func (s StoreIndex ) Put (tag string , desc ocispecs.Descriptor ) error {
81
+ func (s StoreIndex ) Put (desc ocispecs.Descriptor , names ... NameOrTag ) error {
65
82
// lock the store to prevent concurrent access
66
83
lock := flock .New (s .lockPath )
67
84
locked , err := lock .TryLock ()
@@ -107,8 +124,19 @@ func (s StoreIndex) Put(tag string, desc ocispecs.Descriptor) error {
107
124
}
108
125
109
126
setOCIIndexDefaults (& idx )
110
- if err = insertDesc (& idx , desc , tag ); err != nil {
111
- return err
127
+
128
+ namesp := make ([]* NameOrTag , 0 , len (names ))
129
+ for _ , n := range names {
130
+ namesp = append (namesp , & n )
131
+ }
132
+ if len (names ) == 0 {
133
+ namesp = append (namesp , nil )
134
+ }
135
+
136
+ for _ , name := range namesp {
137
+ if err = insertDesc (& idx , desc , name ); err != nil {
138
+ return err
139
+ }
112
140
}
113
141
114
142
idxData , err = json .Marshal (idx )
@@ -130,6 +158,12 @@ func (s StoreIndex) Get(tag string) (*ocispecs.Descriptor, error) {
130
158
return nil , err
131
159
}
132
160
161
+ for _ , m := range idx .Manifests {
162
+ if t , ok := m .Annotations [annotationImageName ]; ok && t == tag {
163
+ return & m , nil
164
+ }
165
+ }
166
+
133
167
for _ , m := range idx .Manifests {
134
168
if t , ok := m .Annotations [ocispecs .AnnotationRefName ]; ok && t == tag {
135
169
return & m , nil
@@ -165,20 +199,34 @@ func setOCIIndexDefaults(index *ocispecs.Index) {
165
199
166
200
// insertDesc puts desc to index with tag.
167
201
// Existing manifests with the same tag will be removed from the index.
168
- func insertDesc (index * ocispecs.Index , desc ocispecs.Descriptor , tag string ) error {
202
+ func insertDesc (index * ocispecs.Index , in ocispecs.Descriptor , name * NameOrTag ) error {
169
203
if index == nil {
170
204
return nil
171
205
}
172
206
173
- if tag != "" {
207
+ // make a copy to not modify the input descriptor
208
+ desc := in
209
+ desc .Annotations = maps .Clone (in .Annotations )
210
+
211
+ if name != nil {
174
212
if desc .Annotations == nil {
175
213
desc .Annotations = make (map [string ]string )
176
214
}
177
- desc .Annotations [ocispecs .AnnotationRefName ] = tag
178
- // remove existing manifests with the same tag
215
+ imgName , refName := name .value , name .value
216
+ if name .isTag {
217
+ imgName = ""
218
+ } else {
219
+ refName = ociReferenceName (imgName )
220
+ }
221
+
222
+ if imgName != "" {
223
+ desc .Annotations [annotationImageName ] = imgName
224
+ }
225
+ desc .Annotations [ocispecs .AnnotationRefName ] = refName
226
+ // remove existing manifests with the same tag/name
179
227
var manifests []ocispecs.Descriptor
180
228
for _ , m := range index .Manifests {
181
- if m .Annotations [ocispecs .AnnotationRefName ] != tag {
229
+ if m .Annotations [ocispecs .AnnotationRefName ] != refName || m . Annotations [ annotationImageName ] != imgName {
182
230
manifests = append (manifests , m )
183
231
}
184
232
}
@@ -187,3 +235,20 @@ func insertDesc(index *ocispecs.Index, desc ocispecs.Descriptor, tag string) err
187
235
index .Manifests = append (index .Manifests , desc )
188
236
return nil
189
237
}
238
+
239
+ // ociReferenceName takes the loosely defined reference name same way as
240
+ // containerd tar exporter does.
241
+ func ociReferenceName (name string ) string {
242
+ // OCI defines the reference name as only a tag excluding the
243
+ // repository. The containerd annotation contains the full image name
244
+ // since the tag is insufficient for correctly naming and referring to an
245
+ // image
246
+ var ociRef string
247
+ if spec , err := reference .Parse (name ); err == nil {
248
+ ociRef = spec .Object
249
+ } else {
250
+ ociRef = name
251
+ }
252
+
253
+ return ociRef
254
+ }
0 commit comments