1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Moving docker service digest pinning to client side

Signed-off-by: Nishant Totla <nishanttotla@gmail.com>
This commit is contained in:
Nishant Totla 2017-04-05 15:43:17 -07:00
parent 6ef7afce83
commit c1635c1ae3
No known key found for this signature in database
GPG key ID: 7EA5781C9B3D0C19
6 changed files with 122 additions and 2 deletions

View file

@ -276,6 +276,12 @@ type ServiceCreateOptions struct {
//
// This field follows the format of the X-Registry-Auth header.
EncodedRegistryAuth string
// QueryRegistry indicates whether the service update requires
// contacting a registry. A registry may be contacted to retrieve
// the image digest and manifest, which in turn can be used to update
// platform or other information about the service.
QueryRegistry bool
}
// ServiceCreateResponse contains the information returned to a client
@ -315,6 +321,12 @@ type ServiceUpdateOptions struct {
// The valid values are "previous" and "none". An empty value is the
// same as "none".
Rollback string
// QueryRegistry indicates whether the service update requires
// contacting a registry. A registry may be contacted to retrieve
// the image digest and manifest, which in turn can be used to update
// platform or other information about the service.
QueryRegistry bool
}
// ServiceListOptions holds parameters to list services with.

View file

@ -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
}

View file

@ -20,6 +20,7 @@ import (
type CommonAPIClient interface {
ConfigAPIClient
ContainerAPIClient
DistributionAPIClient
ImageAPIClient
NodeAPIClient
NetworkAPIClient
@ -69,6 +70,11 @@ type ContainerAPIClient interface {
ContainersPrune(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error)
}
// DistributionAPIClient defines API client methods for the registry
type DistributionAPIClient interface {
DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registry.DistributionInspect, error)
}
// ImageAPIClient defines API client methods for the images
type ImageAPIClient interface {
ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)

View file

@ -2,15 +2,21 @@ 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 (
headers map[string][]string
distErr error
)
if options.EncodedRegistryAuth != "" {
headers = map[string][]string{
@ -18,6 +24,18 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
}
}
// 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 {
@ -25,6 +43,38 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
}
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)
}

View file

@ -15,6 +15,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
var (
headers map[string][]string
query = url.Values{}
distErr error
)
if options.EncodedRegistryAuth != "" {
@ -33,6 +34,20 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
query.Set("version", strconv.FormatUint(version.Index, 10))
// Contact the registry to retrieve digest and platform information
// This happens only when the image has changed
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.ServiceUpdateResponse
resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers)
if err != nil {
@ -40,6 +55,11 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
}
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
}

View file

@ -1,9 +1,10 @@
package client
import (
"github.com/docker/docker/api/types/filters"
"net/url"
"regexp"
"github.com/docker/docker/api/types/filters"
)
var headerRegexp = regexp.MustCompile(`\ADocker/.+\s\((.+)\)\z`)