-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Image pruner: Determine protocol just once #14914
Merged
openshift-merge-robot
merged 2 commits into
openshift:master
from
miminar:secure-image-prune
Aug 17, 2017
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// Package prune contains logic for pruning images and interoperating with the integrated Docker registry. | ||
package prune |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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.")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Golint errors: error strings should not be capitalized or end with punctuation or a newline. More info. |
||
} 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() } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Golint comments: should have a package comment, unless it's in another file for this package. More info.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's in another file.