-
Notifications
You must be signed in to change notification settings - Fork 270
/
check_image_existence.go
118 lines (104 loc) 路 3.07 KB
/
check_image_existence.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
package artifacts
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"github.com/pkg/errors"
)
const (
realmKey = "realm="
serviceKey = "service="
scopeKey = "scope="
)
type CheckImageExistence struct {
ImageUri string
AuthHeader string
}
type tokenResponse struct {
Token string `json:"token"`
}
func (d CheckImageExistence) Run(ctx context.Context) error {
registry, repository, tag, error := splitImageUri(d.ImageUri)
if error != nil {
return error
}
requestUrl := fmt.Sprintf("https://%s/v2/%s/manifests/%s", registry, repository, tag)
req, err := http.NewRequest("GET", requestUrl, nil)
if err != nil {
return errors.Cause(err)
}
req.Header.Add("Authorization", d.AuthHeader)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return fmt.Errorf("requested image not found")
} else if resp.StatusCode == http.StatusUnauthorized && len(d.AuthHeader) == 0 {
splits := strings.Split(resp.Header.Get("www-authenticate"), ",")
var realm, service, scope string
for _, split := range splits {
if strings.Contains(split, realmKey) {
startIndex := strings.Index(split, realmKey) + len(realmKey)
realm = strings.Trim(split[startIndex:], "\"")
} else if strings.Contains(split, serviceKey) {
startIndex := strings.Index(split, serviceKey) + len(serviceKey)
service = strings.Trim(split[startIndex:], "\"")
} else if strings.Contains(split, scopeKey) {
startIndex := strings.Index(split, scopeKey) + len(scopeKey)
scope = strings.Trim(split[startIndex:], "\"")
}
}
token, err := getRegistryToken(realm, service, scope)
if err != nil {
return err
}
d.AuthHeader = "Bearer " + token
return d.Run(ctx)
} else if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unknown response: %s", resp.Status)
}
return nil
}
func getRegistryToken(realm, service, scope string) (string, error) {
requestUrl := fmt.Sprintf("%s?service=\"%s\"&scope=\"%s\"", realm, service, scope)
req, err := http.NewRequest("GET", requestUrl, nil)
if err != nil {
return "", errors.Cause(err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("failed to token from %s", requestUrl)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
tokenResp := tokenResponse{}
if err := json.Unmarshal(body, &tokenResp); err != nil {
return "", err
}
return tokenResp.Token, nil
}
func splitImageUri(imageUri string) (string, string, string, error) {
indexOfSlash := strings.Index(imageUri, "/")
if indexOfSlash < 0 {
return "", "", "", errors.Errorf("Invalid URI: %s", imageUri)
}
registry := imageUri[:indexOfSlash]
imageUriSplit := strings.Split(imageUri[len(registry)+1:], ":")
if len(imageUriSplit) < 2 {
return "", "", "", errors.Errorf("Invalid URI: %s", imageUri)
}
repository := strings.Replace(imageUriSplit[0], registry+"/", "", -1)
tag := imageUriSplit[1]
return registry, repository, tag, nil
}