package main import ( "archive/tar" "bytes" "errors" "flag" "fmt" "io" "io/ioutil" "log" "os/exec" "path/filepath" "github.com/docker/moby/pkg/initrd" ) const ( docker2tar = "mobylinux/docker2tar:82a3f11f70b2959c7100dd6e184b511ebfc65908@sha256:e4fd36febc108477a2e5316d263ac257527779409891c7ac10d455a162df05c1" ) func dockerRun(args ...string) ([]byte, error) { // TODO switch to using Docker client API not exec - just a quick prototype docker, err := exec.LookPath("docker") if err != nil { return []byte{}, errors.New("Docker does not seem to be installed") } args = append([]string{"run", "--rm"}, args...) cmd := exec.Command(docker, args...) stderrPipe, err := cmd.StderrPipe() if err != nil { return []byte{}, err } stdoutPipe, err := cmd.StdoutPipe() if err != nil { return []byte{}, err } err = cmd.Start() if err != nil { return []byte{}, err } stdout, err := ioutil.ReadAll(stdoutPipe) if err != nil { return []byte{}, err } stderr, err := ioutil.ReadAll(stderrPipe) if err != nil { return []byte{}, err } err = cmd.Wait() if err != nil { return []byte{}, fmt.Errorf("%s: %s", err, stderr) } return stdout, nil } func dockerRunInput(input io.Reader, args ...string) ([]byte, error) { // TODO switch to using Docker client API not exec - just a quick prototype docker, err := exec.LookPath("docker") if err != nil { return []byte{}, errors.New("Docker does not seem to be installed") } args = append([]string{"run", "--rm", "-i"}, args...) cmd := exec.Command(docker, args...) cmd.Stdin = input stderrPipe, err := cmd.StderrPipe() if err != nil { return []byte{}, err } stdoutPipe, err := cmd.StdoutPipe() if err != nil { return []byte{}, err } err = cmd.Start() if err != nil { return []byte{}, err } stdout, err := ioutil.ReadAll(stdoutPipe) if err != nil { return []byte{}, err } stderr, err := ioutil.ReadAll(stderrPipe) if err != nil { return []byte{}, err } err = cmd.Wait() if err != nil { return []byte{}, fmt.Errorf("%s: %s", err, stderr) } return stdout, nil } func untarKernel(buf *bytes.Buffer, bzimageName, ktarName string) (*bytes.Buffer, *bytes.Buffer, error) { tr := tar.NewReader(buf) var bzimage, ktar *bytes.Buffer for { hdr, err := tr.Next() if err == io.EOF { break } if err != nil { log.Fatalln(err) } switch hdr.Name { case bzimageName: bzimage = new(bytes.Buffer) _, err := io.Copy(bzimage, tr) if err != nil { return nil, nil, err } case ktarName: ktar = new(bytes.Buffer) _, err := io.Copy(bzimage, tr) if err != nil { return nil, nil, err } default: continue } } if ktar == nil || bzimage == nil { return nil, nil, errors.New("did not find bzImage and kernel.tar in tarball") } return bzimage, ktar, nil } func containersInitrd(containers []*bytes.Buffer) (*bytes.Buffer, error) { w := new(bytes.Buffer) iw := initrd.NewWriter(w) defer iw.Close() for _, file := range containers { _, err := initrd.Copy(iw, file) if err != nil { return nil, err } } return w, nil } func build(m *Moby, name string) { containers := []*bytes.Buffer{} // get kernel bzImage and initrd tarball from container // TODO examine contents to see what names they might have const ( bzimageName = "bzImage" ktarName = "kernel.tar" ) out, err := dockerRun(m.Kernel.Image, "tar", "cf", "-", bzimageName, ktarName) if err != nil { log.Fatalf("Failed to extract kernel image and tarball: %v", err) } buf := bytes.NewBuffer(out) bzimage, ktar, err := untarKernel(buf, bzimageName, ktarName) if err != nil { log.Fatalf("Could not extract bzImage and kernel filesystem from tarball") } containers = append(containers, ktar) // convert init image to tarball init, err := dockerRun("-v", "/var/run/docker.sock:/var/run/docker.sock", docker2tar, m.Init) if err != nil { log.Fatalf("Failed to build init tarball: %v", err) } buffer := bytes.NewBuffer(init) containers = append(containers, buffer) for i, image := range m.System { args := ConfigToRun(i, "system", &image) out, err := dockerRun(args...) if err != nil { log.Fatalf("Failed to build container tarball: %v", err) } buffer := bytes.NewBuffer(out) containers = append(containers, buffer) } for i, image := range m.Daemon { args := ConfigToRun(i, "daemon", &image) out, err := dockerRun(args...) if err != nil { log.Fatalf("Failed to build container tarball: %v", err) } buffer := bytes.NewBuffer(out) containers = append(containers, buffer) } // add files buffer, err = filesystem(m) if err != nil { log.Fatalf("failed to add filesystem parts: %v", err) } containers = append(containers, buffer) initrd, err := containersInitrd(containers) if err != nil { log.Fatalf("Failed to make initrd %v", err) } err = outputs(m, name, bzimage.Bytes(), initrd.Bytes()) if err != nil { log.Fatalf("Error writing outputs: %v", err) } } var ( conf string cmdline bool name string ) func main() { flag.BoolVar(&cmdline, "cmdline", false, "Print the kernel command line and exit") flag.StringVar(&name, "name", "", "Name to use for output files") flag.Parse() conf = "moby.yaml" if len(flag.Args()) > 0 { conf = flag.Args()[0] } if name == "" { name = filepath.Base(conf) ext := filepath.Ext(conf) if ext != "" { name = name[:len(name)-len(ext)] } } config, err := ioutil.ReadFile(conf) if err != nil { log.Fatalf("Cannot open config file: %v", err) } m, err := NewConfig(config) if err != nil { log.Fatalf("Invalid config: %v", err) } if cmdline { fmt.Printf("%s\n", m.Kernel.Cmdline) return } build(m, name) }