service_create.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. package client
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "github.com/docker/distribution/reference"
  6. "github.com/docker/docker/api/types"
  7. registrytypes "github.com/docker/docker/api/types/registry"
  8. "github.com/docker/docker/api/types/swarm"
  9. "github.com/opencontainers/go-digest"
  10. "golang.org/x/net/context"
  11. )
  12. // ServiceCreate creates a new Service.
  13. func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) {
  14. var distErr error
  15. headers := map[string][]string{
  16. "version": {cli.version},
  17. }
  18. if options.EncodedRegistryAuth != "" {
  19. headers["X-Registry-Auth"] = []string{options.EncodedRegistryAuth}
  20. }
  21. // ensure that the image is tagged
  22. if taggedImg := imageWithTagString(service.TaskTemplate.ContainerSpec.Image); taggedImg != "" {
  23. service.TaskTemplate.ContainerSpec.Image = taggedImg
  24. }
  25. // Contact the registry to retrieve digest and platform information
  26. if options.QueryRegistry {
  27. distributionInspect, err := cli.DistributionInspect(ctx, service.TaskTemplate.ContainerSpec.Image, options.EncodedRegistryAuth)
  28. distErr = err
  29. if err == nil {
  30. // now pin by digest if the image doesn't already contain a digest
  31. if img := imageWithDigestString(service.TaskTemplate.ContainerSpec.Image, distributionInspect.Descriptor.Digest); img != "" {
  32. service.TaskTemplate.ContainerSpec.Image = img
  33. }
  34. // add platforms that are compatible with the service
  35. service.TaskTemplate.Placement = updateServicePlatforms(service.TaskTemplate.Placement, distributionInspect)
  36. }
  37. }
  38. var response types.ServiceCreateResponse
  39. resp, err := cli.post(ctx, "/services/create", nil, service, headers)
  40. if err != nil {
  41. return response, err
  42. }
  43. err = json.NewDecoder(resp.body).Decode(&response)
  44. if distErr != nil {
  45. response.Warnings = append(response.Warnings, digestWarning(service.TaskTemplate.ContainerSpec.Image))
  46. }
  47. ensureReaderClosed(resp)
  48. return response, err
  49. }
  50. // imageWithDigestString takes an image string and a digest, and updates
  51. // the image string if it didn't originally contain a digest. It returns
  52. // an empty string if there are no updates.
  53. func imageWithDigestString(image string, dgst digest.Digest) string {
  54. namedRef, err := reference.ParseNormalizedNamed(image)
  55. if err == nil {
  56. if _, isCanonical := namedRef.(reference.Canonical); !isCanonical {
  57. // ensure that image gets a default tag if none is provided
  58. img, err := reference.WithDigest(namedRef, dgst)
  59. if err == nil {
  60. return reference.FamiliarString(img)
  61. }
  62. }
  63. }
  64. return ""
  65. }
  66. // imageWithTagString takes an image string, and returns a tagged image
  67. // string, adding a 'latest' tag if one was not provided. It returns an
  68. // emptry string if a canonical reference was provided
  69. func imageWithTagString(image string) string {
  70. namedRef, err := reference.ParseNormalizedNamed(image)
  71. if err == nil {
  72. return reference.FamiliarString(reference.TagNameOnly(namedRef))
  73. }
  74. return ""
  75. }
  76. // updateServicePlatforms updates the Platforms in swarm.Placement to list
  77. // all compatible platforms for the service, as found in distributionInspect
  78. // and returns a pointer to the new or updated swarm.Placement struct
  79. func updateServicePlatforms(placement *swarm.Placement, distributionInspect registrytypes.DistributionInspect) *swarm.Placement {
  80. if placement == nil {
  81. placement = &swarm.Placement{}
  82. }
  83. for _, p := range distributionInspect.Platforms {
  84. placement.Platforms = append(placement.Platforms, swarm.Platform{
  85. Architecture: p.Architecture,
  86. OS: p.OS,
  87. })
  88. }
  89. return placement
  90. }
  91. // digestWarning constructs a formatted warning string using the
  92. // image name that could not be pinned by digest. The formatting
  93. // is hardcoded, but could me made smarter in the future
  94. func digestWarning(image string) string {
  95. 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)
  96. }