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