run.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. /*
  2. Copyright 2020 Docker Compose CLI authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package compose
  14. import (
  15. "context"
  16. "fmt"
  17. "io"
  18. "github.com/docker/compose-cli/pkg/api"
  19. "github.com/compose-spec/compose-go/types"
  20. "github.com/docker/cli/cli/streams"
  21. moby "github.com/docker/docker/api/types"
  22. "github.com/docker/docker/pkg/ioutils"
  23. "github.com/docker/docker/pkg/stdcopy"
  24. "github.com/docker/docker/pkg/stringid"
  25. "github.com/moby/term"
  26. )
  27. func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) {
  28. observedState, err := s.getContainers(ctx, project.Name, oneOffInclude, true)
  29. if err != nil {
  30. return 0, err
  31. }
  32. containerID, err := s.prepareRun(ctx, project, observedState, opts)
  33. if err != nil {
  34. return 0, err
  35. }
  36. if opts.Detach {
  37. err := s.apiClient.ContainerStart(ctx, containerID, moby.ContainerStartOptions{})
  38. if err != nil {
  39. return 0, err
  40. }
  41. fmt.Fprintln(opts.Stdout, containerID)
  42. return 0, nil
  43. }
  44. return s.runInteractive(ctx, containerID, opts)
  45. }
  46. func (s *composeService) runInteractive(ctx context.Context, containerID string, opts api.RunOptions) (int, error) {
  47. r, err := s.getEscapeKeyProxy(opts.Stdin)
  48. if err != nil {
  49. return 0, err
  50. }
  51. stdin, stdout, err := s.getContainerStreams(ctx, containerID)
  52. if err != nil {
  53. return 0, err
  54. }
  55. in := streams.NewIn(opts.Stdin)
  56. if in.IsTerminal() {
  57. state, err := term.SetRawTerminal(in.FD())
  58. if err != nil {
  59. return 0, err
  60. }
  61. defer term.RestoreTerminal(in.FD(), state) //nolint:errcheck
  62. }
  63. outputDone := make(chan error)
  64. inputDone := make(chan error)
  65. go func() {
  66. if opts.Tty {
  67. _, err := io.Copy(opts.Stdout, stdout) //nolint:errcheck
  68. outputDone <- err
  69. } else {
  70. _, err := stdcopy.StdCopy(opts.Stdout, opts.Stderr, stdout) //nolint:errcheck
  71. outputDone <- err
  72. }
  73. stdout.Close() //nolint:errcheck
  74. }()
  75. go func() {
  76. _, err := io.Copy(stdin, r)
  77. inputDone <- err
  78. stdin.Close() //nolint:errcheck
  79. }()
  80. err = s.apiClient.ContainerStart(ctx, containerID, moby.ContainerStartOptions{})
  81. if err != nil {
  82. return 0, err
  83. }
  84. s.monitorTTySize(ctx, containerID, s.apiClient.ContainerResize)
  85. for {
  86. select {
  87. case err := <-outputDone:
  88. if err != nil {
  89. return 0, err
  90. }
  91. inspect, err := s.apiClient.ContainerInspect(ctx, containerID)
  92. if err != nil {
  93. return 0, err
  94. }
  95. exitCode := 0
  96. if inspect.State != nil {
  97. exitCode = inspect.State.ExitCode
  98. }
  99. return exitCode, nil
  100. case err := <-inputDone:
  101. if _, ok := err.(term.EscapeError); ok {
  102. return 0, nil
  103. }
  104. if err != nil {
  105. return 0, err
  106. }
  107. // Wait for output to complete streaming
  108. case <-ctx.Done():
  109. return 0, ctx.Err()
  110. }
  111. }
  112. }
  113. func (s *composeService) prepareRun(ctx context.Context, project *types.Project, observedState Containers, opts api.RunOptions) (string, error) {
  114. service, err := project.GetService(opts.Service)
  115. if err != nil {
  116. return "", err
  117. }
  118. applyRunOptions(project, &service, opts)
  119. slug := stringid.GenerateRandomID()
  120. if service.ContainerName == "" {
  121. service.ContainerName = fmt.Sprintf("%s_%s_run_%s", project.Name, service.Name, stringid.TruncateID(slug))
  122. }
  123. service.Scale = 1
  124. service.StdinOpen = true
  125. service.Restart = ""
  126. if service.Deploy != nil {
  127. service.Deploy.RestartPolicy = nil
  128. }
  129. service.Labels = service.Labels.Add(api.SlugLabel, slug)
  130. service.Labels = service.Labels.Add(api.OneoffLabel, "True")
  131. if err := s.ensureImagesExists(ctx, project, observedState, false); err != nil { // all dependencies already checked, but might miss service img
  132. return "", err
  133. }
  134. if err := s.waitDependencies(ctx, project, service); err != nil {
  135. return "", err
  136. }
  137. created, err := s.createContainer(ctx, project, service, service.ContainerName, 1, opts.AutoRemove, opts.UseNetworkAliases)
  138. if err != nil {
  139. return "", err
  140. }
  141. containerID := created.ID
  142. return containerID, nil
  143. }
  144. func (s *composeService) getEscapeKeyProxy(r io.ReadCloser) (io.ReadCloser, error) {
  145. var escapeKeys = []byte{16, 17}
  146. if s.configFile.DetachKeys != "" {
  147. customEscapeKeys, err := term.ToBytes(s.configFile.DetachKeys)
  148. if err != nil {
  149. return nil, err
  150. }
  151. escapeKeys = customEscapeKeys
  152. }
  153. return ioutils.NewReadCloserWrapper(term.NewEscapeProxy(r, escapeKeys), r.Close), nil
  154. }
  155. func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts api.RunOptions) {
  156. service.Tty = opts.Tty
  157. service.ContainerName = opts.Name
  158. if len(opts.Command) > 0 {
  159. service.Command = opts.Command
  160. }
  161. if len(opts.User) > 0 {
  162. service.User = opts.User
  163. }
  164. if len(opts.WorkingDir) > 0 {
  165. service.WorkingDir = opts.WorkingDir
  166. }
  167. if len(opts.Entrypoint) > 0 {
  168. service.Entrypoint = opts.Entrypoint
  169. }
  170. if len(opts.Environment) > 0 {
  171. env := types.NewMappingWithEquals(opts.Environment)
  172. projectEnv := env.Resolve(func(s string) (string, bool) {
  173. v, ok := project.Environment[s]
  174. return v, ok
  175. }).RemoveEmpty()
  176. service.Environment.OverrideBy(projectEnv)
  177. }
  178. for k, v := range opts.Labels {
  179. service.Labels.Add(k, v)
  180. }
  181. }