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
Moving docker service digest pinning to client side #32388
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package client | ||
|
||
import ( | ||
"encoding/json" | ||
"net/url" | ||
|
||
registrytypes "github.com/docker/docker/api/types/registry" | ||
"golang.org/x/net/context" | ||
) | ||
|
||
// DistributionInspect returns the image digest with full Manifest | ||
func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registrytypes.DistributionInspect, error) { | ||
var headers map[string][]string | ||
|
||
if encodedRegistryAuth != "" { | ||
headers = map[string][]string{ | ||
"X-Registry-Auth": {encodedRegistryAuth}, | ||
} | ||
} | ||
|
||
// Contact the registry to retrieve digest and platform information | ||
var distributionInspect registrytypes.DistributionInspect | ||
resp, err := cli.get(ctx, "/distribution/"+image+"/json", url.Values{}, headers) | ||
if err != nil { | ||
return distributionInspect, err | ||
} | ||
|
||
err = json.NewDecoder(resp.body).Decode(&distributionInspect) | ||
ensureReaderClosed(resp) | ||
return distributionInspect, err | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,29 +2,78 @@ package client | |
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
|
||
"github.com/docker/distribution/reference" | ||
"github.com/docker/docker/api/types" | ||
"github.com/docker/docker/api/types/swarm" | ||
"github.com/opencontainers/go-digest" | ||
"golang.org/x/net/context" | ||
) | ||
|
||
// ServiceCreate creates a new Service. | ||
func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) { | ||
var headers map[string][]string | ||
var distErr error | ||
|
||
headers := map[string][]string{ | ||
"version": {cli.version}, | ||
} | ||
|
||
if options.EncodedRegistryAuth != "" { | ||
headers = map[string][]string{ | ||
"X-Registry-Auth": {options.EncodedRegistryAuth}, | ||
} | ||
headers["X-Registry-Auth"] = []string{options.EncodedRegistryAuth} | ||
} | ||
|
||
// Contact the registry to retrieve digest and platform information | ||
if options.QueryRegistry { | ||
distributionInspect, err := cli.DistributionInspect(ctx, service.TaskTemplate.ContainerSpec.Image, options.EncodedRegistryAuth) | ||
distErr = err | ||
if err == nil { | ||
// now pin by digest if the image doesn't already contain a digest | ||
img := imageWithDigestString(service.TaskTemplate.ContainerSpec.Image, distributionInspect.Descriptor.Digest) | ||
if img != "" { | ||
service.TaskTemplate.ContainerSpec.Image = img | ||
} | ||
} | ||
} | ||
var response types.ServiceCreateResponse | ||
resp, err := cli.post(ctx, "/services/create", nil, service, headers) | ||
if err != nil { | ||
return response, err | ||
} | ||
|
||
err = json.NewDecoder(resp.body).Decode(&response) | ||
|
||
if distErr != nil { | ||
response.Warnings = append(response.Warnings, digestWarning(service.TaskTemplate.ContainerSpec.Image)) | ||
} | ||
|
||
ensureReaderClosed(resp) | ||
return response, err | ||
} | ||
|
||
// imageWithDigestString takes an image string and a digest, and updates | ||
// the image string if it didn't originally contain a digest. It assumes | ||
// that the image string is not an image ID | ||
func imageWithDigestString(image string, dgst digest.Digest) string { | ||
isCanonical := false | ||
ref, err := reference.ParseAnyReference(image) | ||
if err == nil { | ||
_, isCanonical = ref.(reference.Canonical) | ||
|
||
if !isCanonical { | ||
namedRef, _ := ref.(reference.Named) | ||
img, err := reference.WithDigest(namedRef, dgst) | ||
if err == nil { | ||
return img.String() | ||
} | ||
} | ||
} | ||
return "" | ||
} | ||
|
||
// digestWarning constructs a formatted warning string using the | ||
// image name that could not be pinned by digest. The formatting | ||
// is hardcoded, but could me made smarter in the future | ||
func digestWarning(image string) string { | ||
return fmt.Sprintf("image %s could not be accessed on a registry to record\nits digest. Each node will access %s independently,\npossibly leading to different nodes running different\nversions of the image.\n", image, image) | ||
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. the 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. @tiborvass this is to add line breaks, since the text is big. This is how it is also in the daemon right now. |
||
} |
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.
@nishanttotla I'm wondering if the default shouldn't be the current behavior.
SkipRegistryQuery
?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.
@tiborvass the corresponding CLI PR is docker/cli#30.
The idea is that
QueryRegistry
should be set on allservice create
commands unless the user explicitly provides a flag to prevent it, and for allservice update
commands that update the image, using the--image
flag (also unless the user explicitly asks to not do it with the flag).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.
@tiborvass: The current behavior is that the client does not perform this query.
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.
@aaronlehmann my bad i thought this was server side.