backend.go 5.7 KB

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