service_create_test.go 6.6 KB

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