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