From dd4ee77a2360d194e65796173538cc87d226f559 Mon Sep 17 00:00:00 2001 From: Justin Cormack Date: Fri, 24 Mar 2017 13:05:28 +0000 Subject: [PATCH] Use Go code to extract rootfs from system containers - this removes the use of riddler to extract the rootfs, use code we were using for rootfs. riddler now just geenrates the config, next stage is to generate this ourselves - change the naming of the daemons so no longer include number as we do not guarantee ordering as they start up simultaneously Signed-off-by: Justin Cormack --- src/cmd/moby/build.go | 26 +++++++---- src/cmd/moby/config.go | 16 ++++--- src/cmd/moby/image.go | 101 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 120 insertions(+), 23 deletions(-) diff --git a/src/cmd/moby/build.go b/src/cmd/moby/build.go index a780c8f3b..f94b7f9a6 100644 --- a/src/cmd/moby/build.go +++ b/src/cmd/moby/build.go @@ -4,6 +4,7 @@ import ( "archive/tar" "bytes" "errors" + "fmt" "io" "io/ioutil" "log" @@ -108,7 +109,7 @@ func build(name string, args []string) { containers = append(containers, ktar) // convert init image to tarball - init, err := imageExtract(m.Init) + init, err := ImageExtract(m.Init, "") if err != nil { log.Fatalf("Failed to build init tarball: %v", err) } @@ -116,20 +117,29 @@ func build(name string, args []string) { containers = append(containers, buffer) for i, image := range m.System { - args := ConfigToRun(i, "system", &image) - out, err := dockerRun(args...) + config, err := ConfigToOCI(&image) if err != nil { - log.Fatalf("Failed to build container tarball: %v", err) + log.Fatalf("Failed to run riddler to get config.json for %s: %v", image.Image, err) + } + so := fmt.Sprintf("%03d", i) + path := "containers/system/" + so + "-" + image.Name + out, err := ImageBundle(path, image.Image, config) + if err != nil { + log.Fatalf("Failed to extract root filesystem for %s: %v", image.Image, err) } buffer := bytes.NewBuffer(out) containers = append(containers, buffer) } - for i, image := range m.Daemon { - args := ConfigToRun(i, "daemon", &image) - out, err := dockerRun(args...) + for _, image := range m.Daemon { + config, err := ConfigToOCI(&image) if err != nil { - log.Fatalf("Failed to build container tarball: %v", err) + log.Fatalf("Failed to run riddler to get config.json for %s: %v", image.Image, err) + } + path := "containers/daemon/" + image.Name + out, err := ImageBundle(path, image.Image, config) + if err != nil { + log.Fatalf("Failed to extract root filesystem for %s: %v", image.Image, err) } buffer := bytes.NewBuffer(out) containers = append(containers, buffer) diff --git a/src/cmd/moby/config.go b/src/cmd/moby/config.go index 76480dee7..58fbbcfdd 100644 --- a/src/cmd/moby/config.go +++ b/src/cmd/moby/config.go @@ -50,7 +50,7 @@ type MobyImage struct { ReadOnly bool `yaml:"read_only"` } -const riddler = "mobylinux/riddler:c23ab4b6e2a2a4ebd4dd51a059cef7f270da72cb@sha256:7e7744b2f554518411633200db98e599782b120e323348495f43f540de26f7b6" +const riddler = "mobylinux/riddler:2b4051422b155f659019f9e3fef8cca04e153f5c@sha256:f4bb0c39f1e5c636ed52ebd3ed8ec447ca6c0dc554ffb5784cbeff423ac70d34" // NewConfig parses a config file func NewConfig(config []byte) (*Moby, error) { @@ -64,11 +64,10 @@ func NewConfig(config []byte) (*Moby, error) { return &m, nil } -// ConfigToRun converts a config to a series of arguments for docker run -func ConfigToRun(order int, path string, image *MobyImage) []string { +// ConfigToOCI converts a config specification to an OCI config file +func ConfigToOCI(image *MobyImage) (string, error) { // riddler arguments - so := fmt.Sprintf("%03d", order) - args := []string{"-v", "/var/run/docker.sock:/var/run/docker.sock", riddler, image.Image, "/containers/" + path + "/" + so + "-" + image.Name} + args := []string{"-v", "/var/run/docker.sock:/var/run/docker.sock", riddler, image.Image} // docker arguments args = append(args, "--cap-drop", "all") for _, cap := range image.Capabilities { @@ -107,7 +106,12 @@ func ConfigToRun(order int, path string, image *MobyImage) []string { // command args = append(args, image.Command...) - return args + config, err := dockerRun(args...) + if err != nil { + return "", fmt.Errorf("Failed to run riddler to get config.json: %v", err) + } + + return string(config), nil } func filesystem(m *Moby) (*bytes.Buffer, error) { diff --git a/src/cmd/moby/image.go b/src/cmd/moby/image.go index c0531a872..6a8b3d938 100644 --- a/src/cmd/moby/image.go +++ b/src/cmd/moby/image.go @@ -164,23 +164,69 @@ nameserver 2001:4860:4860::8844 "etc/hostname": "moby", } -func imageExtract(image string) ([]byte, error) { +// ImageExtract extracts the filesystem from an image and returns a tarball with the files prefixed by the given path +func ImageExtract(image, prefix string) ([]byte, error) { + out := new(bytes.Buffer) + tw := tar.NewWriter(out) + err := tarPrefix(prefix, tw) + if err != nil { + return []byte{}, err + } + err = imageTar(image, prefix, tw) + if err != nil { + return []byte{}, err + } + err = tw.Close() + if err != nil { + return []byte{}, err + } + return out.Bytes(), nil +} + +// tarPrefix creates the leading directories for a path +func tarPrefix(path string, tw *tar.Writer) error { + if path == "" { + return nil + } + if path[len(path)-1] != byte('/') { + return fmt.Errorf("path does not end with /: %s", path) + } + path = path[:len(path)-1] + if path[0] == byte('/') { + return fmt.Errorf("path should be relative: %s", path) + } + mkdir := "" + for _, dir := range strings.Split(path, "/") { + mkdir = mkdir + dir + hdr := &tar.Header{ + Name: mkdir, + Mode: 0755, + Typeflag: tar.TypeDir, + } + tw.WriteHeader(hdr) + mkdir = mkdir + "/" + } + return nil +} + +func imageTar(image, prefix string, tw *tar.Writer) error { + if prefix != "" && prefix[len(prefix)-1] != byte('/') { + return fmt.Errorf("prefix does not end with /: %s", prefix) + } container, err := dockerCreate(image) if err != nil { - return []byte{}, fmt.Errorf("Failed to docker create image %s: %v", image, err) + return fmt.Errorf("Failed to docker create image %s: %v", image, err) } contents, err := dockerExport(container) if err != nil { - return []byte{}, fmt.Errorf("Failed to docker export container from container %s: %v", container, err) + return fmt.Errorf("Failed to docker export container from container %s: %v", container, err) } err = dockerRm(container) if err != nil { - return []byte{}, fmt.Errorf("Failed to docker rm container %s: %v", container, err) + return fmt.Errorf("Failed to docker rm container %s: %v", container, err) } // now we need to filter out some files from the resulting tar archive - out := new(bytes.Buffer) - tw := tar.NewWriter(out) r := bytes.NewReader(contents) tr := tar.NewReader(r) @@ -191,21 +237,58 @@ func imageExtract(image string) ([]byte, error) { break } if err != nil { - return []byte{}, err + return err } if exclude[hdr.Name] { io.Copy(ioutil.Discard, tr) } else if replace[hdr.Name] != "" { - hdr.Size = int64(len(replace[hdr.Name])) + contents := replace[hdr.Name] + hdr.Size = int64(len(contents)) + hdr.Name = prefix + hdr.Name tw.WriteHeader(hdr) - buf := bytes.NewBufferString(replace[hdr.Name]) + buf := bytes.NewBufferString(contents) io.Copy(tw, buf) + io.Copy(ioutil.Discard, tr) } else { + hdr.Name = prefix + hdr.Name tw.WriteHeader(hdr) io.Copy(tw, tr) } } err = tw.Close() + if err != nil { + return err + } + return nil +} + +// ImageBundle produces an OCI bundle at the given path in a tarball, given an image and a config.json +func ImageBundle(path, image, config string) ([]byte, error) { + out := new(bytes.Buffer) + tw := tar.NewWriter(out) + err := tarPrefix(path+"/rootfs/", tw) + if err != nil { + return []byte{}, err + } + hdr := &tar.Header{ + Name: path + "/" + "config.json", + Mode: 0644, + Size: int64(len(config)), + } + err = tw.WriteHeader(hdr) + if err != nil { + return []byte{}, err + } + buf := bytes.NewBufferString(config) + _, err = io.Copy(tw, buf) + if err != nil { + return []byte{}, err + } + err = imageTar(image, path+"/rootfs/", tw) + if err != nil { + return []byte{}, err + } + err = tw.Close() if err != nil { return []byte{}, err }