porter.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. package docker
  2. import (
  3. "fmt"
  4. "time"
  5. "github.com/docker/docker/api/types"
  6. "github.com/docker/docker/api/types/container"
  7. "github.com/docker/docker/api/types/mount"
  8. "github.com/docker/go-connections/nat"
  9. )
  10. // PorterStartOpts are the options for starting the Porter container
  11. type PorterStartOpts struct {
  12. Name string
  13. Image string
  14. StartCmd []string
  15. HostPort uint
  16. ContainerPort uint
  17. Mounts []mount.Mount
  18. VolumeMap map[string]struct{}
  19. Env []string
  20. }
  21. // StartPorterContainerAndWait pulls a specific Porter image and starts a container
  22. // using the Docker engine. It returns when the container has stopped.
  23. func (a *Agent) StartPorterContainerAndWait(opts PorterStartOpts) error {
  24. id, err := a.upsertPorterContainer(opts)
  25. if err != nil {
  26. return err
  27. }
  28. err = a.startPorterContainer(id)
  29. if err != nil {
  30. return err
  31. }
  32. // wait for container to stop before exit
  33. statusCh, errCh := a.client.ContainerWait(a.ctx, id, container.WaitConditionNotRunning)
  34. select {
  35. case err := <-errCh:
  36. if err != nil {
  37. return a.handleDockerClientErr(err, "Error waiting for stopped container")
  38. }
  39. case <-statusCh:
  40. }
  41. return nil
  42. }
  43. // detect if container exists and is running, and stop
  44. // if spec has changed, remove and recreate container
  45. // if container does not exist, create the container
  46. // otherwise, return stopped container
  47. func (a *Agent) upsertPorterContainer(opts PorterStartOpts) (id string, err error) {
  48. containers, err := a.getContainersCreatedByStart()
  49. // remove the matching container
  50. for _, container := range containers {
  51. if len(container.Names) > 0 && container.Names[0] == "/"+opts.Name {
  52. timeout, _ := time.ParseDuration("15s")
  53. err := a.client.ContainerStop(a.ctx, container.ID, &timeout)
  54. if err != nil {
  55. return "", a.handleDockerClientErr(err, "Could not stop container "+container.ID)
  56. }
  57. err = a.client.ContainerRemove(a.ctx, container.ID, types.ContainerRemoveOptions{})
  58. if err != nil {
  59. return "", a.handleDockerClientErr(err, "Could not remove container "+container.ID)
  60. }
  61. }
  62. }
  63. return a.pullAndCreatePorterContainer(opts)
  64. }
  65. // create the container and return its id
  66. func (a *Agent) pullAndCreatePorterContainer(opts PorterStartOpts) (id string, err error) {
  67. // pull the specified image
  68. _, err = a.client.ImagePull(a.ctx, opts.Image, types.ImagePullOptions{})
  69. if err != nil {
  70. return "", a.handleDockerClientErr(err, "Could not pull Porter image")
  71. }
  72. // format the port array for binding to host machine
  73. ports := []string{fmt.Sprintf("127.0.0.1:%d:%d/tcp", opts.HostPort, opts.ContainerPort)}
  74. _, portBindings, err := nat.ParsePortSpecs(ports)
  75. if err != nil {
  76. return "", fmt.Errorf("Unable to parse port specification %s", ports)
  77. }
  78. labels := make(map[string]string)
  79. labels[a.label] = "true"
  80. // create the container with a label specifying this was created via the CLI
  81. resp, err := a.client.ContainerCreate(a.ctx, &container.Config{
  82. Image: opts.Image,
  83. Cmd: opts.StartCmd,
  84. Tty: false,
  85. Labels: labels,
  86. Volumes: opts.VolumeMap,
  87. Env: opts.Env,
  88. }, &container.HostConfig{
  89. PortBindings: portBindings,
  90. Mounts: opts.Mounts,
  91. }, nil, opts.Name)
  92. if err != nil {
  93. return "", a.handleDockerClientErr(err, "Could not create Porter container")
  94. }
  95. return resp.ID, nil
  96. }
  97. // start the container
  98. func (a *Agent) startPorterContainer(id string) error {
  99. if err := a.client.ContainerStart(a.ctx, id, types.ContainerStartOptions{}); err != nil {
  100. return a.handleDockerClientErr(err, "Could not start Porter container")
  101. }
  102. return nil
  103. }
  104. // detect if container exists and is running, and stop
  105. // if it is running, stop it
  106. // if it is stopped, return id
  107. // if it does not exist, create it and return it
  108. // func (a *Agent) upsertPostgresContainer() error {
  109. // }
  110. // create the container and return it
  111. // func (a *Agent) createPostgresContainer() error {
  112. // }
  113. // start the container in the background
  114. // func (a *Agent) startPostgresContainer() error {
  115. // }
  116. // StopPorterContainers finds all containers that were started via the CLI and stops them
  117. // without removal.
  118. func (a *Agent) StopPorterContainers() error {
  119. containers, err := a.getContainersCreatedByStart()
  120. if err != nil {
  121. return err
  122. }
  123. // remove all Porter containers
  124. for _, container := range containers {
  125. timeout, _ := time.ParseDuration("15s")
  126. err := a.client.ContainerStop(a.ctx, container.ID, &timeout)
  127. if err != nil {
  128. return a.handleDockerClientErr(err, "Could not stop container "+container.ID)
  129. }
  130. }
  131. return nil
  132. }
  133. // getContainersCreatedByStart gets all containers that were created by the "porter start"
  134. // command by looking for the label "CreatedByPorterCLI" (or .label of the agent)
  135. func (a *Agent) getContainersCreatedByStart() ([]types.Container, error) {
  136. containers, err := a.client.ContainerList(a.ctx, types.ContainerListOptions{
  137. All: true,
  138. })
  139. if err != nil {
  140. return nil, a.handleDockerClientErr(err, "Could not list containers")
  141. }
  142. res := make([]types.Container, 0)
  143. for _, container := range containers {
  144. if contains, ok := container.Labels[a.label]; ok && contains == "true" {
  145. res = append(res, container)
  146. }
  147. }
  148. return res, nil
  149. }