client/service_create_test.go
7c36a1af
 package client
 
 import (
 	"bytes"
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"net/http"
 	"strings"
 	"testing"
 
 	"github.com/docker/docker/api/types"
587d07cc
 	registrytypes "github.com/docker/docker/api/types/registry"
7c36a1af
 	"github.com/docker/docker/api/types/swarm"
75c7536d
 	"github.com/opencontainers/go-digest"
587d07cc
 	"github.com/opencontainers/image-spec/specs-go/v1"
f3fce00e
 	"github.com/stretchr/testify/assert"
7c36a1af
 	"golang.org/x/net/context"
 )
 
 func TestServiceCreateError(t *testing.T) {
 	client := &Client{
9a072adf
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
7c36a1af
 	}
 	_, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, types.ServiceCreateOptions{})
 	if err == nil || err.Error() != "Error response from daemon: Server error" {
 		t.Fatalf("expected a Server Error, got %v", err)
 	}
 }
 
 func TestServiceCreate(t *testing.T) {
 	expectedURL := "/services/create"
 	client := &Client{
9a072adf
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
7c36a1af
 			if !strings.HasPrefix(req.URL.Path, expectedURL) {
 				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
 			}
 			if req.Method != "POST" {
 				return nil, fmt.Errorf("expected POST method, got %s", req.Method)
 			}
 			b, err := json.Marshal(types.ServiceCreateResponse{
 				ID: "service_id",
 			})
 			if err != nil {
 				return nil, err
 			}
 			return &http.Response{
 				StatusCode: http.StatusOK,
 				Body:       ioutil.NopCloser(bytes.NewReader(b)),
 			}, nil
 		}),
 	}
 
 	r, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, types.ServiceCreateOptions{})
 	if err != nil {
 		t.Fatal(err)
 	}
 	if r.ID != "service_id" {
 		t.Fatalf("expected `service_id`, got %s", r.ID)
 	}
 }
587d07cc
 
 func TestServiceCreateCompatiblePlatforms(t *testing.T) {
 	client := &Client{
1401342f
 		version: "1.30",
587d07cc
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
1401342f
 			if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
f3fce00e
 				var serviceSpec swarm.ServiceSpec
 
7d4b8fb3
 				// check if the /distribution endpoint returned correct output
f3fce00e
 				err := json.NewDecoder(req.Body).Decode(&serviceSpec)
7d4b8fb3
 				if err != nil {
 					return nil, err
587d07cc
 				}
7d4b8fb3
 
f3fce00e
 				assert.Equal(t, "foobar:1.0@sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96", serviceSpec.TaskTemplate.ContainerSpec.Image)
 				assert.Len(t, serviceSpec.TaskTemplate.Placement.Platforms, 1)
 
 				p := serviceSpec.TaskTemplate.Placement.Platforms[0]
587d07cc
 				b, err := json.Marshal(types.ServiceCreateResponse{
f3fce00e
 					ID: "service_" + p.OS + "_" + p.Architecture,
587d07cc
 				})
 				if err != nil {
 					return nil, err
 				}
 				return &http.Response{
 					StatusCode: http.StatusOK,
 					Body:       ioutil.NopCloser(bytes.NewReader(b)),
 				}, nil
1401342f
 			} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
587d07cc
 				b, err := json.Marshal(registrytypes.DistributionInspect{
f3fce00e
 					Descriptor: v1.Descriptor{
 						Digest: "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96",
 					},
 					Platforms: []v1.Platform{
 						{
 							Architecture: "amd64",
 							OS:           "linux",
 						},
 					},
587d07cc
 				})
 				if err != nil {
 					return nil, err
 				}
 				return &http.Response{
 					StatusCode: http.StatusOK,
 					Body:       ioutil.NopCloser(bytes.NewReader(b)),
 				}, nil
 			} else {
 				return nil, fmt.Errorf("unexpected URL '%s'", req.URL.Path)
 			}
 		}),
 	}
 
72c3bcf2
 	spec := swarm.ServiceSpec{TaskTemplate: swarm.TaskSpec{ContainerSpec: &swarm.ContainerSpec{Image: "foobar:1.0"}}}
f3fce00e
 
 	r, err := client.ServiceCreate(context.Background(), spec, types.ServiceCreateOptions{QueryRegistry: true})
 	assert.NoError(t, err)
 	assert.Equal(t, "service_linux_amd64", r.ID)
587d07cc
 }
75c7536d
 
 func TestServiceCreateDigestPinning(t *testing.T) {
 	dgst := "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96"
 	dgstAlt := "sha256:37ffbf3f7497c07584dc9637ffbf3f7497c0758c0537ffbf3f7497c0c88e2bb7"
 	serviceCreateImage := ""
 	pinByDigestTests := []struct {
 		img      string // input image provided by the user
 		expected string // expected image after digest pinning
 	}{
 		// default registry returns familiar string
 		{"docker.io/library/alpine", "alpine:latest@" + dgst},
 		// provided tag is preserved and digest added
 		{"alpine:edge", "alpine:edge@" + dgst},
 		// image with provided alternative digest remains unchanged
 		{"alpine@" + dgstAlt, "alpine@" + dgstAlt},
 		// image with provided tag and alternative digest remains unchanged
 		{"alpine:edge@" + dgstAlt, "alpine:edge@" + dgstAlt},
 		// image on alternative registry does not result in familiar string
 		{"alternate.registry/library/alpine", "alternate.registry/library/alpine:latest@" + dgst},
 		// unresolvable image does not get a digest
 		{"cannotresolve", "cannotresolve:latest"},
 	}
 
 	client := &Client{
1401342f
 		version: "1.30",
75c7536d
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
1401342f
 			if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
75c7536d
 				// reset and set image received by the service create endpoint
 				serviceCreateImage = ""
 				var service swarm.ServiceSpec
 				if err := json.NewDecoder(req.Body).Decode(&service); err != nil {
 					return nil, fmt.Errorf("could not parse service create request")
 				}
 				serviceCreateImage = service.TaskTemplate.ContainerSpec.Image
 
 				b, err := json.Marshal(types.ServiceCreateResponse{
 					ID: "service_id",
 				})
 				if err != nil {
 					return nil, err
 				}
 				return &http.Response{
 					StatusCode: http.StatusOK,
 					Body:       ioutil.NopCloser(bytes.NewReader(b)),
 				}, nil
1401342f
 			} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/cannotresolve") {
75c7536d
 				// unresolvable image
 				return nil, fmt.Errorf("cannot resolve image")
1401342f
 			} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
75c7536d
 				// resolvable images
 				b, err := json.Marshal(registrytypes.DistributionInspect{
 					Descriptor: v1.Descriptor{
 						Digest: digest.Digest(dgst),
 					},
 				})
 				if err != nil {
 					return nil, err
 				}
 				return &http.Response{
 					StatusCode: http.StatusOK,
 					Body:       ioutil.NopCloser(bytes.NewReader(b)),
 				}, nil
 			}
 			return nil, fmt.Errorf("unexpected URL '%s'", req.URL.Path)
 		}),
 	}
 
 	// run pin by digest tests
 	for _, p := range pinByDigestTests {
 		r, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{
 			TaskTemplate: swarm.TaskSpec{
72c3bcf2
 				ContainerSpec: &swarm.ContainerSpec{
75c7536d
 					Image: p.img,
 				},
 			},
 		}, types.ServiceCreateOptions{QueryRegistry: true})
 
 		if err != nil {
 			t.Fatal(err)
 		}
 
 		if r.ID != "service_id" {
 			t.Fatalf("expected `service_id`, got %s", r.ID)
 		}
 
 		if p.expected != serviceCreateImage {
 			t.Fatalf("expected image %s, got %s", p.expected, serviceCreateImage)
 		}
 	}
 }