diff --git a/copy.go b/copy.go new file mode 100644 index 00000000..27145dec --- /dev/null +++ b/copy.go @@ -0,0 +1,80 @@ +package main + +import ( + "encoding/json" + "fmt" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" +) + +// FIXME: Also handle schema2, and put this elsewhere: +// docker.go contains similar code, more sophisticated +// (at the very least the deduplication should be reused from there). +func manifestLayers(manifest []byte) ([]string, error) { + man := manifestSchema1{} + if err := json.Unmarshal(manifest, &man); err != nil { + return nil, err + } + + layers := []string{} + for _, layer := range man.FSLayers { + layers = append(layers, layer.BlobSum) + } + return layers, nil +} + +func copyHandler(context *cli.Context) { + if len(context.Args()) != 2 { + logrus.Fatal("Usage: copy source destination") + } + + src, err := parseImageSource(context, context.Args()[0]) + if err != nil { + logrus.Fatalf("Error initializing %s: %s", context.Args()[0], err.Error()) + } + + dest, err := parseImageDestination(context, context.Args()[1]) + if err != nil { + logrus.Fatalf("Error initializing %s: %s", context.Args()[1], err.Error()) + } + + manifest, digest, err := src.GetManifest() + if err != nil { + logrus.Fatalf("Error reading manifest: %s", err.Error()) + } + fmt.Printf("Canonical manifest digest: %s\n", digest) + + layers, err := manifestLayers(manifest) + if err != nil { + logrus.Fatalf("Error parsing manifest: %s", err.Error()) + } + for _, layer := range layers { + stream, err := src.GetLayer(layer) + if err != nil { + logrus.Fatalf("Error reading layer %s: %s", layer, err.Error()) + } + defer stream.Close() + if err := dest.PutLayer(layer, stream); err != nil { + logrus.Fatalf("Error writing layer: %s", err.Error()) + } + } + + sigs, err := src.GetSignatures() + if err != nil { + logrus.Fatalf("Error reading signatures: %s", err.Error()) + } + if err := dest.PutSignatures(sigs); err != nil { + logrus.Fatalf("Error writing signatures: %s", err.Error()) + } + + // FIXME: We need to call PutManifest after PutLayer and PutSignatures. This seems ugly; move to a "set properties" + "commit" model? + if err := dest.PutManifest(manifest); err != nil { + logrus.Fatalf("Error writing manifest: %s", err.Error()) + } +} + +var copyCmd = cli.Command{ + Name: "copy", + Action: copyHandler, +} diff --git a/main.go b/main.go index 3f09c1fb..55bf2b77 100644 --- a/main.go +++ b/main.go @@ -60,6 +60,7 @@ func main() { return nil } app.Commands = []cli.Command{ + copyCmd, inspectCmd, layersCmd, standaloneSignCmd, diff --git a/man1/skopeo.1 b/man1/skopeo.1 index e1260b07..9762129d 100644 --- a/man1/skopeo.1 +++ b/man1/skopeo.1 @@ -8,6 +8,8 @@ .SH NAME skopeo \(em Inspect Docker images and repositories on registries .SH SYNOPSIS +\fBskopeo copy\fR source-location destination-location +.PP \fBskopeo inspect\fR image-name [\fB--raw\fR] .PP \fBskopeo layers\fR image-name @@ -45,6 +47,15 @@ Show help .B "--version, -v" print the version number .SH COMMANDS +.TP +.B copy +Copy an image (manifest, filesystem layers, signatures) from one location to another. +.sp +.B source-location +and +.B destination-location +can be \fBdocker://\fRdocker-reference, \fBdir:\fRlocal-path, or \fBatomic:\fRimagestream-name\fB:\fRtag . + .TP .B inspect Return low-level information on images in a registry