123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- package client // import "github.com/docker/docker/client"
- import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
- "testing"
- "github.com/docker/docker/api/types"
- registrytypes "github.com/docker/docker/api/types/registry"
- "github.com/docker/docker/api/types/swarm"
- "github.com/docker/docker/errdefs"
- "github.com/opencontainers/go-digest"
- ocispec "github.com/opencontainers/image-spec/specs-go/v1"
- "gotest.tools/v3/assert"
- is "gotest.tools/v3/assert/cmp"
- )
- func TestServiceCreateError(t *testing.T) {
- client := &Client{
- client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
- }
- _, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, types.ServiceCreateOptions{})
- assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
- }
- func TestServiceCreate(t *testing.T) {
- expectedURL := "/services/create"
- client := &Client{
- client: newMockClient(func(req *http.Request) (*http.Response, error) {
- if !strings.HasPrefix(req.URL.Path, expectedURL) {
- return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
- }
- if req.Method != http.MethodPost {
- return nil, fmt.Errorf("expected POST method, got %s", req.Method)
- }
- b, err := json.Marshal(swarm.ServiceCreateResponse{
- ID: "service_id",
- })
- if err != nil {
- return nil, err
- }
- return &http.Response{
- StatusCode: http.StatusOK,
- Body: io.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)
- }
- }
- func TestServiceCreateCompatiblePlatforms(t *testing.T) {
- client := &Client{
- version: "1.30",
- client: newMockClient(func(req *http.Request) (*http.Response, error) {
- if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
- var serviceSpec swarm.ServiceSpec
- // check if the /distribution endpoint returned correct output
- err := json.NewDecoder(req.Body).Decode(&serviceSpec)
- if err != nil {
- return nil, err
- }
- assert.Check(t, is.Equal("foobar:1.0@sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96", serviceSpec.TaskTemplate.ContainerSpec.Image))
- assert.Check(t, is.Len(serviceSpec.TaskTemplate.Placement.Platforms, 1))
- p := serviceSpec.TaskTemplate.Placement.Platforms[0]
- b, err := json.Marshal(swarm.ServiceCreateResponse{
- ID: "service_" + p.OS + "_" + p.Architecture,
- })
- if err != nil {
- return nil, err
- }
- return &http.Response{
- StatusCode: http.StatusOK,
- Body: io.NopCloser(bytes.NewReader(b)),
- }, nil
- } else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
- b, err := json.Marshal(registrytypes.DistributionInspect{
- Descriptor: ocispec.Descriptor{
- Digest: "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96",
- },
- Platforms: []ocispec.Platform{
- {
- Architecture: "amd64",
- OS: "linux",
- },
- },
- })
- if err != nil {
- return nil, err
- }
- return &http.Response{
- StatusCode: http.StatusOK,
- Body: io.NopCloser(bytes.NewReader(b)),
- }, nil
- } else {
- return nil, fmt.Errorf("unexpected URL '%s'", req.URL.Path)
- }
- }),
- }
- spec := swarm.ServiceSpec{TaskTemplate: swarm.TaskSpec{ContainerSpec: &swarm.ContainerSpec{Image: "foobar:1.0"}}}
- r, err := client.ServiceCreate(context.Background(), spec, types.ServiceCreateOptions{QueryRegistry: true})
- assert.Check(t, err)
- assert.Check(t, is.Equal("service_linux_amd64", r.ID))
- }
- 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{
- version: "1.30",
- client: newMockClient(func(req *http.Request) (*http.Response, error) {
- if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
- // 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(swarm.ServiceCreateResponse{
- ID: "service_id",
- })
- if err != nil {
- return nil, err
- }
- return &http.Response{
- StatusCode: http.StatusOK,
- Body: io.NopCloser(bytes.NewReader(b)),
- }, nil
- } else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/cannotresolve") {
- // unresolvable image
- return nil, fmt.Errorf("cannot resolve image")
- } else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
- // resolvable images
- b, err := json.Marshal(registrytypes.DistributionInspect{
- Descriptor: ocispec.Descriptor{
- Digest: digest.Digest(dgst),
- },
- })
- if err != nil {
- return nil, err
- }
- return &http.Response{
- StatusCode: http.StatusOK,
- Body: io.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{
- ContainerSpec: &swarm.ContainerSpec{
- 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)
- }
- }
- }
|