191 lines
4.6 KiB
Go
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)
|
|
}
|