service_create_test.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  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. client: newMockClient(func(req *http.Request) (*http.Response, error) {
  65. if strings.HasPrefix(req.URL.Path, "/services/create") {
  66. // check if the /distribution endpoint returned correct output
  67. err := json.NewDecoder(distributionInspectBody).Decode(&distributionInspect)
  68. if err != nil {
  69. return nil, err
  70. }
  71. if len(distributionInspect.Platforms) == 0 || distributionInspect.Platforms[0].Architecture != platforms[0].Architecture || distributionInspect.Platforms[0].OS != platforms[0].OS {
  72. return nil, fmt.Errorf("received incorrect platform information from registry")
  73. }
  74. b, err := json.Marshal(types.ServiceCreateResponse{
  75. ID: "service_" + platforms[0].Architecture,
  76. })
  77. if err != nil {
  78. return nil, err
  79. }
  80. return &http.Response{
  81. StatusCode: http.StatusOK,
  82. Body: ioutil.NopCloser(bytes.NewReader(b)),
  83. }, nil
  84. } else if strings.HasPrefix(req.URL.Path, "/distribution/") {
  85. platforms = []v1.Platform{
  86. {
  87. Architecture: "amd64",
  88. OS: "linux",
  89. },
  90. }
  91. b, err := json.Marshal(registrytypes.DistributionInspect{
  92. Descriptor: v1.Descriptor{},
  93. Platforms: platforms,
  94. })
  95. if err != nil {
  96. return nil, err
  97. }
  98. distributionInspectBody = ioutil.NopCloser(bytes.NewReader(b))
  99. return &http.Response{
  100. StatusCode: http.StatusOK,
  101. Body: ioutil.NopCloser(bytes.NewReader(b)),
  102. }, nil
  103. } else {
  104. return nil, fmt.Errorf("unexpected URL '%s'", req.URL.Path)
  105. }
  106. }),
  107. }
  108. r, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, types.ServiceCreateOptions{QueryRegistry: true})
  109. if err != nil {
  110. t.Fatal(err)
  111. }
  112. if r.ID != "service_amd64" {
  113. t.Fatalf("expected `service_amd64`, got %s", r.ID)
  114. }
  115. }
  116. func TestServiceCreateDigestPinning(t *testing.T) {
  117. dgst := "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96"
  118. dgstAlt := "sha256:37ffbf3f7497c07584dc9637ffbf3f7497c0758c0537ffbf3f7497c0c88e2bb7"
  119. serviceCreateImage := ""
  120. pinByDigestTests := []struct {
  121. img string // input image provided by the user
  122. expected string // expected image after digest pinning
  123. }{
  124. // default registry returns familiar string
  125. {"docker.io/library/alpine", "alpine:latest@" + dgst},
  126. // provided tag is preserved and digest added
  127. {"alpine:edge", "alpine:edge@" + dgst},
  128. // image with provided alternative digest remains unchanged
  129. {"alpine@" + dgstAlt, "alpine@" + dgstAlt},
  130. // image with provided tag and alternative digest remains unchanged
  131. {"alpine:edge@" + dgstAlt, "alpine:edge@" + dgstAlt},
  132. // image on alternative registry does not result in familiar string
  133. {"alternate.registry/library/alpine", "alternate.registry/library/alpine:latest@" + dgst},
  134. // unresolvable image does not get a digest
  135. {"cannotresolve", "cannotresolve:latest"},
  136. }
  137. client := &Client{
  138. client: newMockClient(func(req *http.Request) (*http.Response, error) {
  139. if strings.HasPrefix(req.URL.Path, "/services/create") {
  140. // reset and set image received by the service create endpoint
  141. serviceCreateImage = ""
  142. var service swarm.ServiceSpec
  143. if err := json.NewDecoder(req.Body).Decode(&service); err != nil {
  144. return nil, fmt.Errorf("could not parse service create request")
  145. }
  146. serviceCreateImage = service.TaskTemplate.ContainerSpec.Image
  147. b, err := json.Marshal(types.ServiceCreateResponse{
  148. ID: "service_id",
  149. })
  150. if err != nil {
  151. return nil, err
  152. }
  153. return &http.Response{
  154. StatusCode: http.StatusOK,
  155. Body: ioutil.NopCloser(bytes.NewReader(b)),
  156. }, nil
  157. } else if strings.HasPrefix(req.URL.Path, "/distribution/cannotresolve") {
  158. // unresolvable image
  159. return nil, fmt.Errorf("cannot resolve image")
  160. } else if strings.HasPrefix(req.URL.Path, "/distribution/") {
  161. // resolvable images
  162. b, err := json.Marshal(registrytypes.DistributionInspect{
  163. Descriptor: v1.Descriptor{
  164. Digest: digest.Digest(dgst),
  165. },
  166. })
  167. if err != nil {
  168. return nil, err
  169. }
  170. return &http.Response{
  171. StatusCode: http.StatusOK,
  172. Body: ioutil.NopCloser(bytes.NewReader(b)),
  173. }, nil
  174. }
  175. return nil, fmt.Errorf("unexpected URL '%s'", req.URL.Path)
  176. }),
  177. }
  178. // run pin by digest tests
  179. for _, p := range pinByDigestTests {
  180. r, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{
  181. TaskTemplate: swarm.TaskSpec{
  182. ContainerSpec: swarm.ContainerSpec{
  183. Image: p.img,
  184. },
  185. },
  186. }, types.ServiceCreateOptions{QueryRegistry: true})
  187. if err != nil {
  188. t.Fatal(err)
  189. }
  190. if r.ID != "service_id" {
  191. t.Fatalf("expected `service_id`, got %s", r.ID)
  192. }
  193. if p.expected != serviceCreateImage {
  194. t.Fatalf("expected image %s, got %s", p.expected, serviceCreateImage)
  195. }
  196. }
  197. }