replace pull and push with docker lib
This commit is contained in:
parent
82cdeb2371
commit
7fc769e152
@ -1,110 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/coreos/clair/cmd/clairctl/docker"
|
|
||||||
"github.com/coreos/clair/cmd/clairctl/dockerdist"
|
|
||||||
"github.com/docker/distribution/digest"
|
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
dockercli "github.com/fsouza/go-dockerclient"
|
|
||||||
)
|
|
||||||
|
|
||||||
const pullDockerTplt = `
|
|
||||||
Image: {{.Named.FullName}}
|
|
||||||
{{.V1Manifest.FSLayers | len}} layers found
|
|
||||||
{{range .V1Manifest.FSLayers}} ➜ {{.BlobSum}}
|
|
||||||
{{end}}
|
|
||||||
`
|
|
||||||
|
|
||||||
var pullDockerCmd = &cobra.Command{
|
|
||||||
Use: "pull-docker IMAGE",
|
|
||||||
Short: "Pull Docker image to Clair",
|
|
||||||
Long: `Upload a Docker image to Clair for further analysis`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
|
|
||||||
if len(args) != 1 {
|
|
||||||
fmt.Printf("clairctl: \"pull\" requires a minimum of 1 argument\n")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
imageName := args[0]
|
|
||||||
var manifest schema1.SignedManifest
|
|
||||||
var named reference.Named
|
|
||||||
|
|
||||||
if !docker.IsLocal {
|
|
||||||
n, m, err := dockerdist.DownloadManifest(imageName, true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(errInternalError)
|
|
||||||
logrus.Fatalf("parsing image %q: %v", imageName, err)
|
|
||||||
}
|
|
||||||
// Ensure that the manifest type is supported.
|
|
||||||
switch m.(type) {
|
|
||||||
case *schema1.SignedManifest:
|
|
||||||
manifest = m.(schema1.SignedManifest)
|
|
||||||
named = n
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
fmt.Println(errInternalError)
|
|
||||||
logrus.Fatalf("only v1 manifests are currently supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
named, manifest, err = LocalManifest(imageName)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(errInternalError)
|
|
||||||
logrus.Fatalf("parsing image %q: %v", imageName, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data := struct {
|
|
||||||
V1Manifest schema1.SignedManifest
|
|
||||||
Named reference.Named
|
|
||||||
}{
|
|
||||||
V1Manifest: manifest,
|
|
||||||
Named: named,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := template.Must(template.New("pull").Parse(pullDockerTplt)).Execute(os.Stdout, data)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(errInternalError)
|
|
||||||
logrus.Fatalf("rendering image: %v", err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func LocalManifest(imageName string) (reference.Named, schema1.SignedManifest, error) {
|
|
||||||
manifest := schema1.SignedManifest{}
|
|
||||||
// Parse the image name as a docker image reference.
|
|
||||||
named, err := reference.ParseNamed(imageName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, manifest, err
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoint := "unix:///var/run/docker.sock"
|
|
||||||
client, _ := dockercli.NewClient(endpoint)
|
|
||||||
histories, _ := client.ImageHistory(imageName)
|
|
||||||
for _, history := range histories {
|
|
||||||
var d digest.Digest
|
|
||||||
d, err := digest.ParseDigest(history.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, manifest, err
|
|
||||||
}
|
|
||||||
manifest.FSLayers = append(manifest.FSLayers, schema1.FSLayer{BlobSum: d})
|
|
||||||
}
|
|
||||||
return named, manifest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RootCmd.AddCommand(pullDockerCmd)
|
|
||||||
pullDockerCmd.Flags().BoolVarP(&docker.IsLocal, "local", "l", false, "Use local images")
|
|
||||||
}
|
|
@ -1,55 +1,80 @@
|
|||||||
// Copyright © 2016 NAME HERE <EMAIL ADDRESS>
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"os"
|
"os"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/coreos/clair/cmd/clairctl/docker"
|
"github.com/coreos/clair/cmd/clairctl/docker"
|
||||||
|
"github.com/coreos/clair/cmd/clairctl/dockerdist"
|
||||||
|
"github.com/docker/distribution/digest"
|
||||||
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
|
"github.com/docker/docker/reference"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
dockercli "github.com/fsouza/go-dockerclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
const pullTplt = `
|
const pullTplt = `
|
||||||
Image: {{.String}}
|
Image: {{.Named.FullName}}
|
||||||
{{.FsLayers | len}} layers found
|
{{.V1Manifest.FSLayers | len}} layers found
|
||||||
{{range .FsLayers}} ➜ {{.BlobSum}}
|
{{range .V1Manifest.FSLayers}} ➜ {{.BlobSum}}
|
||||||
{{end}}
|
{{end}}
|
||||||
`
|
`
|
||||||
|
|
||||||
// pingCmd represents the ping command
|
|
||||||
var pullCmd = &cobra.Command{
|
var pullCmd = &cobra.Command{
|
||||||
Use: "pull IMAGE",
|
Use: "pull IMAGE",
|
||||||
Short: "Pull Docker image information",
|
Short: "Pull Docker image to Clair",
|
||||||
Long: `Pull image information from Docker Hub or Registry`,
|
Long: `Upload a Docker image to Clair for further analysis`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
//TODO how to use args with viper
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
fmt.Printf("clairctl: \"pull\" requires a minimum of 1 argument\n")
|
fmt.Printf("clairctl: \"pull\" requires a minimum of 1 argument\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
im := args[0]
|
|
||||||
image, err := docker.Pull(im)
|
imageName := args[0]
|
||||||
if err != nil {
|
var manifest schema1.SignedManifest
|
||||||
fmt.Println(errInternalError)
|
var named reference.Named
|
||||||
logrus.Fatalf("pulling image %v: %v", args[0], err)
|
|
||||||
|
if !docker.IsLocal {
|
||||||
|
n, m, err := dockerdist.DownloadManifest(imageName, true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(errInternalError)
|
||||||
|
logrus.Fatalf("parsing image %q: %v", imageName, err)
|
||||||
|
}
|
||||||
|
// Ensure that the manifest type is supported.
|
||||||
|
switch m.(type) {
|
||||||
|
case *schema1.SignedManifest:
|
||||||
|
manifest = m.(schema1.SignedManifest)
|
||||||
|
named = n
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
fmt.Println(errInternalError)
|
||||||
|
logrus.Fatalf("only v1 manifests are currently supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
named, manifest, err = localManifest(imageName)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(errInternalError)
|
||||||
|
logrus.Fatalf("parsing image %q: %v", imageName, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = template.Must(template.New("pull").Parse(pullTplt)).Execute(os.Stdout, image)
|
data := struct {
|
||||||
|
V1Manifest schema1.SignedManifest
|
||||||
|
Named reference.Named
|
||||||
|
}{
|
||||||
|
V1Manifest: manifest,
|
||||||
|
Named: named,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := template.Must(template.New("pull").Parse(pullTplt)).Execute(os.Stdout, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(errInternalError)
|
fmt.Println(errInternalError)
|
||||||
logrus.Fatalf("rendering image: %v", err)
|
logrus.Fatalf("rendering image: %v", err)
|
||||||
@ -57,6 +82,30 @@ var pullCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func localManifest(imageName string) (reference.Named, schema1.SignedManifest, error) {
|
||||||
|
manifest := schema1.SignedManifest{}
|
||||||
|
// Parse the image name as a docker image reference.
|
||||||
|
named, err := reference.ParseNamed(imageName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, manifest, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: use socket by default, but check for DOCKER_HOST env variable
|
||||||
|
endpoint := "unix:///var/run/docker.sock"
|
||||||
|
client, _ := dockercli.NewClient(endpoint)
|
||||||
|
histories, _ := client.ImageHistory(imageName)
|
||||||
|
for _, history := range histories {
|
||||||
|
var d digest.Digest
|
||||||
|
d, err := digest.ParseDigest(history.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, manifest, err
|
||||||
|
}
|
||||||
|
manifest.FSLayers = append(manifest.FSLayers, schema1.FSLayer{BlobSum: d})
|
||||||
|
}
|
||||||
|
return named, manifest, nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RootCmd.AddCommand(pullCmd)
|
RootCmd.AddCommand(pullCmd)
|
||||||
|
pullCmd.Flags().BoolVarP(&docker.IsLocal, "local", "l", false, "Use local images")
|
||||||
}
|
}
|
||||||
|
@ -1,234 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"compress/bzip2"
|
|
||||||
"compress/gzip"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/artyom/untar"
|
|
||||||
"github.com/coreos/clair/cmd/clairctl/config"
|
|
||||||
"github.com/coreos/clair/cmd/clairctl/docker"
|
|
||||||
"github.com/coreos/clair/cmd/clairctl/dockerdist"
|
|
||||||
"github.com/docker/distribution/digest"
|
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
dockercli "github.com/fsouza/go-dockerclient"
|
|
||||||
)
|
|
||||||
|
|
||||||
var pushDockerCmd = &cobra.Command{
|
|
||||||
Use: "push-docker IMAGE",
|
|
||||||
Short: "Push Docker image to Clair",
|
|
||||||
Long: `Upload a Docker image to Clair for further analysis`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
if len(args) != 1 {
|
|
||||||
fmt.Printf("clairctl: \"push\" requires a minimum of 1 argument\n")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
startLocalServer()
|
|
||||||
|
|
||||||
imageName := args[0]
|
|
||||||
if !docker.IsLocal {
|
|
||||||
|
|
||||||
image, manifest, err := dockerdist.DownloadManifest(imageName, true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(errInternalError)
|
|
||||||
logrus.Fatalf("parsing local image %q: %v", imageName, err)
|
|
||||||
}
|
|
||||||
// Ensure that the manifest type is supported.
|
|
||||||
switch manifest.(type) {
|
|
||||||
case *schema1.SignedManifest:
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
fmt.Println(errInternalError)
|
|
||||||
logrus.Fatalf("only v1 manifests are currently supported")
|
|
||||||
}
|
|
||||||
v1manifest := manifest.(*schema1.SignedManifest)
|
|
||||||
|
|
||||||
if err := dockerdist.Push(image, *v1manifest); err != nil {
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(errInternalError)
|
|
||||||
logrus.Fatalf("pushing image %q: %v", imageName, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
named, err := reference.ParseNamed(imageName)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(errInternalError)
|
|
||||||
logrus.Fatalf("pushing image %q: %v", imageName, err)
|
|
||||||
}
|
|
||||||
p, err := save(named)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(errInternalError)
|
|
||||||
logrus.Fatalf("saving image %q: %v", imageName, err)
|
|
||||||
}
|
|
||||||
m, err := historyFromManifest(p)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(errInternalError)
|
|
||||||
logrus.Fatalf("reading manifest %q: %v", imageName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, layer := range m.FSLayers {
|
|
||||||
fmt.Println("ID: ", layer.BlobSum.String())
|
|
||||||
}
|
|
||||||
// var err error
|
|
||||||
// image, err = docker.Parse(imageName)
|
|
||||||
// if err != nil {
|
|
||||||
// fmt.Println(errInternalError)
|
|
||||||
// logrus.Fatalf("parsing local image %q: %v", imageName, err)
|
|
||||||
// }
|
|
||||||
// err = docker.Prepare(&image)
|
|
||||||
// logrus.Debugf("prepared image layers: %d", len(image.FsLayers))
|
|
||||||
// if err != nil {
|
|
||||||
// fmt.Println(errInternalError)
|
|
||||||
// logrus.Fatalf("preparing local image %q from history: %v", imageName, err)
|
|
||||||
// }
|
|
||||||
// logrus.Info("Pushing Image [OLD WAY] should be deprecated")
|
|
||||||
// if err := docker.Push(image); err != nil {
|
|
||||||
// if err != nil {
|
|
||||||
// fmt.Println(errInternalError)
|
|
||||||
// logrus.Fatalf("pushing image %q: %v", imageName, err)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%v has been pushed to Clair\n", imageName)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func historyFromManifest(path string) (*schema1.SignedManifest, error) {
|
|
||||||
mf, err := os.Open(path + "/manifest.json")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer mf.Close()
|
|
||||||
|
|
||||||
// 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 nil, err
|
|
||||||
} else if len(manifest) != 1 {
|
|
||||||
return nil, 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
|
|
||||||
fmt.Println(strings.TrimSuffix(layer, "/layer.tar"))
|
|
||||||
d, err := digest.ParseDigest("sha256:" + strings.TrimSuffix(layer, "/layer.tar"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m.FSLayers = append(m.FSLayers, schema1.FSLayer{BlobSum: d})
|
|
||||||
}
|
|
||||||
|
|
||||||
return &m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func save(image reference.Named) (string, error) {
|
|
||||||
|
|
||||||
imageName := image.Name()
|
|
||||||
path := config.TmpLocal() + "/" + strings.Split(imageName, ":")[0] + "/blobs"
|
|
||||||
|
|
||||||
if _, err := os.Stat(path); os.IsExist(err) {
|
|
||||||
err := os.RemoveAll(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := os.MkdirAll(path, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.Debugln("docker image to save: ", imageName)
|
|
||||||
logrus.Debugln("saving in: ", path)
|
|
||||||
|
|
||||||
// open output file
|
|
||||||
fo, err := os.Create(path + "/output.tar")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// close fo on exit and check for its returned error
|
|
||||||
defer func() {
|
|
||||||
if err := fo.Close(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// make a write buffer
|
|
||||||
w := bufio.NewWriter(fo)
|
|
||||||
|
|
||||||
endpoint := "unix:///var/run/docker.sock"
|
|
||||||
client, _ := dockercli.NewClient(endpoint)
|
|
||||||
err = client.ExportImage(dockercli.ExportImageOptions{Name: imageName, OutputStream: w})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
err = openAndUntar(path+"/output.tar", path)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.Remove(path + "/output.tar")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return path, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func openAndUntar(name, dst string) error {
|
|
||||||
var rd io.Reader
|
|
||||||
f, err := os.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RootCmd.AddCommand(pushDockerCmd)
|
|
||||||
pushDockerCmd.Flags().BoolVarP(&docker.IsLocal, "local", "l", false, "Use local images")
|
|
||||||
}
|
|
@ -7,7 +7,11 @@ import (
|
|||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/coreos/clair/cmd/clairctl/config"
|
"github.com/coreos/clair/cmd/clairctl/config"
|
||||||
"github.com/coreos/clair/cmd/clairctl/docker"
|
"github.com/coreos/clair/cmd/clairctl/docker"
|
||||||
|
"github.com/coreos/clair/cmd/clairctl/dockercli"
|
||||||
|
"github.com/coreos/clair/cmd/clairctl/dockerdist"
|
||||||
"github.com/coreos/clair/cmd/clairctl/server"
|
"github.com/coreos/clair/cmd/clairctl/server"
|
||||||
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
|
"github.com/docker/docker/reference"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,7 +20,6 @@ var pushCmd = &cobra.Command{
|
|||||||
Short: "Push Docker image to Clair",
|
Short: "Push Docker image to Clair",
|
||||||
Long: `Upload a Docker image to Clair for further analysis`,
|
Long: `Upload a Docker image to Clair for further analysis`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
fmt.Printf("clairctl: \"push\" requires a minimum of 1 argument\n")
|
fmt.Printf("clairctl: \"push\" requires a minimum of 1 argument\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -25,53 +28,53 @@ var pushCmd = &cobra.Command{
|
|||||||
startLocalServer()
|
startLocalServer()
|
||||||
|
|
||||||
imageName := args[0]
|
imageName := args[0]
|
||||||
|
var image reference.Named
|
||||||
|
var manifest *schema1.SignedManifest
|
||||||
|
|
||||||
var image docker.Image
|
|
||||||
if !docker.IsLocal {
|
if !docker.IsLocal {
|
||||||
var err error
|
n, m, err := dockerdist.DownloadManifest(imageName, true)
|
||||||
image, err = docker.Pull(imageName)
|
|
||||||
if err != nil {
|
|
||||||
if err == config.ErrLoginNotFound {
|
|
||||||
fmt.Println(err)
|
|
||||||
} else {
|
|
||||||
fmt.Println(errInternalError)
|
|
||||||
}
|
|
||||||
logrus.Fatalf("pulling image %q: %v", imageName, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
image, err = docker.Parse(imageName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(errInternalError)
|
fmt.Println(errInternalError)
|
||||||
logrus.Fatalf("parsing local image %q: %v", imageName, err)
|
logrus.Fatalf("parsing local image %q: %v", imageName, err)
|
||||||
}
|
}
|
||||||
err = docker.Prepare(&image)
|
// Ensure that the manifest type is supported.
|
||||||
logrus.Debugf("prepared image layers: %d", len(image.FsLayers))
|
switch m.(type) {
|
||||||
|
case *schema1.SignedManifest:
|
||||||
|
manifest = m.(*schema1.SignedManifest)
|
||||||
|
image = n
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
fmt.Println(errInternalError)
|
||||||
|
logrus.Fatalf("only v1 manifests are currently supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
n, err := reference.ParseNamed(imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(errInternalError)
|
fmt.Println(errInternalError)
|
||||||
logrus.Fatalf("preparing local image %q from history: %v", imageName, err)
|
logrus.Fatalf("pushing image %q: %v", imageName, err)
|
||||||
}
|
}
|
||||||
|
m, err := dockercli.Save(n)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(errInternalError)
|
||||||
|
logrus.Fatalf("saving image %q: %v", imageName, err)
|
||||||
|
}
|
||||||
|
manifest = m
|
||||||
|
image = n
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Info("Pushing Image")
|
if err := dockerdist.Push(image, *manifest); err != nil {
|
||||||
if err := docker.Push(image); err != nil {
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(errInternalError)
|
fmt.Println(errInternalError)
|
||||||
logrus.Fatalf("pushing image %q: %v", imageName, err)
|
logrus.Fatalf("pushing image %q: %v", imageName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%v has been pushed to Clair\n", imageName)
|
fmt.Printf("%v has been pushed to Clair\n", imageName)
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
RootCmd.AddCommand(pushCmd)
|
|
||||||
pushCmd.Flags().BoolVarP(&docker.IsLocal, "local", "l", false, "Use local images")
|
|
||||||
}
|
|
||||||
|
|
||||||
//StartLocalServer start the clairctl local server needed for reverse proxy and file server
|
|
||||||
func startLocalServer() {
|
func startLocalServer() {
|
||||||
sURL, err := config.LocalServerIP()
|
sURL, err := config.LocalServerIP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -84,3 +87,8 @@ func startLocalServer() {
|
|||||||
logrus.Fatalf("starting local server: %v", err)
|
logrus.Fatalf("starting local server: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RootCmd.AddCommand(pushCmd)
|
||||||
|
pushCmd.Flags().BoolVarP(&docker.IsLocal, "local", "l", false, "Use local images")
|
||||||
|
}
|
||||||
|
@ -57,7 +57,7 @@ func FromHistory(im *Image) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanLocal() error {
|
func CleanLocal() error {
|
||||||
logrus.Debugln("cleaning temporary local repository")
|
logrus.Debugln("cleaning temporary local repository")
|
||||||
err := os.RemoveAll(TmpLocal())
|
err := os.RemoveAll(TmpLocal())
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ func Push(image Image) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if IsLocal {
|
if IsLocal {
|
||||||
if err := cleanLocal(); err != nil {
|
if err := CleanLocal(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
141
cmd/clairctl/dockercli/dockercli.go
Normal file
141
cmd/clairctl/dockercli/dockercli.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Save local images to tmp folder
|
||||||
|
func Save(image reference.Named) (*schema1.SignedManifest, error) {
|
||||||
|
|
||||||
|
imageName := image.Name()
|
||||||
|
path := config.TmpLocal() + "/" + strings.Split(imageName, ":")[0] + "/blobs"
|
||||||
|
|
||||||
|
if _, err := os.Stat(path); os.IsExist(err) {
|
||||||
|
err := os.RemoveAll(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.MkdirAll(path, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugln("docker image to save: ", imageName)
|
||||||
|
logrus.Debugln("saving in: ", path)
|
||||||
|
|
||||||
|
// open output file
|
||||||
|
fo, err := os.Create(path + "/output.tar")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// close fo on exit and check for its returned error
|
||||||
|
defer func() {
|
||||||
|
if err := fo.Close(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// make a write buffer
|
||||||
|
w := bufio.NewWriter(fo)
|
||||||
|
|
||||||
|
endpoint := "unix:///var/run/docker.sock"
|
||||||
|
client, _ := dockerclient.NewClient(endpoint)
|
||||||
|
err = client.ExportImage(dockerclient.ExportImageOptions{Name: imageName, OutputStream: w})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = openAndUntar(path+"/output.tar", path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(path + "/output.tar")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return historyFromManifest(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func historyFromManifest(path string) (*schema1.SignedManifest, error) {
|
||||||
|
mf, err := os.Open(path + "/manifest.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer mf.Close()
|
||||||
|
|
||||||
|
// 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 nil, err
|
||||||
|
} else if len(manifest) != 1 {
|
||||||
|
return nil, 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 nil, err
|
||||||
|
}
|
||||||
|
m.FSLayers = append(m.FSLayers, schema1.FSLayer{BlobSum: d})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func openAndUntar(name, dst string) error {
|
||||||
|
var rd io.Reader
|
||||||
|
f, err := os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
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)
|
||||||
|
}
|
@ -31,28 +31,32 @@ func Push(image reference.Named, manifest schema1.SignedManifest) error {
|
|||||||
}
|
}
|
||||||
hURL := fmt.Sprintf("http://%v/v2", localIP)
|
hURL := fmt.Sprintf("http://%v/v2", localIP)
|
||||||
if docker.IsLocal {
|
if docker.IsLocal {
|
||||||
hURL += "/local"
|
hURL = strings.Replace(hURL, "/v2", "/local", -1)
|
||||||
logrus.Infof("using %v as local url", hURL)
|
logrus.Infof("using %v as local url", hURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
for index, layer := range manifest.FSLayers {
|
for index, layer := range manifest.FSLayers {
|
||||||
lUID := xstrings.Substr(layer.BlobSum.String(), 0, 12)
|
blobsum := layer.BlobSum.String()
|
||||||
|
if docker.IsLocal {
|
||||||
|
blobsum = strings.TrimPrefix(blobsum, "sha256:")
|
||||||
|
}
|
||||||
|
|
||||||
|
lUID := xstrings.Substr(blobsum, 0, 12)
|
||||||
logrus.Infof("Pushing Layer %d/%d [%v]", index+1, layerCount, lUID)
|
logrus.Infof("Pushing Layer %d/%d [%v]", index+1, layerCount, lUID)
|
||||||
|
|
||||||
insertRegistryMapping(layer.BlobSum.String(), image.Hostname())
|
insertRegistryMapping(blobsum, image.Hostname())
|
||||||
payload := v1.LayerEnvelope{Layer: &v1.Layer{
|
payload := v1.LayerEnvelope{Layer: &v1.Layer{
|
||||||
Name: layer.BlobSum.String(),
|
Name: blobsum,
|
||||||
Path: blobsURI(image.Hostname(), image.RemoteName(), layer.BlobSum.String()),
|
Path: blobsURI(image.Hostname(), image.RemoteName(), blobsum),
|
||||||
ParentName: parentID,
|
ParentName: parentID,
|
||||||
Format: "Docker",
|
Format: "Docker",
|
||||||
}}
|
}}
|
||||||
|
|
||||||
//FIXME Update to TLS
|
//FIXME Update to TLS
|
||||||
//FIXME use local with new push
|
if docker.IsLocal {
|
||||||
// if IsLocal {
|
|
||||||
// payload.Layer.Name = layer.History
|
payload.Layer.Path += "/layer.tar"
|
||||||
// payload.Layer.Path += "/layer.tar"
|
}
|
||||||
// }
|
|
||||||
payload.Layer.Path = strings.Replace(payload.Layer.Path, image.Hostname(), hURL, 1)
|
payload.Layer.Path = strings.Replace(payload.Layer.Path, image.Hostname(), hURL, 1)
|
||||||
if err := clair.Push(payload); err != nil {
|
if err := clair.Push(payload); err != nil {
|
||||||
logrus.Infof("adding layer %d/%d [%v]: %v", index+1, layerCount, lUID, err)
|
logrus.Infof("adding layer %d/%d [%v]: %v", index+1, layerCount, lUID, err)
|
||||||
@ -64,11 +68,11 @@ func Push(image reference.Named, manifest schema1.SignedManifest) error {
|
|||||||
parentID = payload.Layer.Name
|
parentID = payload.Layer.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if IsLocal {
|
if docker.IsLocal {
|
||||||
// if err := cleanLocal(); err != nil {
|
if err := docker.CleanLocal(); err != nil {
|
||||||
// return err
|
return err
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,10 +72,10 @@ func newSingleHostReverseProxy() *httputil.ReverseProxy {
|
|||||||
logrus.Errorf("cannot parse url: %v", u)
|
logrus.Errorf("cannot parse url: %v", u)
|
||||||
}
|
}
|
||||||
var host string
|
var host string
|
||||||
if docker.IsLocal {
|
host, err := dockerdist.GetRegistryMapping(validID.FindStringSubmatch(u)[1])
|
||||||
host, _ = docker.GetRegistryMapping(validID.FindStringSubmatch(u)[1])
|
if err != nil {
|
||||||
} else {
|
logrus.Errorf("response error: %v", err)
|
||||||
host, _ = dockerdist.GetRegistryMapping(validID.FindStringSubmatch(u)[1])
|
return
|
||||||
}
|
}
|
||||||
out, _ := url.Parse(host)
|
out, _ := url.Parse(host)
|
||||||
request.URL.Scheme = out.Scheme
|
request.URL.Scheme = out.Scheme
|
||||||
|
Loading…
Reference in New Issue
Block a user