// NewContainerFromDocker converts a container object given by // docker client to a local Container object func NewContainerFromDocker(dockerContainer *docker.Container) (*Container, error) { cfg, err := config.NewFromDocker(dockerContainer) if err != nil { if _, ok := err.(config.ErrNotRockerCompose); !ok { return nil, err } } return &Container{ ID: dockerContainer.ID, Image: imagename.New(dockerContainer.Config.Image), ImageID: dockerContainer.Image, Name: config.NewContainerNameFromString(dockerContainer.Name), Created: dockerContainer.Created, State: &ContainerState{ Running: dockerContainer.State.Running, Paused: dockerContainer.State.Paused, Restarting: dockerContainer.State.Restarting, OOMKilled: dockerContainer.State.OOMKilled, Pid: dockerContainer.State.Pid, ExitCode: dockerContainer.State.ExitCode, Error: dockerContainer.State.Error, StartedAt: dockerContainer.State.StartedAt, FinishedAt: dockerContainer.State.FinishedAt, }, Config: cfg, container: dockerContainer, }, nil }
// NewContainerFromConfig makes a single Container object from a spec Config object. func NewContainerFromConfig(name *config.ContainerName, containerConfig *config.Container) *Container { container := &Container{ Name: name, State: &ContainerState{ Running: containerConfig.State.Bool(), }, Config: containerConfig, } if containerConfig.Image != nil { container.Image = imagename.New(*containerConfig.Image) } return container }
// GetBridgeIP gets the ip address of docker network bridge // it is useful when you want to loose couple containers and not have tightly link them // container A may publish port 8125 to host network and container B may access this port through // a bridge ip address; it's a hacky solution, any better way to obtain bridge ip without ssh access // to host machine is welcome // // Here we create a dummy container and look at .NetworkSettings.Gateway value // // TODO: maybe we don't need this anymore since docker 1.8 seem to specify all existing containers // in a /etc/hosts file of every contianer. Need to research it further. // // https://github.com/docker/docker/issues/1143 // https://github.com/docker/docker/issues/11247 // func GetBridgeIP(client *docker.Client) (ip string, err error) { // Ensure empty image existing _, err = client.InspectImage(emptyImageName) if err != nil && err.Error() == "no such image" { log.Infof("Pulling image %s to obtain network bridge address", emptyImageName) if err := PullDockerImage(client, imagename.New(emptyImageName), &docker.AuthConfiguration{}); err != nil { return "", err } } else if err != nil { return "", fmt.Errorf("Failed to inspect image %s, error: %s", emptyImageName, err) } container, err := client.CreateContainer(docker.CreateContainerOptions{ Config: &docker.Config{ Image: emptyImageName, Cmd: []string{"/bin/sh", "-c", "while true; do sleep 1; done"}, }, HostConfig: &docker.HostConfig{}, }) if err != nil { return "", fmt.Errorf("Failed to create dummy network container, error: %s", err) } defer func() { removeOpts := docker.RemoveContainerOptions{ ID: container.ID, Force: true, RemoveVolumes: true, } if err2 := client.RemoveContainer(removeOpts); err2 != nil && err == nil { err = err2 } }() if err := client.StartContainer(container.ID, &docker.HostConfig{}); err != nil { return "", fmt.Errorf("Failed to start dummy network container %.12s, error: %s", container.ID, err) } inspect, err := client.InspectContainer(container.ID) if err != nil { return "", fmt.Errorf("Failed to inspect dummy network container %.12s, error: %s", container.ID, err) } return inspect.NetworkSettings.Gateway, nil }
func TestClientClean(t *testing.T) { // This test involves interaction with docker // enable it in case you want to test Clean() functionality t.Skip() dockerCli, err := dockerclient.New() if err != nil { t.Fatal(err) } // Create number of images to test createdContainers := []string{} createdImages := []string{} defer func() { for _, id := range createdContainers { if err := dockerCli.RemoveContainer(docker.RemoveContainerOptions{ID: id, Force: true}); err != nil { t.Error(err) } } for _, id := range createdImages { if err := dockerCli.RemoveImageExtended(id, docker.RemoveImageOptions{Force: true}); err != nil { if err.Error() == "no such image" { continue } t.Error(err) } } }() for i := 1; i <= 5; i++ { c, err := dockerCli.CreateContainer(docker.CreateContainerOptions{ Config: &docker.Config{ Image: "gliderlabs/alpine:3.1", Cmd: []string{"true"}, }, }) if err != nil { t.Fatal(err) } createdContainers = append(createdContainers, c.ID) commitOpts := docker.CommitContainerOptions{ Container: c.ID, Repository: "rocker-compose-test-image-clean", Tag: fmt.Sprintf("%d", i), } img, err := dockerCli.CommitContainer(commitOpts) if err != nil { t.Fatal(err) } createdImages = append(createdImages, img.ID) // Make sure images have different timestamps time.Sleep(time.Second) } //////////////////////// cli, err := NewClient(&DockerClient{Docker: dockerCli, KeepImages: 2}) if err != nil { t.Fatal(err) } yml := ` namespace: test containers: main: image: rocker-compose-test-image-clean:5 ` config, err := config.ReadConfig("test.yml", strings.NewReader(yml), map[string]interface{}{}, map[string]interface{}{}, false) if err != nil { t.Fatal(err) } if err := cli.Clean(config); err != nil { t.Fatal(err) } // test that images left all, err := dockerCli.ListImages(docker.ListImagesOptions{}) if err != nil { t.Fatal(err) } n := 0 for _, image := range all { for _, repoTag := range image.RepoTags { imageName := imagename.New(repoTag) if imageName.Name == "rocker-compose-test-image-clean" { n++ } } } assert.Equal(t, 2, n, "Expected images to be cleaned up") // test removed images list removed := cli.GetRemovedImages() assert.Equal(t, 3, len(removed), "Expected to remove a particular number of images") assert.EqualValues(t, &imagename.ImageName{"", "rocker-compose-test-image-clean", "3"}, removed[0], "removed wrong image") assert.EqualValues(t, &imagename.ImageName{"", "rocker-compose-test-image-clean", "2"}, removed[1], "removed wrong image") assert.EqualValues(t, &imagename.ImageName{"", "rocker-compose-test-image-clean", "1"}, removed[2], "removed wrong image") }
// Clean finds the obsolete image tags from container specs that exist in docker daemon, // skipping topN images that we want to keep (keep_images, default 5) and deletes them. func (client *DockerClient) Clean(config *config.Config) error { // do not pull same image twice images := map[imagename.ImageName]*imagename.Tags{} keep := client.KeepImages // keep 5 latest images by default if keep == 0 { keep = 5 } for _, container := range GetContainersFromConfig(config) { if container.Image == nil { continue } images[*container.Image] = &imagename.Tags{} } if len(images) == 0 { return nil } // Go through every image and list existing tags all, err := client.Docker.ListImages(docker.ListImagesOptions{}) if err != nil { return fmt.Errorf("Failed to list all images, error: %s", err) } // collect tags for every image for _, image := range all { for _, repoTag := range image.RepoTags { imageName := imagename.New(repoTag) for img := range images { if img.IsSameKind(*imageName) { images[img].Items = append(images[img].Items, &imagename.Tag{ Id: image.ID, Name: *imageName, Created: image.Created, }) } } } } // for every image, delete obsolete tags for name, tags := range images { toDelete := tags.GetOld(keep) if len(toDelete) == 0 { continue } log.Infof("Cleanup: removing %d tags of image %s", len(toDelete), name.NameWithRegistry()) for _, n := range toDelete { if name.GetTag() == n.GetTag() { log.Infof("Cleanup: skipping %s because it is in the spec", n) continue } wasRemoved := true log.Infof("Cleanup: remove %s", n) if err := client.Docker.RemoveImageExtended(n.String(), docker.RemoveImageOptions{Force: false}); err != nil { // 409 is conflict, which means there is a container exists running under this image if e, ok := err.(*docker.Error); ok && e.Status == 409 { log.Infof("Cleanup: skip %s because there is an existing container using it", n) wasRemoved = false } else { return err } } // cannot refer to &n because of for loop if wasRemoved { removed := n client.removedImages = append(client.removedImages, &removed) } } } return nil }
func makeImageHelper(vars Vars) func(string, ...string) (string, error) { // Sort artifacts so we match semver on latest item var ( artifacts = &imagename.Artifacts{} ok bool ) if artifacts.RockerArtifacts, ok = vars["RockerArtifacts"].([]imagename.Artifact); !ok { artifacts.RockerArtifacts = []imagename.Artifact{} } sort.Sort(artifacts) log.Debugf("`image` helper got artifacts: %# v", pretty.Formatter(artifacts)) return func(img string, args ...string) (string, error) { var ( matched bool ok bool shouldMatch bool image = imagename.NewFromString(img) ) if len(args) > 0 { image = imagename.New(img, args[0]) } for _, a := range artifacts.RockerArtifacts { if !image.IsSameKind(*a.Name) { continue } if image.HasVersionRange() { if !image.Contains(a.Name) { log.Debugf("Skipping artifact %s because it is not suitable for %s", a.Name, image) continue } } else if image.GetTag() != a.Name.GetTag() { log.Debugf("Skipping artifact %s because it is not suitable for %s", a.Name, image) continue } if a.Digest != "" { log.Infof("Apply artifact digest %s for image %s", a.Digest, image) image.SetTag(a.Digest) matched = true break } if a.Name.HasTag() { log.Infof("Apply artifact tag %s for image %s", a.Name.GetTag(), image) image.SetTag(a.Name.GetTag()) matched = true break } } if shouldMatch, ok = vars["DemandArtifacts"].(bool); ok && shouldMatch && !matched { return "", fmt.Errorf("Cannot find suitable artifact for image %s", image) } return image.String(), nil } }