|
|
@@ -1,32 +1,92 @@
|
|
|
package docker
|
|
|
|
|
|
import (
|
|
|
- "context"
|
|
|
"fmt"
|
|
|
"time"
|
|
|
|
|
|
"github.com/docker/docker/api/types"
|
|
|
"github.com/docker/docker/api/types/container"
|
|
|
- "github.com/docker/docker/client"
|
|
|
+ "github.com/docker/docker/api/types/mount"
|
|
|
"github.com/docker/go-connections/nat"
|
|
|
)
|
|
|
|
|
|
// PorterStartOpts are the options for starting the Porter container
|
|
|
type PorterStartOpts struct {
|
|
|
+ Name string
|
|
|
Image string
|
|
|
StartCmd []string
|
|
|
HostPort uint
|
|
|
ContainerPort uint
|
|
|
+ Mounts []mount.Mount
|
|
|
+ VolumeMap map[string]struct{}
|
|
|
+ Env []string
|
|
|
}
|
|
|
|
|
|
// StartPorterContainerAndWait pulls a specific Porter image and starts a container
|
|
|
// using the Docker engine. It returns when the container has stopped.
|
|
|
func (a *Agent) StartPorterContainerAndWait(opts PorterStartOpts) error {
|
|
|
+ id, err := a.upsertPorterContainer(opts)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ err = a.startPorterContainer(id)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // wait for container to stop before exit
|
|
|
+ statusCh, errCh := a.client.ContainerWait(a.ctx, id, container.WaitConditionNotRunning)
|
|
|
+
|
|
|
+ select {
|
|
|
+ case err := <-errCh:
|
|
|
+ if err != nil {
|
|
|
+ return a.handleDockerClientErr(err, "Error waiting for stopped container")
|
|
|
+ }
|
|
|
+ case <-statusCh:
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// detect if container exists and is running, and stop
|
|
|
+// if spec has changed, remove and recreate container
|
|
|
+// if container does not exist, create the container
|
|
|
+// otherwise, return stopped container
|
|
|
+func (a *Agent) upsertPorterContainer(opts PorterStartOpts) (id string, err error) {
|
|
|
+ containers, err := a.getContainersCreatedByStart()
|
|
|
+
|
|
|
+ // remove the matching container
|
|
|
+ for _, container := range containers {
|
|
|
+ if len(container.Names) > 0 && container.Names[0] == "/"+opts.Name {
|
|
|
+ timeout, _ := time.ParseDuration("15s")
|
|
|
+
|
|
|
+ err := a.client.ContainerStop(a.ctx, container.ID, &timeout)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return "", a.handleDockerClientErr(err, "Could not stop container "+container.ID)
|
|
|
+ }
|
|
|
+
|
|
|
+ err = a.client.ContainerRemove(a.ctx, container.ID, types.ContainerRemoveOptions{})
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return "", a.handleDockerClientErr(err, "Could not remove container "+container.ID)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return a.pullAndCreatePorterContainer(opts)
|
|
|
+}
|
|
|
+
|
|
|
+// create the container and return its id
|
|
|
+func (a *Agent) pullAndCreatePorterContainer(opts PorterStartOpts) (id string, err error) {
|
|
|
// pull the specified image
|
|
|
- _, err := a.client.ImagePull(a.ctx, opts.Image, types.ImagePullOptions{})
|
|
|
+ _, err = a.client.ImagePull(a.ctx, opts.Image, types.ImagePullOptions{})
|
|
|
|
|
|
if err != nil {
|
|
|
- return a.handleDockerClientErr(err, "Could not pull Porter image")
|
|
|
+ return "", a.handleDockerClientErr(err, "Could not pull Porter image")
|
|
|
}
|
|
|
|
|
|
// format the port array for binding to host machine
|
|
|
@@ -35,52 +95,62 @@ func (a *Agent) StartPorterContainerAndWait(opts PorterStartOpts) error {
|
|
|
_, portBindings, err := nat.ParsePortSpecs(ports)
|
|
|
|
|
|
if err != nil {
|
|
|
- return fmt.Errorf("Unable to parse port specification %s", ports)
|
|
|
+ return "", fmt.Errorf("Unable to parse port specification %s", ports)
|
|
|
}
|
|
|
|
|
|
+ labels := make(map[string]string)
|
|
|
+ labels[a.label] = "true"
|
|
|
+
|
|
|
// create the container with a label specifying this was created via the CLI
|
|
|
resp, err := a.client.ContainerCreate(a.ctx, &container.Config{
|
|
|
- Image: opts.Image,
|
|
|
- Cmd: opts.StartCmd,
|
|
|
- Tty: false,
|
|
|
- Labels: map[string]string{
|
|
|
- "CreatedByPorterCLI": "true",
|
|
|
- },
|
|
|
+ Image: opts.Image,
|
|
|
+ Cmd: opts.StartCmd,
|
|
|
+ Tty: false,
|
|
|
+ Labels: labels,
|
|
|
+ Volumes: opts.VolumeMap,
|
|
|
+ Env: opts.Env,
|
|
|
}, &container.HostConfig{
|
|
|
PortBindings: portBindings,
|
|
|
- }, nil, "")
|
|
|
+ Mounts: opts.Mounts,
|
|
|
+ }, nil, opts.Name)
|
|
|
|
|
|
if err != nil {
|
|
|
- return a.handleDockerClientErr(err, "Could not create Porter container")
|
|
|
+ return "", a.handleDockerClientErr(err, "Could not create Porter container")
|
|
|
}
|
|
|
|
|
|
- // start the container and listen until the container is stopped
|
|
|
- if err := a.client.ContainerStart(a.ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
|
|
|
- return a.handleDockerClientErr(err, "Could not start Porter container")
|
|
|
- }
|
|
|
+ return resp.ID, nil
|
|
|
+}
|
|
|
|
|
|
- statusCh, errCh := a.client.ContainerWait(a.ctx, resp.ID, container.WaitConditionNotRunning)
|
|
|
- select {
|
|
|
- case err := <-errCh:
|
|
|
- if err != nil {
|
|
|
- return a.handleDockerClientErr(err, "Error waiting for stopped container")
|
|
|
- }
|
|
|
- case <-statusCh:
|
|
|
+// start the container
|
|
|
+func (a *Agent) startPorterContainer(id string) error {
|
|
|
+ if err := a.client.ContainerStart(a.ctx, id, types.ContainerStartOptions{}); err != nil {
|
|
|
+ return a.handleDockerClientErr(err, "Could not start Porter container")
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+// detect if container exists and is running, and stop
|
|
|
+// if it is running, stop it
|
|
|
+// if it is stopped, return id
|
|
|
+// if it does not exist, create it and return it
|
|
|
+// func (a *Agent) upsertPostgresContainer() error {
|
|
|
+
|
|
|
+// }
|
|
|
+
|
|
|
+// create the container and return it
|
|
|
+// func (a *Agent) createPostgresContainer() error {
|
|
|
+
|
|
|
+// }
|
|
|
+
|
|
|
+// start the container in the background
|
|
|
+// func (a *Agent) startPostgresContainer() error {
|
|
|
+
|
|
|
+// }
|
|
|
+
|
|
|
// StopPorterContainers finds all containers that were started via the CLI and stops them
|
|
|
// without removal.
|
|
|
func (a *Agent) StopPorterContainers() error {
|
|
|
- ctx := context.Background()
|
|
|
- cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|
|
-
|
|
|
- if err != nil {
|
|
|
- panic(err)
|
|
|
- }
|
|
|
-
|
|
|
containers, err := a.getContainersCreatedByStart()
|
|
|
|
|
|
if err != nil {
|
|
|
@@ -89,10 +159,9 @@ func (a *Agent) StopPorterContainers() error {
|
|
|
|
|
|
// remove all Porter containers
|
|
|
for _, container := range containers {
|
|
|
- fmt.Println("removing container", container.ID)
|
|
|
timeout, _ := time.ParseDuration("15s")
|
|
|
|
|
|
- err := cli.ContainerStop(ctx, container.ID, &timeout)
|
|
|
+ err := a.client.ContainerStop(a.ctx, container.ID, &timeout)
|
|
|
|
|
|
if err != nil {
|
|
|
return a.handleDockerClientErr(err, "Could not stop container "+container.ID)
|
|
|
@@ -103,7 +172,7 @@ func (a *Agent) StopPorterContainers() error {
|
|
|
}
|
|
|
|
|
|
// getContainersCreatedByStart gets all containers that were created by the "porter start"
|
|
|
-// command by looking for the label "CreatedByPorterCLI"
|
|
|
+// command by looking for the label "CreatedByPorterCLI" (or .label of the agent)
|
|
|
func (a *Agent) getContainersCreatedByStart() ([]types.Container, error) {
|
|
|
containers, err := a.client.ContainerList(a.ctx, types.ContainerListOptions{
|
|
|
All: true,
|