backend.go 5.9 KB

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