forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
helper.go
208 lines (174 loc) · 6.28 KB
/
helper.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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package prune
import (
"fmt"
"net/http"
"net/url"
"sort"
"strings"
"github.com/docker/distribution/registry/api/errcode"
"github.com/golang/glog"
kerrors "k8s.io/apimachinery/pkg/util/errors"
imageapi "github.com/openshift/origin/pkg/image/apis/image"
"github.com/openshift/origin/pkg/util/netutils"
)
// order younger images before older
type imgByAge []*imageapi.Image
func (ba imgByAge) Len() int { return len(ba) }
func (ba imgByAge) Swap(i, j int) { ba[i], ba[j] = ba[j], ba[i] }
func (ba imgByAge) Less(i, j int) bool {
return ba[i].CreationTimestamp.After(ba[j].CreationTimestamp.Time)
}
// order younger image stream before older
type isByAge []imageapi.ImageStream
func (ba isByAge) Len() int { return len(ba) }
func (ba isByAge) Swap(i, j int) { ba[i], ba[j] = ba[j], ba[i] }
func (ba isByAge) Less(i, j int) bool {
return ba[i].CreationTimestamp.After(ba[j].CreationTimestamp.Time)
}
// DetermineRegistryHost returns registry host embedded in a pull-spec of the latest unmanaged image or the
// latest imagestream from the provided lists. If no such pull-spec is found, error is returned.
func DetermineRegistryHost(images *imageapi.ImageList, imageStreams *imageapi.ImageStreamList) (string, error) {
var pullSpec string
var managedImages []*imageapi.Image
// 1st try to determine registry url from a pull spec of the youngest managed image
for i := range images.Items {
image := &images.Items[i]
if image.Annotations[imageapi.ManagedByOpenShiftAnnotation] != "true" {
continue
}
managedImages = append(managedImages, image)
}
// be sure to pick up the newest managed image which should have an up to date information
sort.Sort(imgByAge(managedImages))
if len(managedImages) > 0 {
pullSpec = managedImages[0].DockerImageReference
} else {
// 2nd try to get the pull spec from any image stream
// Sorting by creation timestamp may not get us up to date info. Modification time would be much
// better if there were such an attribute.
sort.Sort(isByAge(imageStreams.Items))
for _, is := range imageStreams.Items {
if len(is.Status.DockerImageRepository) == 0 {
continue
}
pullSpec = is.Status.DockerImageRepository
}
}
if len(pullSpec) == 0 {
return "", fmt.Errorf("no managed image found")
}
ref, err := imageapi.ParseDockerImageReference(pullSpec)
if err != nil {
return "", fmt.Errorf("unable to parse %q: %v", pullSpec, err)
}
if len(ref.Registry) == 0 {
return "", fmt.Errorf("%s does not include a registry", pullSpec)
}
return ref.Registry, nil
}
// RegistryPinger performs a health check against a registry.
type RegistryPinger interface {
// Ping performs a health check against registry. It returns registry url qualified with schema unless an
// error occurs.
Ping(registry string) (*url.URL, error)
}
// DefaultRegistryPinger implements RegistryPinger.
type DefaultRegistryPinger struct {
Client *http.Client
Insecure bool
}
// Ping verifies that the integrated registry is ready, determines its transport protocol and returns its url
// or error.
func (drp *DefaultRegistryPinger) Ping(registry string) (*url.URL, error) {
var (
registryURL *url.URL
err error
)
pathLoop:
// first try the new default / path, then fall-back to the obsolete /healthz endpoint
for _, path := range []string{"/", "/healthz"} {
registryURL, err = TryProtocolsWithRegistryURL(registry, drp.Insecure, func(u url.URL) error {
u.Path = path
healthResponse, err := drp.Client.Get(u.String())
if err != nil {
return err
}
defer healthResponse.Body.Close()
if healthResponse.StatusCode != http.StatusOK {
return &retryPath{err: fmt.Errorf("unexpected status: %s", healthResponse.Status)}
}
return nil
})
// determine whether to retry with another endpoint
switch t := err.(type) {
case *retryPath:
// return the nested error if this is the last ping attempt
err = t.err
continue pathLoop
case kerrors.Aggregate:
// if any aggregated error indicates a possible retry, do it
for _, err := range t.Errors() {
if _, ok := err.(*retryPath); ok {
continue pathLoop
}
}
}
break
}
return registryURL, err
}
// DryRunRegistryPinger implements RegistryPinger.
type DryRunRegistryPinger struct {
}
// Ping implements Ping method.
func (*DryRunRegistryPinger) Ping(registry string) (*url.URL, error) {
return url.Parse("https://" + registry)
}
// TryProtocolsWithRegistryURL runs given action with different protocols until no error is returned. The
// https protocol is the first attempt. If it fails and allowInsecure is true, http will be the next. Obtained
// errors will be concatenated and returned.
func TryProtocolsWithRegistryURL(registry string, allowInsecure bool, action func(registryURL url.URL) error) (*url.URL, error) {
var errs []error
if !strings.Contains(registry, "://") {
registry = "unset://" + registry
}
url, err := url.Parse(registry)
if err != nil {
return nil, err
}
var protos []string
switch {
case len(url.Scheme) > 0 && url.Scheme != "unset":
protos = []string{url.Scheme}
case allowInsecure || netutils.IsPrivateAddress(registry):
protos = []string{"https", "http"}
default:
protos = []string{"https"}
}
registry = url.Host
for _, proto := range protos {
glog.V(4).Infof("Trying protocol %s for the registry URL %s", proto, registry)
url.Scheme = proto
err := action(*url)
if err == nil {
return url, nil
}
if err != nil {
glog.V(4).Infof("Error with %s for %s: %v", proto, registry, err)
}
if _, ok := err.(*errcode.Errors); ok {
// we got a response back from the registry, so return it
return url, err
}
errs = append(errs, err)
if proto == "https" && strings.Contains(err.Error(), "server gave HTTP response to HTTPS client") && !allowInsecure {
errs = append(errs, fmt.Errorf("\n* Append --force-insecure if you really want to prune the registry using insecure connection."))
} else if proto == "http" && strings.Contains(err.Error(), "malformed HTTP response") {
errs = append(errs, fmt.Errorf("\n* Are you trying to connect to a TLS-enabled registry without TLS?"))
}
}
return nil, kerrors.NewAggregate(errs)
}
// retryPath is an error indicating that another connection attempt may be retried with a different path
type retryPath struct{ err error }
func (rp *retryPath) Error() string { return rp.err.Error() }