github.com/rhatdan/docker@v0.7.7-0.20180119204836-47a0dcbcd20a/client/service_create_test.go (about)

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/docker/docker/api/types"
    13  	registrytypes "github.com/docker/docker/api/types/registry"
    14  	"github.com/docker/docker/api/types/swarm"
    15  	"github.com/opencontainers/go-digest"
    16  	"github.com/opencontainers/image-spec/specs-go/v1"
    17  	"github.com/stretchr/testify/assert"
    18  	"golang.org/x/net/context"
    19  )
    20  
    21  func TestServiceCreateError(t *testing.T) {
    22  	client := &Client{
    23  		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
    24  	}
    25  	_, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, types.ServiceCreateOptions{})
    26  	if err == nil || err.Error() != "Error response from daemon: Server error" {
    27  		t.Fatalf("expected a Server Error, got %v", err)
    28  	}
    29  }
    30  
    31  func TestServiceCreate(t *testing.T) {
    32  	expectedURL := "/services/create"
    33  	client := &Client{
    34  		client: newMockClient(func(req *http.Request) (*http.Response, error) {
    35  			if !strings.HasPrefix(req.URL.Path, expectedURL) {
    36  				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
    37  			}
    38  			if req.Method != "POST" {
    39  				return nil, fmt.Errorf("expected POST method, got %s", req.Method)
    40  			}
    41  			b, err := json.Marshal(types.ServiceCreateResponse{
    42  				ID: "service_id",
    43  			})
    44  			if err != nil {
    45  				return nil, err
    46  			}
    47  			return &http.Response{
    48  				StatusCode: http.StatusOK,
    49  				Body:       ioutil.NopCloser(bytes.NewReader(b)),
    50  			}, nil
    51  		}),
    52  	}
    53  
    54  	r, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, types.ServiceCreateOptions{})
    55  	if err != nil {
    56  		t.Fatal(err)
    57  	}
    58  	if r.ID != "service_id" {
    59  		t.Fatalf("expected `service_id`, got %s", r.ID)
    60  	}
    61  }
    62  
    63  func TestServiceCreateCompatiblePlatforms(t *testing.T) {
    64  	client := &Client{
    65  		version: "1.30",
    66  		client: newMockClient(func(req *http.Request) (*http.Response, error) {
    67  			if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
    68  				var serviceSpec swarm.ServiceSpec
    69  
    70  				// check if the /distribution endpoint returned correct output
    71  				err := json.NewDecoder(req.Body).Decode(&serviceSpec)
    72  				if err != nil {
    73  					return nil, err
    74  				}
    75  
    76  				assert.Equal(t, "foobar:1.0@sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96", serviceSpec.TaskTemplate.ContainerSpec.Image)
    77  				assert.Len(t, serviceSpec.TaskTemplate.Placement.Platforms, 1)
    78  
    79  				p := serviceSpec.TaskTemplate.Placement.Platforms[0]
    80  				b, err := json.Marshal(types.ServiceCreateResponse{
    81  					ID: "service_" + p.OS + "_" + p.Architecture,
    82  				})
    83  				if err != nil {
    84  					return nil, err
    85  				}
    86  				return &http.Response{
    87  					StatusCode: http.StatusOK,
    88  					Body:       ioutil.NopCloser(bytes.NewReader(b)),
    89  				}, nil
    90  			} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
    91  				b, err := json.Marshal(registrytypes.DistributionInspect{
    92  					Descriptor: v1.Descriptor{
    93  						Digest: "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96",
    94  					},
    95  					Platforms: []v1.Platform{
    96  						{
    97  							Architecture: "amd64",
    98  							OS:           "linux",
    99  						},
   100  					},
   101  				})
   102  				if err != nil {
   103  					return nil, err
   104  				}
   105  				return &http.Response{
   106  					StatusCode: http.StatusOK,
   107  					Body:       ioutil.NopCloser(bytes.NewReader(b)),
   108  				}, nil
   109  			} else {
   110  				return nil, fmt.Errorf("unexpected URL '%s'", req.URL.Path)
   111  			}
   112  		}),
   113  	}
   114  
   115  	spec := swarm.ServiceSpec{TaskTemplate: swarm.TaskSpec{ContainerSpec: &swarm.ContainerSpec{Image: "foobar:1.0"}}}
   116  
   117  	r, err := client.ServiceCreate(context.Background(), spec, types.ServiceCreateOptions{QueryRegistry: true})
   118  	assert.NoError(t, err)
   119  	assert.Equal(t, "service_linux_amd64", r.ID)
   120  }
   121  
   122  func TestServiceCreateDigestPinning(t *testing.T) {
   123  	dgst := "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96"
   124  	dgstAlt := "sha256:37ffbf3f7497c07584dc9637ffbf3f7497c0758c0537ffbf3f7497c0c88e2bb7"
   125  	serviceCreateImage := ""
   126  	pinByDigestTests := []struct {
   127  		img      string // input image provided by the user
   128  		expected string // expected image after digest pinning
   129  	}{
   130  		// default registry returns familiar string
   131  		{"docker.io/library/alpine", "alpine:latest@" + dgst},
   132  		// provided tag is preserved and digest added
   133  		{"alpine:edge", "alpine:edge@" + dgst},
   134  		// image with provided alternative digest remains unchanged
   135  		{"alpine@" + dgstAlt, "alpine@" + dgstAlt},
   136  		// image with provided tag and alternative digest remains unchanged
   137  		{"alpine:edge@" + dgstAlt, "alpine:edge@" + dgstAlt},
   138  		// image on alternative registry does not result in familiar string
   139  		{"alternate.registry/library/alpine", "alternate.registry/library/alpine:latest@" + dgst},
   140  		// unresolvable image does not get a digest
   141  		{"cannotresolve", "cannotresolve:latest"},
   142  	}
   143  
   144  	client := &Client{
   145  		version: "1.30",
   146  		client: newMockClient(func(req *http.Request) (*http.Response, error) {
   147  			if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
   148  				// reset and set image received by the service create endpoint
   149  				serviceCreateImage = ""
   150  				var service swarm.ServiceSpec
   151  				if err := json.NewDecoder(req.Body).Decode(&service); err != nil {
   152  					return nil, fmt.Errorf("could not parse service create request")
   153  				}
   154  				serviceCreateImage = service.TaskTemplate.ContainerSpec.Image
   155  
   156  				b, err := json.Marshal(types.ServiceCreateResponse{
   157  					ID: "service_id",
   158  				})
   159  				if err != nil {
   160  					return nil, err
   161  				}
   162  				return &http.Response{
   163  					StatusCode: http.StatusOK,
   164  					Body:       ioutil.NopCloser(bytes.NewReader(b)),
   165  				}, nil
   166  			} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/cannotresolve") {
   167  				// unresolvable image
   168  				return nil, fmt.Errorf("cannot resolve image")
   169  			} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
   170  				// resolvable images
   171  				b, err := json.Marshal(registrytypes.DistributionInspect{
   172  					Descriptor: v1.Descriptor{
   173  						Digest: digest.Digest(dgst),
   174  					},
   175  				})
   176  				if err != nil {
   177  					return nil, err
   178  				}
   179  				return &http.Response{
   180  					StatusCode: http.StatusOK,
   181  					Body:       ioutil.NopCloser(bytes.NewReader(b)),
   182  				}, nil
   183  			}
   184  			return nil, fmt.Errorf("unexpected URL '%s'", req.URL.Path)
   185  		}),
   186  	}
   187  
   188  	// run pin by digest tests
   189  	for _, p := range pinByDigestTests {
   190  		r, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{
   191  			TaskTemplate: swarm.TaskSpec{
   192  				ContainerSpec: &swarm.ContainerSpec{
   193  					Image: p.img,
   194  				},
   195  			},
   196  		}, types.ServiceCreateOptions{QueryRegistry: true})
   197  
   198  		if err != nil {
   199  			t.Fatal(err)
   200  		}
   201  
   202  		if r.ID != "service_id" {
   203  			t.Fatalf("expected `service_id`, got %s", r.ID)
   204  		}
   205  
   206  		if p.expected != serviceCreateImage {
   207  			t.Fatalf("expected image %s, got %s", p.expected, serviceCreateImage)
   208  		}
   209  	}
   210  }