service_create.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. package client
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "strings"
  6. "github.com/docker/distribution/reference"
  7. "github.com/docker/docker/api/types"
  8. "github.com/docker/docker/api/types/swarm"
  9. digest "github.com/opencontainers/go-digest"
  10. "github.com/pkg/errors"
  11. "golang.org/x/net/context"
  12. )
  13. // ServiceCreate creates a new Service.
  14. func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) {
  15. var distErr error
  16. headers := map[string][]string{
  17. "version": {cli.version},
  18. }
  19. if options.EncodedRegistryAuth != "" {
  20. headers["X-Registry-Auth"] = []string{options.EncodedRegistryAuth}
  21. }
  22. // Make sure containerSpec is not nil when no runtime is set or the runtime is set to container
  23. if service.TaskTemplate.ContainerSpec == nil && (service.TaskTemplate.Runtime == "" || service.TaskTemplate.Runtime == swarm.RuntimeContainer) {
  24. service.TaskTemplate.ContainerSpec = &swarm.ContainerSpec{}
  25. }
  26. if err := validateServiceSpec(service); err != nil {
  27. return types.ServiceCreateResponse{}, err
  28. }
  29. // ensure that the image is tagged
  30. var imgPlatforms []swarm.Platform
  31. if service.TaskTemplate.ContainerSpec != nil {
  32. if taggedImg := imageWithTagString(service.TaskTemplate.ContainerSpec.Image); taggedImg != "" {
  33. service.TaskTemplate.ContainerSpec.Image = taggedImg
  34. }
  35. if options.QueryRegistry {
  36. var img string
  37. img, imgPlatforms, distErr = imageDigestAndPlatforms(ctx, cli, service.TaskTemplate.ContainerSpec.Image, options.EncodedRegistryAuth)
  38. if img != "" {
  39. service.TaskTemplate.ContainerSpec.Image = img
  40. }
  41. }
  42. }
  43. // ensure that the image is tagged
  44. if service.TaskTemplate.PluginSpec != nil {
  45. if taggedImg := imageWithTagString(service.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
  46. service.TaskTemplate.PluginSpec.Remote = taggedImg
  47. }
  48. if options.QueryRegistry {
  49. var img string
  50. img, imgPlatforms, distErr = imageDigestAndPlatforms(ctx, cli, service.TaskTemplate.PluginSpec.Remote, options.EncodedRegistryAuth)
  51. if img != "" {
  52. service.TaskTemplate.PluginSpec.Remote = img
  53. }
  54. }
  55. }
  56. if service.TaskTemplate.Placement == nil && len(imgPlatforms) > 0 {
  57. service.TaskTemplate.Placement = &swarm.Placement{}
  58. }
  59. if len(imgPlatforms) > 0 {
  60. service.TaskTemplate.Placement.Platforms = imgPlatforms
  61. }
  62. var response types.ServiceCreateResponse
  63. resp, err := cli.post(ctx, "/services/create", nil, service, headers)
  64. if err != nil {
  65. return response, err
  66. }
  67. err = json.NewDecoder(resp.body).Decode(&response)
  68. if distErr != nil {
  69. response.Warnings = append(response.Warnings, digestWarning(service.TaskTemplate.ContainerSpec.Image))
  70. }
  71. ensureReaderClosed(resp)
  72. return response, err
  73. }
  74. func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) {
  75. distributionInspect, err := cli.DistributionInspect(ctx, image, encodedAuth)
  76. var platforms []swarm.Platform
  77. if err != nil {
  78. return "", nil, err
  79. }
  80. imageWithDigest := imageWithDigestString(image, distributionInspect.Descriptor.Digest)
  81. if len(distributionInspect.Platforms) > 0 {
  82. platforms = make([]swarm.Platform, 0, len(distributionInspect.Platforms))
  83. for _, p := range distributionInspect.Platforms {
  84. // clear architecture field for arm. This is a temporary patch to address
  85. // https://github.com/docker/swarmkit/issues/2294. The issue is that while
  86. // image manifests report "arm" as the architecture, the node reports
  87. // something like "armv7l" (includes the variant), which causes arm images
  88. // to stop working with swarm mode. This patch removes the architecture
  89. // constraint for arm images to ensure tasks get scheduled.
  90. arch := p.Architecture
  91. if strings.ToLower(arch) == "arm" {
  92. arch = ""
  93. }
  94. platforms = append(platforms, swarm.Platform{
  95. Architecture: arch,
  96. OS: p.OS,
  97. })
  98. }
  99. }
  100. return imageWithDigest, platforms, err
  101. }
  102. // imageWithDigestString takes an image string and a digest, and updates
  103. // the image string if it didn't originally contain a digest. It returns
  104. // an empty string if there are no updates.
  105. func imageWithDigestString(image string, dgst digest.Digest) string {
  106. namedRef, err := reference.ParseNormalizedNamed(image)
  107. if err == nil {
  108. if _, isCanonical := namedRef.(reference.Canonical); !isCanonical {
  109. // ensure that image gets a default tag if none is provided
  110. img, err := reference.WithDigest(namedRef, dgst)
  111. if err == nil {
  112. return reference.FamiliarString(img)
  113. }
  114. }
  115. }
  116. return ""
  117. }
  118. // imageWithTagString takes an image string, and returns a tagged image
  119. // string, adding a 'latest' tag if one was not provided. It returns an
  120. // emptry string if a canonical reference was provided
  121. func imageWithTagString(image string) string {
  122. namedRef, err := reference.ParseNormalizedNamed(image)
  123. if err == nil {
  124. return reference.FamiliarString(reference.TagNameOnly(namedRef))
  125. }
  126. return ""
  127. }
  128. // digestWarning constructs a formatted warning string using the
  129. // image name that could not be pinned by digest. The formatting
  130. // is hardcoded, but could me made smarter in the future
  131. func digestWarning(image string) string {
  132. return fmt.Sprintf("image %s could not be accessed on a registry to record\nits digest. Each node will access %s independently,\npossibly leading to different nodes running different\nversions of the image.\n", image, image)
  133. }
  134. func validateServiceSpec(s swarm.ServiceSpec) error {
  135. if s.TaskTemplate.ContainerSpec != nil && s.TaskTemplate.PluginSpec != nil {
  136. return errors.New("must not specify both a container spec and a plugin spec in the task template")
  137. }
  138. if s.TaskTemplate.PluginSpec != nil && s.TaskTemplate.Runtime != swarm.RuntimePlugin {
  139. return errors.New("mismatched runtime with plugin spec")
  140. }
  141. if s.TaskTemplate.ContainerSpec != nil && (s.TaskTemplate.Runtime != "" && s.TaskTemplate.Runtime != swarm.RuntimeContainer) {
  142. return errors.New("mismatched runtime with container spec")
  143. }
  144. return nil
  145. }