clair/cmd/clairctl/docker/dockercli/dockercli.go
2016-09-28 15:24:38 +02:00

191 lines
4.6 KiB
Go

package dockercli
import (
"bufio"
"compress/bzip2"
"compress/gzip"
"encoding/json"
"io"
"os"
"strings"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/artyom/untar"
"github.com/coreos/clair/cmd/clairctl/config"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/docker/reference"
dockerclient "github.com/fsouza/go-dockerclient"
)
//GetLocalManifest retrieve manifest for local image
func GetLocalManifest(imageName string, withExport bool) (reference.Named, schema1.SignedManifest, error) {
image, err := reference.ParseNamed(imageName)
if err != nil {
return nil, schema1.SignedManifest{}, err
}
var manifest schema1.SignedManifest
if withExport {
manifest, err = save(image.Name())
} else {
manifest, err = historyFromCommand(image.Name())
}
if err != nil {
return nil, schema1.SignedManifest{}, err
}
manifest.Name = image.Name()
if strings.Contains(image.String(), ":") {
manifest.Tag = strings.SplitAfter(image.String(), ":")[1]
}
return image, manifest, err
}
func save(imageName string) (schema1.SignedManifest, error) {
path := config.TmpLocal() + "/" + strings.Split(imageName, ":")[0] + "/blobs"
if _, err := os.Stat(path); os.IsExist(err) {
err := os.RemoveAll(path)
if err != nil {
return schema1.SignedManifest{}, err
}
}
err := os.MkdirAll(path, 0755)
if err != nil {
return schema1.SignedManifest{}, err
}
logrus.Debugln("docker image to save: ", imageName)
logrus.Debugln("saving in: ", path)
// open output file
fo, err := os.Create(path + "/output.tar")
// close fo on exit and check for its returned error
defer func() {
if err := fo.Close(); err != nil {
panic(err)
}
}()
if err != nil {
return schema1.SignedManifest{}, err
}
// make a write buffer
w := bufio.NewWriter(fo)
client, err := dockerclient.NewClientFromEnv()
if err != nil {
return schema1.SignedManifest{}, err
}
err = client.ExportImage(dockerclient.ExportImageOptions{Name: imageName, OutputStream: w})
if err != nil {
return schema1.SignedManifest{}, err
}
err = openAndUntar(path+"/output.tar", path)
if err != nil {
return schema1.SignedManifest{}, err
}
err = os.Remove(path + "/output.tar")
if err != nil {
return schema1.SignedManifest{}, err
}
return historyFromManifest(path)
}
func historyFromManifest(path string) (schema1.SignedManifest, error) {
mf, err := os.Open(path + "/manifest.json")
defer mf.Close()
if err != nil {
return schema1.SignedManifest{}, err
}
// https://github.com/docker/docker/blob/master/image/tarexport/tarexport.go#L17
type manifestItem struct {
Config string
RepoTags []string
Layers []string
}
var manifest []manifestItem
if err = json.NewDecoder(mf).Decode(&manifest); err != nil {
return schema1.SignedManifest{}, err
} else if len(manifest) != 1 {
return schema1.SignedManifest{}, err
}
var layers []string
for _, layer := range manifest[0].Layers {
layers = append(layers, strings.TrimSuffix(layer, "/layer.tar"))
}
var m schema1.SignedManifest
for _, layer := range manifest[0].Layers {
var d digest.Digest
d, err := digest.ParseDigest("sha256:" + strings.TrimSuffix(layer, "/layer.tar"))
if err != nil {
return schema1.SignedManifest{}, err
}
m.FSLayers = append(m.FSLayers, schema1.FSLayer{BlobSum: d})
}
return m, nil
}
func historyFromCommand(imageName string) (schema1.SignedManifest, error) {
client, err := dockerclient.NewClientFromEnv()
if err != nil {
return schema1.SignedManifest{}, err
}
histories, err := client.ImageHistory(imageName)
if err != nil {
return schema1.SignedManifest{}, err
}
manifest := schema1.SignedManifest{}
for _, history := range histories {
var d digest.Digest
d, err := digest.ParseDigest(history.ID)
if err != nil {
return schema1.SignedManifest{}, err
}
manifest.FSLayers = append(manifest.FSLayers, schema1.FSLayer{BlobSum: d})
}
return manifest, nil
}
func openAndUntar(name, dst string) error {
var rd io.Reader
f, err := os.Open(name)
defer f.Close()
if err != nil {
return err
}
rd = f
if strings.HasSuffix(name, ".gz") || strings.HasSuffix(name, ".tgz") {
gr, err := gzip.NewReader(f)
if err != nil {
return err
}
defer gr.Close()
rd = gr
} else if strings.HasSuffix(name, ".bz2") {
rd = bzip2.NewReader(f)
}
if err := os.MkdirAll(dst, os.ModeDir|os.ModePerm); err != nil {
return err
}
// resetting umask is essential to have exact permissions on unpacked
// files; it's not not put inside untar function as it changes
// process-wide umask
mask := syscall.Umask(0)
defer syscall.Umask(mask)
return untar.Untar(rd, dst)
}