forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathhelper.go
303 lines (260 loc) · 8.6 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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package imageprune
import (
"fmt"
"net/http"
"net/url"
"sort"
"strings"
"github.com/docker/distribution/registry/api/errcode"
"github.com/golang/glog"
kmeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
kerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kubernetes/pkg/api/legacyscheme"
kapiref "k8s.io/kubernetes/pkg/api/ref"
kapi "k8s.io/kubernetes/pkg/apis/core"
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() }
// ErrBadReference denotes an invalid reference to image, imagestreamtag or imagestreamimage stored in a
// particular object. The object is identified by kind, namespace and name.
type ErrBadReference struct {
kind string
namespace string
name string
targetKind string
reference string
reason string
}
func newErrBadReferenceToImage(reference string, obj *kapi.ObjectReference, reason string) error {
kind := "<UnknownType>"
namespace := ""
name := "<unknown-name>"
if obj != nil {
kind = obj.Kind
namespace = obj.Namespace
name = obj.Name
}
return &ErrBadReference{
kind: kind,
namespace: namespace,
name: name,
reference: reference,
reason: reason,
}
}
func newErrBadReferenceTo(targetKind, reference string, obj *kapi.ObjectReference, reason string) error {
return &ErrBadReference{
kind: obj.Kind,
namespace: obj.Namespace,
name: obj.Name,
targetKind: targetKind,
reference: reference,
reason: reason,
}
}
func (e *ErrBadReference) Error() string {
return e.String()
}
func (e *ErrBadReference) String() string {
name := e.name
if len(e.namespace) > 0 {
name = e.namespace + "/" + name
}
targetKind := "docker image"
if len(e.targetKind) > 0 {
targetKind = e.targetKind
}
return fmt.Sprintf("%s[%s]: invalid %s reference %q: %s", e.kind, name, targetKind, e.reference, e.reason)
}
func getName(obj runtime.Object) string {
accessor, err := kmeta.Accessor(obj)
if err != nil {
glog.V(4).Infof("Error getting accessor for %#v", obj)
return "<unknown>"
}
ns := accessor.GetNamespace()
if len(ns) == 0 {
return accessor.GetName()
}
return fmt.Sprintf("%s/%s", ns, accessor.GetName())
}
func getKindName(obj *kapi.ObjectReference) string {
if obj == nil {
return "unknown object"
}
name := obj.Name
if len(obj.Namespace) > 0 {
name = obj.Namespace + "/" + name
}
return fmt.Sprintf("%s[%s]", obj.Kind, name)
}
func getRef(obj runtime.Object) *kapi.ObjectReference {
ref, err := kapiref.GetReference(legacyscheme.Scheme, obj)
if err != nil {
glog.Errorf("failed to get reference to object %T: %v", obj, err)
return nil
}
return ref
}