Merge pull request #386 from thaJeztah/fix-image-resolve-detection

Fix image resolve detection
This commit is contained in:
Vincent Demeester 2017-08-22 15:05:39 +02:00 committed by GitHub
commit 8da1daeefa
3 changed files with 103 additions and 6 deletions

View File

@ -34,10 +34,13 @@ type fakeClient struct {
nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error) nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error)
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error) taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
nodeInspectWithRaw func(ref string) (swarm.Node, []byte, error) nodeInspectWithRaw func(ref string) (swarm.Node, []byte, error)
serviceRemoveFunc func(serviceID string) error
networkRemoveFunc func(networkID string) error serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error)
secretRemoveFunc func(secretID string) error
configRemoveFunc func(configID string) error serviceRemoveFunc func(serviceID string) error
networkRemoveFunc func(networkID string) error
secretRemoveFunc func(secretID string) error
configRemoveFunc func(configID string) error
} }
func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) { func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) {
@ -132,6 +135,14 @@ func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, ref string) (swar
return swarm.Node{}, nil, nil return swarm.Node{}, nil, nil
} }
func (cli *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
if cli.serviceUpdateFunc != nil {
return cli.serviceUpdateFunc(serviceID, version, service, options)
}
return types.ServiceUpdateResponse{}, nil
}
func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) error { func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) error {
if cli.serviceRemoveFunc != nil { if cli.serviceRemoveFunc != nil {
return cli.serviceRemoveFunc(serviceID) return cli.serviceRemoveFunc(serviceID)

View File

@ -318,10 +318,17 @@ func deployServices(
updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth} updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
if resolveImage == resolveImageAlways || (resolveImage == resolveImageChanged && image != service.Spec.Labels[convert.LabelImage]) { switch {
case resolveImage == resolveImageAlways || (resolveImage == resolveImageChanged && image != service.Spec.Labels[convert.LabelImage]):
// image should be updated by the server using QueryRegistry
updateOpts.QueryRegistry = true updateOpts.QueryRegistry = true
case image == service.Spec.Labels[convert.LabelImage]:
// image has not changed; update the serviceSpec with the
// existing information that was set by QueryRegistry on the
// previous deploy. Otherwise this will trigger an incorrect
// service update.
serviceSpec.TaskTemplate.ContainerSpec.Image = service.Spec.TaskTemplate.ContainerSpec.Image
} }
response, err := apiClient.ServiceUpdate( response, err := apiClient.ServiceUpdate(
ctx, ctx,
service.ID, service.ID,

View File

@ -5,6 +5,8 @@ import (
"github.com/docker/cli/cli/compose/convert" "github.com/docker/cli/cli/compose/convert"
"github.com/docker/cli/cli/internal/test" "github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -22,3 +24,80 @@ func TestPruneServices(t *testing.T) {
pruneServices(ctx, dockerCli, namespace, services) pruneServices(ctx, dockerCli, namespace, services)
assert.Equal(t, buildObjectIDs([]string{objectName("foo", "remove")}), client.removedServices) assert.Equal(t, buildObjectIDs([]string{objectName("foo", "remove")}), client.removedServices)
} }
// TestServiceUpdateResolveImageChanged tests that the service's
// image digest is preserved if the image did not change in the compose file
func TestServiceUpdateResolveImageChanged(t *testing.T) {
namespace := convert.NewNamespace("mystack")
var (
receivedOptions types.ServiceUpdateOptions
receivedService swarm.ServiceSpec
)
client := test.NewFakeCli(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{
{
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{
Name: namespace.Name() + "_myservice",
Labels: map[string]string{"com.docker.stack.image": "foobar:1.2.3"},
},
TaskTemplate: swarm.TaskSpec{
ContainerSpec: swarm.ContainerSpec{
Image: "foobar:1.2.3@sha256:deadbeef",
},
},
},
},
}, nil
},
serviceUpdateFunc: func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
receivedOptions = options
receivedService = service
return types.ServiceUpdateResponse{}, nil
},
})
var testcases = []struct {
image string
expectedQueryRegistry bool
expectedImage string
}{
// Image not changed
{
image: "foobar:1.2.3",
expectedQueryRegistry: false,
expectedImage: "foobar:1.2.3@sha256:deadbeef",
},
// Image changed
{
image: "foobar:1.2.4",
expectedQueryRegistry: true,
expectedImage: "foobar:1.2.4",
},
}
ctx := context.Background()
for _, testcase := range testcases {
t.Logf("Testing image %q", testcase.image)
spec := map[string]swarm.ServiceSpec{
"myservice": {
TaskTemplate: swarm.TaskSpec{
ContainerSpec: swarm.ContainerSpec{
Image: testcase.image,
},
},
},
}
err := deployServices(ctx, client, spec, namespace, false, resolveImageChanged)
assert.NoError(t, err)
assert.Equal(t, testcase.expectedQueryRegistry, receivedOptions.QueryRegistry)
assert.Equal(t, testcase.expectedImage, receivedService.TaskTemplate.ContainerSpec.Image)
receivedService = swarm.ServiceSpec{}
receivedOptions = types.ServiceUpdateOptions{}
}
}