backend.go 5.9 KB

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