helpers.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. package swarm // import "github.com/docker/docker/api/server/router/swarm"
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "github.com/docker/docker/api/server/httputils"
  7. basictypes "github.com/docker/docker/api/types"
  8. "github.com/docker/docker/api/types/backend"
  9. "github.com/docker/docker/api/types/swarm"
  10. "github.com/docker/docker/api/types/versions"
  11. )
  12. // swarmLogs takes an http response, request, and selector, and writes the logs
  13. // specified by the selector to the response
  14. func (sr *swarmRouter) swarmLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, selector *backend.LogSelector) error {
  15. // Args are validated before the stream starts because when it starts we're
  16. // sending HTTP 200 by writing an empty chunk of data to tell the client that
  17. // daemon is going to stream. By sending this initial HTTP 200 we can't report
  18. // any error after the stream starts (i.e. container not found, wrong parameters)
  19. // with the appropriate status code.
  20. stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
  21. if !(stdout || stderr) {
  22. return fmt.Errorf("Bad parameters: you must choose at least one stream")
  23. }
  24. // there is probably a neater way to manufacture the ContainerLogsOptions
  25. // struct, probably in the caller, to eliminate the dependency on net/http
  26. logsConfig := &basictypes.ContainerLogsOptions{
  27. Follow: httputils.BoolValue(r, "follow"),
  28. Timestamps: httputils.BoolValue(r, "timestamps"),
  29. Since: r.Form.Get("since"),
  30. Tail: r.Form.Get("tail"),
  31. ShowStdout: stdout,
  32. ShowStderr: stderr,
  33. Details: httputils.BoolValue(r, "details"),
  34. }
  35. tty := false
  36. // checking for whether logs are TTY involves iterating over every service
  37. // and task. idk if there is a better way
  38. for _, service := range selector.Services {
  39. s, err := sr.backend.GetService(service, false)
  40. if err != nil {
  41. // maybe should return some context with this error?
  42. return err
  43. }
  44. tty = (s.Spec.TaskTemplate.ContainerSpec != nil && s.Spec.TaskTemplate.ContainerSpec.TTY) || tty
  45. }
  46. for _, task := range selector.Tasks {
  47. t, err := sr.backend.GetTask(task)
  48. if err != nil {
  49. // as above
  50. return err
  51. }
  52. tty = t.Spec.ContainerSpec.TTY || tty
  53. }
  54. msgs, err := sr.backend.ServiceLogs(ctx, selector, logsConfig)
  55. if err != nil {
  56. return err
  57. }
  58. contentType := basictypes.MediaTypeRawStream
  59. if !tty && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") {
  60. contentType = basictypes.MediaTypeMultiplexedStream
  61. }
  62. w.Header().Set("Content-Type", contentType)
  63. httputils.WriteLogStream(ctx, w, msgs, logsConfig, !tty)
  64. return nil
  65. }
  66. // adjustForAPIVersion takes a version and service spec and removes fields to
  67. // make the spec compatible with the specified version.
  68. func adjustForAPIVersion(cliVersion string, service *swarm.ServiceSpec) {
  69. if cliVersion == "" {
  70. return
  71. }
  72. if versions.LessThan(cliVersion, "1.40") {
  73. if service.TaskTemplate.ContainerSpec != nil {
  74. // Sysctls for docker swarm services weren't supported before
  75. // API version 1.40
  76. service.TaskTemplate.ContainerSpec.Sysctls = nil
  77. if service.TaskTemplate.ContainerSpec.Privileges != nil && service.TaskTemplate.ContainerSpec.Privileges.CredentialSpec != nil {
  78. // Support for setting credential-spec through configs was added in API 1.40
  79. service.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config = ""
  80. }
  81. for _, config := range service.TaskTemplate.ContainerSpec.Configs {
  82. // support for the Runtime target was added in API 1.40
  83. config.Runtime = nil
  84. }
  85. }
  86. if service.TaskTemplate.Placement != nil {
  87. // MaxReplicas for docker swarm services weren't supported before
  88. // API version 1.40
  89. service.TaskTemplate.Placement.MaxReplicas = 0
  90. }
  91. }
  92. if versions.LessThan(cliVersion, "1.41") {
  93. if service.TaskTemplate.ContainerSpec != nil {
  94. // Capabilities and Ulimits for docker swarm services weren't
  95. // supported before API version 1.41
  96. service.TaskTemplate.ContainerSpec.CapabilityAdd = nil
  97. service.TaskTemplate.ContainerSpec.CapabilityDrop = nil
  98. service.TaskTemplate.ContainerSpec.Ulimits = nil
  99. }
  100. if service.TaskTemplate.Resources != nil && service.TaskTemplate.Resources.Limits != nil {
  101. // Limits.Pids not supported before API version 1.41
  102. service.TaskTemplate.Resources.Limits.Pids = 0
  103. }
  104. // jobs were only introduced in API version 1.41. Nil out both Job
  105. // modes; if the service is one of these modes and subsequently has no
  106. // mode, then something down the pipe will thrown an error.
  107. service.Mode.ReplicatedJob = nil
  108. service.Mode.GlobalJob = nil
  109. }
  110. if versions.LessThan(cliVersion, "1.44") {
  111. // seccomp, apparmor, and no_new_privs were added in 1.44.
  112. if service.TaskTemplate.ContainerSpec != nil && service.TaskTemplate.ContainerSpec.Privileges != nil {
  113. service.TaskTemplate.ContainerSpec.Privileges.Seccomp = nil
  114. service.TaskTemplate.ContainerSpec.Privileges.AppArmor = nil
  115. service.TaskTemplate.ContainerSpec.Privileges.NoNewPrivileges = false
  116. }
  117. }
  118. }