backend.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. package local
  2. import (
  3. "bufio"
  4. "context"
  5. "io"
  6. "strconv"
  7. "time"
  8. "github.com/docker/docker/pkg/stringid"
  9. "github.com/docker/go-connections/nat"
  10. "github.com/docker/docker/api/types"
  11. "github.com/docker/docker/api/types/container"
  12. "github.com/docker/docker/client"
  13. "github.com/docker/docker/pkg/stdcopy"
  14. "github.com/pkg/errors"
  15. "github.com/docker/api/backend"
  16. "github.com/docker/api/compose"
  17. "github.com/docker/api/containers"
  18. "github.com/docker/api/context/cloud"
  19. "github.com/docker/api/errdefs"
  20. )
  21. type local struct {
  22. apiClient *client.Client
  23. }
  24. func init() {
  25. backend.Register("local", "local", service, cloud.NotImplementedCloudService)
  26. }
  27. func service(ctx context.Context) (backend.Service, error) {
  28. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  29. if err != nil {
  30. return nil, err
  31. }
  32. return &local{
  33. apiClient,
  34. }, nil
  35. }
  36. func (ms *local) ContainerService() containers.Service {
  37. return ms
  38. }
  39. func (ms *local) ComposeService() compose.Service {
  40. return nil
  41. }
  42. func (ms *local) List(ctx context.Context, all bool) ([]containers.Container, error) {
  43. css, err := ms.apiClient.ContainerList(ctx, types.ContainerListOptions{
  44. All: all,
  45. })
  46. if err != nil {
  47. return []containers.Container{}, err
  48. }
  49. var result []containers.Container
  50. for _, container := range css {
  51. result = append(result, containers.Container{
  52. ID: stringid.TruncateID(container.ID),
  53. Image: container.Image,
  54. // TODO: `Status` is a human readable string ("Up 24 minutes"),
  55. // we need to return the `State` instead but first we need to
  56. // define an enum on the proto side with all the possible container
  57. // statuses. We also need to add a `Created` property on the gRPC side.
  58. Status: container.Status,
  59. Command: container.Command,
  60. Ports: toPorts(container.Ports),
  61. })
  62. }
  63. return result, nil
  64. }
  65. func (ms *local) Run(ctx context.Context, r containers.ContainerConfig) error {
  66. exposedPorts, hostBindings, err := fromPorts(r.Ports)
  67. if err != nil {
  68. return err
  69. }
  70. containerConfig := &container.Config{
  71. Image: r.Image,
  72. Labels: r.Labels,
  73. ExposedPorts: exposedPorts,
  74. }
  75. hostConfig := &container.HostConfig{
  76. PortBindings: hostBindings,
  77. }
  78. created, err := ms.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, nil, r.ID)
  79. if err != nil {
  80. if client.IsErrNotFound(err) {
  81. io, err := ms.apiClient.ImagePull(ctx, r.Image, types.ImagePullOptions{})
  82. if err != nil {
  83. return err
  84. }
  85. scanner := bufio.NewScanner(io)
  86. // Read the whole body, otherwise the pulling stops
  87. for scanner.Scan() {
  88. }
  89. if err = scanner.Err(); err != nil {
  90. return err
  91. }
  92. if err = io.Close(); err != nil {
  93. return err
  94. }
  95. created, err = ms.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, nil, r.ID)
  96. if err != nil {
  97. return err
  98. }
  99. } else {
  100. return err
  101. }
  102. }
  103. return ms.apiClient.ContainerStart(ctx, created.ID, types.ContainerStartOptions{})
  104. }
  105. func (ms *local) Stop(ctx context.Context, containerID string, timeout *uint32) error {
  106. var t *time.Duration
  107. if timeout != nil {
  108. timeoutValue := time.Duration(*timeout) * time.Second
  109. t = &timeoutValue
  110. }
  111. return ms.apiClient.ContainerStop(ctx, containerID, t)
  112. }
  113. func (ms *local) Exec(ctx context.Context, name string, command string, reader io.Reader, writer io.Writer) error {
  114. cec, err := ms.apiClient.ContainerExecCreate(ctx, name, types.ExecConfig{
  115. Cmd: []string{command},
  116. Tty: true,
  117. AttachStdin: true,
  118. AttachStdout: true,
  119. AttachStderr: true,
  120. })
  121. if err != nil {
  122. return err
  123. }
  124. resp, err := ms.apiClient.ContainerExecAttach(ctx, cec.ID, types.ExecStartCheck{
  125. Tty: true,
  126. })
  127. if err != nil {
  128. return err
  129. }
  130. defer resp.Close()
  131. readChannel := make(chan error, 10)
  132. writeChannel := make(chan error, 10)
  133. go func() {
  134. _, err := io.Copy(writer, resp.Reader)
  135. readChannel <- err
  136. }()
  137. go func() {
  138. _, err := io.Copy(resp.Conn, reader)
  139. writeChannel <- err
  140. }()
  141. for {
  142. select {
  143. case err := <-readChannel:
  144. return err
  145. case err := <-writeChannel:
  146. return err
  147. }
  148. }
  149. }
  150. func (ms *local) Logs(ctx context.Context, containerName string, request containers.LogsRequest) error {
  151. c, err := ms.apiClient.ContainerInspect(ctx, containerName)
  152. if err != nil {
  153. return err
  154. }
  155. r, err := ms.apiClient.ContainerLogs(ctx, containerName, types.ContainerLogsOptions{
  156. ShowStdout: true,
  157. ShowStderr: true,
  158. Follow: request.Follow,
  159. })
  160. if err != nil {
  161. return err
  162. }
  163. // nolint errcheck
  164. defer r.Close()
  165. if c.Config.Tty {
  166. _, err = io.Copy(request.Writer, r)
  167. } else {
  168. _, err = stdcopy.StdCopy(request.Writer, request.Writer, r)
  169. }
  170. return err
  171. }
  172. func (ms *local) Delete(ctx context.Context, containerID string, force bool) error {
  173. err := ms.apiClient.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{
  174. Force: force,
  175. })
  176. if client.IsErrNotFound(err) {
  177. return errors.Wrapf(errdefs.ErrNotFound, "container %q", containerID)
  178. }
  179. return err
  180. }
  181. func toPorts(ports []types.Port) []containers.Port {
  182. result := []containers.Port{}
  183. for _, port := range ports {
  184. result = append(result, containers.Port{
  185. ContainerPort: uint32(port.PrivatePort),
  186. HostPort: uint32(port.PublicPort),
  187. HostIP: port.IP,
  188. Protocol: port.Type,
  189. })
  190. }
  191. return result
  192. }
  193. func fromPorts(ports []containers.Port) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding, error) {
  194. var (
  195. exposedPorts = make(map[nat.Port]struct{}, len(ports))
  196. bindings = make(map[nat.Port][]nat.PortBinding)
  197. )
  198. for _, port := range ports {
  199. p, err := nat.NewPort(port.Protocol, strconv.Itoa(int(port.ContainerPort)))
  200. if err != nil {
  201. return nil, nil, err
  202. }
  203. if _, exists := exposedPorts[p]; !exists {
  204. exposedPorts[p] = struct{}{}
  205. }
  206. portBinding := nat.PortBinding{
  207. HostIP: port.HostIP,
  208. HostPort: strconv.Itoa(int(port.HostPort)),
  209. }
  210. bslice, exists := bindings[p]
  211. if !exists {
  212. bslice = []nat.PortBinding{}
  213. }
  214. bindings[p] = append(bslice, portBinding)
  215. }
  216. return exposedPorts, bindings, nil
  217. }