/
digest.go
128 lines (110 loc) · 3.7 KB
/
digest.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
package imagelock
import (
"encoding/json"
"errors"
"fmt"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/crane"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/opencontainers/go-digest"
)
// DigestInfo defines the digest information for an Architecture
type DigestInfo struct {
Digest digest.Digest
Arch string
}
func fetchImageDigests(r string, cfg *Config) ([]DigestInfo, error) {
opts := make([]crane.Option, 0)
if cfg.InsecureMode {
opts = append(opts, crane.Insecure)
}
opts = append(opts, crane.WithContext(cfg.Context))
if cfg.Auth.Username != "" && cfg.Auth.Password != "" {
opts = append(opts, crane.WithAuth(&authn.Basic{Username: cfg.Auth.Username, Password: cfg.Auth.Password}))
}
desc, err := GetImageRemoteDescriptor(r, opts...)
if err != nil {
return nil, fmt.Errorf("failed to get descriptor: %v", err)
}
switch desc.MediaType {
case types.OCIImageIndex, types.DockerManifestList:
var idx v1.IndexManifest
if err := json.Unmarshal(desc.Manifest, &idx); err != nil {
return nil, fmt.Errorf("failed to parse images data")
}
digests, err := readDigestsInfoFromIndex(idx)
if err != nil {
return nil, fmt.Errorf("failed to parse multi-arch image digests from remote descriptor: %w", err)
}
return digests, nil
case types.OCIManifestSchema1, types.DockerManifestSchema2:
img, err := desc.Image()
if err != nil {
return nil, fmt.Errorf("faild to get image from descriptor: %w", err)
}
digest, err := readDigestInfoFromImage(img)
if err != nil {
return nil, fmt.Errorf("failed to parse image digest from remote descriptor: %w", err)
}
return []DigestInfo{digest}, nil
default:
return nil, fmt.Errorf("unknown media type %q", desc.MediaType)
}
}
// GetImageRemoteDescriptor returns the image descriptor
func GetImageRemoteDescriptor(image string, opts ...crane.Option) (*remote.Descriptor, error) {
o := crane.GetOptions(opts...)
ref, err := name.ParseReference(image, o.Name...)
if err != nil {
return nil, fmt.Errorf("failed to parse reference %q: %w", image, err)
}
return remote.Get(ref, o.Remote...)
}
func readDigestsInfoFromIndex(idx v1.IndexManifest) ([]DigestInfo, error) {
digests := make([]DigestInfo, 0)
var allErrors error
for _, img := range idx.Manifests {
// Skip attestations
if img.Annotations["vnd.docker.reference.type"] == "attestation-manifest" {
continue
}
switch img.MediaType {
case types.OCIManifestSchema1, types.DockerManifestSchema2:
platform := img.Platform
if platform == nil {
allErrors = errors.Join(allErrors, fmt.Errorf("image does not define a platform"))
continue
}
imgDigest := DigestInfo{
Digest: digest.Digest(img.Digest.String()),
Arch: fmt.Sprintf("%s/%s", platform.OS, platform.Architecture),
}
digests = append(digests, imgDigest)
default:
allErrors = errors.Join(allErrors, fmt.Errorf("unknown media type %q", img.MediaType))
continue
}
}
return digests, allErrors
}
func readDigestInfoFromImage(img v1.Image) (DigestInfo, error) {
conf, err := img.ConfigFile()
if err != nil {
return DigestInfo{}, fmt.Errorf("faild to get image config: %w", err)
}
platform := conf.Platform()
if platform == nil {
return DigestInfo{}, fmt.Errorf("failed to obtain image platform")
}
digestData, err := img.Digest()
if err != nil {
return DigestInfo{}, fmt.Errorf("failed to get image digest: %w", err)
}
return DigestInfo{
Arch: fmt.Sprintf("%s/%s", platform.OS, platform.Architecture),
Digest: digest.Digest(digestData.String()),
}, nil
}