func TestBuild_LookupImage_ExistLocally(t *testing.T) { var ( nilImage *docker.Image b, c = makeBuild(t, "", Config{}) resultImage = &docker.Image{ID: "789"} name = "ubuntu:latest" localImages = []*imagename.ImageName{ imagename.NewFromString("debian:7.7"), imagename.NewFromString("debian:latest"), imagename.NewFromString("ubuntu:12.04"), imagename.NewFromString("ubuntu:14.04"), imagename.NewFromString("ubuntu:latest"), } ) c.On("InspectImage", name).Return(nilImage, nil).Once() c.On("ListImages").Return(localImages, nil).Once() c.On("InspectImage", name).Return(resultImage, nil).Once() result, err := b.lookupImage(name) if err != nil { t.Fatal(err) } assert.Equal(t, resultImage, result) c.AssertExpectations(t) }
// PullImage pulls docker image func (c *DockerClient) PullImage(name string) error { var ( image = imagename.NewFromString(name) pipeReader, pipeWriter = io.Pipe() fdOut, isTerminalOut = term.GetFdInfo(c.log.Out) out = c.log.Out errch = make(chan error, 1) ) if !isTerminalOut { out = c.log.Writer() } opts := docker.PullImageOptions{ Repository: image.NameWithRegistry(), Registry: image.Registry, Tag: image.GetTag(), OutputStream: pipeWriter, RawJSONStream: true, } c.log.Infof("| Pull image %s", image) c.log.Debugf("Pull image %s with options: %# v", image, opts) go func() { errch <- jsonmessage.DisplayJSONMessagesStream(pipeReader, out, fdOut, isTerminalOut) }() if err := c.client.PullImage(opts, c.auth); err != nil { return err } return <-errch }
// Execute runs the command func (c *CommandPush) Execute(b *Build) (State, error) { if len(c.cfg.args) != 1 { return b.state, fmt.Errorf("PUSH requires exactly one argument") } if b.state.ImageID == "" { return b.state, fmt.Errorf("Cannot PUSH empty image") } if err := b.client.TagImage(b.state.ImageID, c.cfg.args[0]); err != nil { return b.state, err } image := imagename.NewFromString(c.cfg.args[0]) artifact := imagename.Artifact{ Name: image, Pushed: b.cfg.Push, Tag: image.GetTag(), ImageID: b.state.ImageID, BuildTime: time.Now(), } // push image and add some lines to artifacts if b.cfg.Push { digest, err := b.client.PushImage(image.String()) if err != nil { return b.state, err } artifact.Digest = digest artifact.Addressable = fmt.Sprintf("%s@%s", image.NameWithRegistry(), digest) } else { log.Infof("| Don't push. Pass --push flag to actually push to the registry") } // Publish artifact files if b.cfg.ArtifactsPath != "" { if err := os.MkdirAll(b.cfg.ArtifactsPath, 0755); err != nil { return b.state, fmt.Errorf("Failed to create directory %s for the artifacts, error: %s", b.cfg.ArtifactsPath, err) } filePath := filepath.Join(b.cfg.ArtifactsPath, artifact.GetFileName()) artifacts := imagename.Artifacts{ []imagename.Artifact{artifact}, } content, err := yaml.Marshal(artifacts) if err != nil { return b.state, err } if err := ioutil.WriteFile(filePath, content, 0644); err != nil { return b.state, fmt.Errorf("Failed to write artifact file %s, error: %s", filePath, err) } log.Infof("| Saved artifact file %s", filePath) log.Debugf("Artifact properties: %# v", pretty.Formatter(artifact)) } return b.state, nil }
func TestBuild_LookupImage_PullAndNotExist(t *testing.T) { var ( b, c = makeBuild(t, "", Config{Pull: true}) name = "ubuntu:latest" remoteImages = []*imagename.ImageName{ imagename.NewFromString("debian:7.7"), imagename.NewFromString("debian:latest"), imagename.NewFromString("ubuntu:12.04"), imagename.NewFromString("ubuntu:14.04"), } ) c.On("ListImageTags", name).Return(remoteImages, nil).Once() _, err := b.lookupImage(name) assert.EqualError(t, err, "Image not found: ubuntu:latest (also checked in the remote registry)") c.AssertExpectations(t) }
// PushImage pushes the image func (c *DockerClient) PushImage(imageName string) (digest string, err error) { var ( img = imagename.NewFromString(imageName) buf bytes.Buffer pipeReader, pipeWriter = io.Pipe() outStream = io.MultiWriter(pipeWriter, &buf) fdOut, isTerminalOut = term.GetFdInfo(c.log.Out) out = c.log.Out opts = docker.PushImageOptions{ Name: img.NameWithRegistry(), Tag: img.GetTag(), Registry: img.Registry, OutputStream: outStream, RawJSONStream: true, } errch = make(chan error, 1) ) if !isTerminalOut { out = c.log.Writer() } c.log.Infof("| Push %s", img) c.log.Debugf("Push with options: %# v", opts) // TODO: DisplayJSONMessagesStream may fail by client.PushImage run without errors go func() { errch <- jsonmessage.DisplayJSONMessagesStream(pipeReader, out, fdOut, isTerminalOut) }() if err := c.client.PushImage(opts, c.auth); err != nil { return "", err } pipeWriter.Close() if err := <-errch; err != nil { return "", fmt.Errorf("Failed to process json stream, error %s", err) } // It is the best way to have pushed image digest so far matches := captureDigest.FindStringSubmatch(buf.String()) if len(matches) > 0 { digest = matches[1] } return digest, nil }
// TagImage adds tag to the image func (c *DockerClient) TagImage(imageID, imageName string) error { img := imagename.NewFromString(imageName) c.log.Infof("| Tag %.12s -> %s", imageID, img) opts := docker.TagImageOptions{ Repo: img.NameWithRegistry(), Tag: img.GetTag(), Force: true, } c.log.Debugf("Tag image %s with options: %# v", imageID, opts) return c.client.TagImage(imageID, opts) }
// ListImages lists all pulled images in the local docker registry func (c *DockerClient) ListImages() (images []*imagename.ImageName, err error) { var dockerImages []docker.APIImages if dockerImages, err = c.client.ListImages(docker.ListImagesOptions{}); err != nil { return } images = []*imagename.ImageName{} for _, image := range dockerImages { for _, repoTag := range image.RepoTags { images = append(images, imagename.NewFromString(repoTag)) } } return }
func (builder *Builder) ensureImage(imageName string, purpose string) error { _, err := builder.Docker.InspectImage(imageName) if err != nil && err.Error() == "no such image" { fmt.Fprintf(builder.OutStream, "[Rocker] Pulling image: %s for %s\n", imageName, purpose) image := imagename.NewFromString(imageName) pipeReader, pipeWriter := io.Pipe() pullOpts := docker.PullImageOptions{ Repository: image.NameWithRegistry(), Registry: image.Registry, Tag: image.GetTag(), OutputStream: pipeWriter, RawJSONStream: true, } errch := make(chan error) go func() { err := builder.Docker.PullImage(pullOpts, *builder.Auth) if err := pipeWriter.Close(); err != nil { fmt.Fprintf(builder.OutStream, "pipeWriter.Close() err: %s\n", err) } errch <- err }() if err := jsonmessage.DisplayJSONMessagesStream(pipeReader, builder.OutStream, builder.fdOut, builder.isTerminalOut); err != nil { return fmt.Errorf("Failed to process json stream for image: %s, error: %s", image, err) } if err := <-errch; err != nil { return fmt.Errorf("Failed to pull image: %s, error: %s", image, err) } } else if err != nil { return err } return nil }
func showCommand(c *cli.Context) { dockerClient, err := dockerclient.NewFromCli(c) if err != nil { log.Fatal(err) } // Initialize context dir args := c.Args() if len(args) == 0 { log.Fatal("Missing image argument") } //parse parameter to name imageName := imagename.NewFromString(args[0]) infos := []*build.RockerImageData{} if imageName.IsStrict() { image, err := dockerClient.InspectImage(args[0]) if err != nil && err.Error() == "no such image" { image, err = imagename.RegistryGet(imageName) if err != nil { log.Fatal(err) } } else if err != nil { log.Fatal(err) } info, err := toInfo(imageName, image) if err != nil { log.Fatal(err) } infos = append(infos, info) } else { images, err := imagename.RegistryListTags(imageName) if err != nil { log.Fatal(err) } type resp struct { name *imagename.ImageName image *docker.Image err error } chResp := make(chan resp, len(images)) for _, img := range images { go func(img *imagename.ImageName) { r := resp{name: img} r.image, r.err = imagename.RegistryGet(img) chResp <- r }(img) } for _ = range images { r := <-chResp if r.err != nil { log.Println(r.err) } else if info, err := toInfo(r.name, r.image); err == nil { infos = append(infos, info) } } } if c.Bool("json") { res, err := json.Marshal(infos) if err != nil { log.Fatal(err) } fmt.Println(string(res)) } else { for _, res := range infos { fmt.Println(res.PrettyString()) } } }
// lookupImage looks up for the image by name and returns *docker.Image object (result of the inspect) // `Pull` config option defines whether we want to update the latest version of the image from the remote registry // See build.Config struct for more details about other build config options. // // If `Pull` is false, it tries to lookup locally by exact matching, e.g. if the image is already // pulled with that exact name given (no fuzzy semver matching) // // Then the function fetches the list of all pulled images and tries to match one of them by the given name. // // If `Pull` is set to true or if it cannot find the image locally, it then fetches all image // tags from the remote registry and finds the best match for the given image name. // // If it cannot find the image either locally or in the remote registry, it returns `nil` // // In case the given image has sha256 tag, it looks for it locally and pulls if it's not found. // No semver matching is done for sha256 tagged images. // // See also TestBuild_LookupImage_* test cases in build_test.go func (b *Build) lookupImage(name string) (img *docker.Image, err error) { var ( candidate, remoteCandidate *imagename.ImageName imgName = imagename.NewFromString(name) pull = false hub = b.cfg.Pull isSha = imgName.TagIsSha() ) // If hub is true, then there is no sense to inspect the local image if !hub || isSha { // Try to inspect image as is, without version resolution if img, err := b.client.InspectImage(name); err != nil || img != nil { return img, err } } if isSha { // If we are still here and image not found locally, we want to pull it candidate = imgName hub = false pull = true } if !isSha && !hub { // List local images var localImages = []*imagename.ImageName{} if localImages, err = b.client.ListImages(); err != nil { return nil, err } // Resolve local candidate candidate = imgName.ResolveVersion(localImages) } // In case we want to include external images as well, pulling list of available // images from the remote registry if hub || candidate == nil { log.Debugf("Getting list of tags for %s from the registry", imgName) var remoteImages []*imagename.ImageName if remoteImages, err = b.client.ListImageTags(imgName.String()); err != nil { err = fmt.Errorf("Failed to list tags of image %s from the remote registry, error: %s", imgName, err) } // Since we found the remote image, we want to pull it if remoteCandidate = imgName.ResolveVersion(remoteImages); remoteCandidate != nil { pull = true candidate = remoteCandidate } } // If not candidate found, it's an error if candidate == nil { err = fmt.Errorf("Image not found: %s (also checked in the remote registry)", imgName) return } if !isSha && imgName.GetTag() != candidate.GetTag() { if remoteCandidate != nil { log.Infof("Resolve %s --> %s (found remotely)", imgName, candidate.GetTag()) } else { log.Infof("Resolve %s --> %s", imgName, candidate.GetTag()) } } if pull { if err = b.client.PullImage(candidate.String()); err != nil { return } } return b.client.InspectImage(candidate.String()) }
"strings" "testing" "github.com/stretchr/testify/assert" ) var ( configTemplateVars = Vars{ "mykey": "myval", "n": "5", "data": map[string]string{ "foo": "bar", }, "RockerArtifacts": []imagename.Artifact{ imagename.Artifact{ Name: imagename.NewFromString("alpine:3.2"), Tag: "3.2", }, imagename.Artifact{ Name: imagename.NewFromString("golang:1.5"), Tag: "1.5", Digest: "sha256:ead434", }, imagename.Artifact{ Name: imagename.NewFromString("data:master"), Tag: "master", Digest: "sha256:fafe14", }, imagename.Artifact{ Name: imagename.NewFromString("ssh:latest"), Tag: "latest",
// cmdTag implements TAG command // TODO: document behavior of cmdTag func (builder *Builder) cmdTag(args []string, attributes map[string]bool, flags map[string]string, original string) (err error) { builder.recentTags = []*imagename.ImageName{} if len(args) == 0 { return fmt.Errorf("Command is missing value: %s", original) } image := imagename.NewFromString(args[0]) // Save rockerfile to label, sot it can be inspected later if builder.AddMeta && !builder.metaAdded { data := &RockerImageData{ ImageName: image, Rockerfile: builder.RockerfileContent, Vars: builder.CliVars, Properties: template.Vars{}, } if hostname, _ := os.Hostname(); hostname != "" { data.Properties["hostname"] = hostname } if user, _ := user.Current(); user != nil { data.Properties["system_login"] = user.Username data.Properties["system_user"] = user.Name } json, err := json.Marshal(data) if err != nil { return fmt.Errorf("Failed to marshal rocker data, error: %s", err) } builder.addLabels(map[string]string{ "rocker-data": string(json), }) fmt.Fprintf(builder.OutStream, "[Rocker] add rocker-data label\n") if err := builder.commitContainer("", builder.Config.Cmd, "LABEL rocker-data"); err != nil { return err } builder.metaAdded = true } doTag := func(tag string) error { img := &imagename.ImageName{ Registry: image.Registry, Name: image.Name, Tag: tag, } builder.recentTags = append(builder.recentTags, img) fmt.Fprintf(builder.OutStream, "[Rocker] Tag %.12s -> %s\n", builder.imageID, img) err := builder.Docker.TagImage(builder.imageID, docker.TagImageOptions{ Repo: img.NameWithRegistry(), Tag: img.GetTag(), Force: true, }) if err != nil { return fmt.Errorf("Failed to set tag %s to image %s", img, builder.imageID) } return nil } // By default, tag with current branch name if tag is not specified // do not use :latest unless it was set explicitly if !image.HasTag() { if builder.Vars.IsSet("branch") && builder.Vars["branch"].(string) != "" { image.Tag = builder.Vars["branch"].(string) } // Additionally, tag image with current git sha if builder.Vars.IsSet("commit") && builder.Vars["commit"] != "" { if err := doTag(fmt.Sprintf("%.7s", builder.Vars["commit"])); err != nil { return err } } } // Do the asked tag if err := doTag(image.GetTag()); err != nil { return err } // Optionally make a semver aliases if _, ok := flags["semver"]; ok && image.HasTag() { ver, err := NewSemver(image.GetTag()) if err != nil { return fmt.Errorf("--semver flag expects tag to be in semver format, error: %s", err) } // If the version is like 1.2.3-build512 we also want to alias 1.2.3 if ver.HasSuffix() { if err := doTag(fmt.Sprintf("%d.%d.%d", ver.Major, ver.Minor, ver.Patch)); err != nil { return err } } if err := doTag(fmt.Sprintf("%d.%d.x", ver.Major, ver.Minor)); err != nil { return err } if err := doTag(fmt.Sprintf("%d.x", ver.Major)); err != nil { return err } } return nil }
// ListImageTags returns the list of images instances obtained from all tags existing in the registry func (c *DockerClient) ListImageTags(name string) (images []*imagename.ImageName, err error) { return imagename.RegistryListTags(imagename.NewFromString(name)) }
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 } }