-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsource.go
More file actions
140 lines (126 loc) · 3.24 KB
/
source.go
File metadata and controls
140 lines (126 loc) · 3.24 KB
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
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company
// SPDX-License-Identifier: Apache-2.0
package cloudprofilesync
import (
"context"
"encoding/json"
"errors"
"strings"
"time"
"golang.org/x/sync/semaphore"
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/auth"
"oras.land/oras-go/v2/registry/remote/retry"
)
type Result[T any] struct {
value T
err error
}
type SourceImage struct {
Version string
Architectures []string
CreatedAt time.Time
}
type Source interface {
GetVersions(ctx context.Context) ([]SourceImage, error)
}
type OCI struct {
repo *remote.Repository
sema *semaphore.Weighted
}
type OCIParams struct {
Registry string `json:"registry"`
Repository string `json:"repository"`
Username string `json:"username"`
Password string `json:"password"` //nolint:gosec,nolintlint
Parallel int64 `json:"parallel"`
}
func NewOCI(params OCIParams, insecure bool) (*OCI, error) {
// Create a new OCI repository
repo, err := remote.NewRepository(params.Registry + "/" + params.Repository)
if err != nil {
return nil, err
}
if params.Username != "" && params.Password != "" {
repo.Client = &auth.Client{
Client: retry.DefaultClient,
Cache: auth.NewCache(),
Credential: auth.StaticCredential(params.Registry, auth.Credential{
Username: params.Username,
Password: params.Password,
}),
}
}
repo.PlainHTTP = insecure
return &OCI{
repo: repo,
sema: semaphore.NewWeighted(params.Parallel),
}, nil
}
func (o *OCI) GetVersions(ctx context.Context) ([]SourceImage, error) {
tags := []string{}
err := o.repo.Tags(ctx, "", func(t []string) error {
tags = append(tags, t...)
return nil
})
if err != nil {
return nil, err
}
out := make(chan Result[SourceImage])
for _, tag := range tags {
go func() {
if err := o.sema.Acquire(ctx, 1); err != nil {
out <- Result[SourceImage]{err: err}
return
}
defer o.sema.Release(1)
_, reader, err := o.repo.FetchReference(ctx, tag)
if err != nil {
out <- Result[SourceImage]{err: err}
return
}
defer reader.Close()
manifest := struct {
Annotations map[string]string `json:"annotations"`
}{}
err = json.NewDecoder(reader).Decode(&manifest)
if err != nil {
out <- Result[SourceImage]{err: err}
return
}
arch, ok := manifest.Annotations["architecture"]
if !ok {
out <- Result[SourceImage]{err: errors.New("architecture annotation not found in descriptor")}
return
}
created := time.Time{}
if s, ok := manifest.Annotations["org.opencontainers.image.created"]; ok {
if t, err := time.Parse(time.RFC3339, s); err == nil {
created = t
}
} else if s, ok := manifest.Annotations["created"]; ok {
if t, err := time.Parse(time.RFC3339, s); err == nil {
created = t
}
}
out <- Result[SourceImage]{
value: SourceImage{
Version: strings.ReplaceAll(tag, "_", "+"), // Follow the helm convention
Architectures: []string{arch},
CreatedAt: created,
},
}
}()
}
images := []SourceImage{}
errs := []error{}
for range tags {
result := <-out
if result.err != nil {
errs = append(errs, result.err)
continue
}
images = append(images, result.value)
}
return images, errors.Join(errs...)
}