154 lines
4.5 KiB
Go
154 lines
4.5 KiB
Go
package docker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
l "hulthe.net/lookbuilding/internal/pkg/logging"
|
|
"hulthe.net/lookbuilding/internal/pkg/registry"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/network"
|
|
"github.com/docker/docker/client"
|
|
d "github.com/opencontainers/go-digest"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
func GetLabeledContainers(cli *client.Client) ([]LabeledContainer, error) {
|
|
out := make([]LabeledContainer, 0)
|
|
|
|
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
l.Logger.Infof("scanning running container labels")
|
|
for _, container := range containers {
|
|
lc := LabeledContainer{Container: container}
|
|
|
|
if container.Image == container.ImageID {
|
|
l.Logger.Errorf("ignoring container %s which has an untagged image", lc.GetName())
|
|
continue
|
|
}
|
|
|
|
l.Logger.Debugf("checking %s %s", lc.GetName(), container.Image)
|
|
for k, v := range container.Labels {
|
|
l.Logger.Debugf(` - "%s": "%s"`, k, v)
|
|
|
|
if k == VersioningModeLabel {
|
|
mode := ParseMode(v)
|
|
if mode == nil {
|
|
l.Logger.Errorf(`failed to parse "%s" as a versioning mode`, v)
|
|
continue
|
|
}
|
|
lc.Mode = *mode
|
|
|
|
inspect, _, err := cli.ImageInspectWithRaw(context.Background(), container.ImageID)
|
|
if err != nil {
|
|
errors.Wrapf(err, "failed to inspect container %s", lc.GetName())
|
|
}
|
|
|
|
if len(inspect.RepoDigests) >= 2 {
|
|
// TODO: find out if having more than one digest could break same_tag version mode
|
|
l.Logger.Warnf("unexpected: container %s had more than one RepoDigest", lc.GetName())
|
|
} else if len(inspect.RepoDigests) == 0 {
|
|
return nil, errors.Errorf("unexpected: container %s has no RepoDigests", lc.GetName())
|
|
}
|
|
|
|
imageDigest := inspect.RepoDigests[0]
|
|
atIndex := strings.Index(imageDigest, "@")
|
|
lc.ImageDigest, err = d.Parse(imageDigest[atIndex+1:])
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse image digest of running container %s", lc.GetName())
|
|
}
|
|
|
|
out = append(out, lc)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func (lc LabeledContainer) UpdateTo(cli *client.Client, tag registry.Tag) error {
|
|
ctx := context.Background()
|
|
|
|
owner, repository, _ := lc.SplitImageParts()
|
|
image := CombineImageParts(owner, repository, &tag.Name)
|
|
canonicalImage := fmt.Sprintf("docker.io/%s", image)
|
|
l.Logger.Infof(`pulling image "%s"`, canonicalImage)
|
|
|
|
imageReader, err := cli.ImagePull(ctx, canonicalImage, types.ImagePullOptions{})
|
|
if err != nil {
|
|
return errors.Wrapf(err, `failed to pull image "%s"`, canonicalImage)
|
|
}
|
|
|
|
loadResponse, err := cli.ImageLoad(ctx, imageReader, false)
|
|
if err != nil {
|
|
return errors.Wrapf(err, `failed to load pulled image "%s"`, canonicalImage)
|
|
}
|
|
|
|
err = loadResponse.Body.Close()
|
|
if err != nil {
|
|
return errors.Wrapf(err, `failed to close rpc response when loading image "%s"`, canonicalImage)
|
|
}
|
|
err = imageReader.Close()
|
|
if err != nil {
|
|
return errors.Wrapf(err, `failed to close rpc response when pulling image "%s"`, canonicalImage)
|
|
}
|
|
|
|
l.Logger.Infof("stopping container %s", lc.GetName())
|
|
err = cli.ContainerStop(ctx, lc.Container.ID, nil)
|
|
if err != nil {
|
|
return errors.Wrapf(err, `failed to stop container "%s"`, lc.GetName())
|
|
}
|
|
|
|
oldContainer, err := cli.ContainerInspect(ctx, lc.Container.ID)
|
|
if err != nil {
|
|
return errors.Wrapf(err, `failed to inspect container "%s"`, lc.GetName())
|
|
}
|
|
|
|
oldTmpName := fmt.Sprintf("%s.lb.old", lc.GetName())
|
|
|
|
config := *oldContainer.Config
|
|
config.Image = image
|
|
|
|
hostConfig := *oldContainer.HostConfig
|
|
hostConfig.VolumesFrom = []string{oldTmpName}
|
|
|
|
l.Logger.Infof(`renaming container %s to %s`, lc.GetName(), oldTmpName)
|
|
err = cli.ContainerRename(ctx, lc.Container.ID, oldTmpName)
|
|
if err != nil {
|
|
return errors.Wrapf(err, `failed to rename container "%s" to "%s"`, lc.GetName(), oldTmpName)
|
|
}
|
|
|
|
l.Logger.Infof("creating new container %s", lc.GetName())
|
|
new, err := cli.ContainerCreate(ctx, &config, &hostConfig, &network.NetworkingConfig{
|
|
EndpointsConfig: oldContainer.NetworkSettings.Networks,
|
|
}, lc.GetName())
|
|
|
|
if err != nil {
|
|
return errors.Wrapf(err, `failed to create container for new version of %s`, image)
|
|
}
|
|
|
|
l.Logger.Infof("starting new container %s with id %s", lc.GetName(), new.ID)
|
|
err = cli.ContainerStart(ctx, new.ID, types.ContainerStartOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
l.Logger.Infof("removing old container %s", oldTmpName)
|
|
err = cli.ContainerRemove(ctx, oldContainer.ID, types.ContainerRemoveOptions{
|
|
RemoveVolumes: false,
|
|
RemoveLinks: false,
|
|
Force: false,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|