func UploadRPM(r *util.Repo, hypervisor string, image string, config *util.Config, verbose bool) error { file := r.ImagePath(hypervisor, image) vmconfig := &VMConfig{ Image: file, Verbose: verbose, Memory: 64, Networking: "nat", NatRules: []nat.Rule{nat.Rule{GuestPort: "10000", HostPort: "10000"}}, BackingFile: false, } qemu, err := LaunchVM(vmconfig) if err != nil { return err } defer qemu.Process.Kill() conn, err := util.ConnectAndWait("tcp", "localhost:10000") if err != nil { return err } cmd := exec.Command("rpm2cpio", config.RpmBase.Filename()) cmd.Stdout = conn err = cmd.Start() if err != nil { return err } defer cmd.Wait() err = qemu.Wait() conn.Close() return err }
func Pull(r *util.Repo, hypervisor string, image string) error { remote, err := util.IsRemoteImage(r.URL, image) if err != nil { return err } if remote { return r.DownloadImage(r.URL, hypervisor, image) } return r.PullImage(image) }
func checkConfig(t *core.Template, r *util.Repo, hypervisor string) error { if _, err := os.Stat(r.ImagePath(hypervisor, t.Base)); os.IsNotExist(err) { if err := Pull(r, hypervisor, t.Base); err != nil { return err } } for _, value := range t.Files { if _, err := os.Stat(value); os.IsNotExist(err) { return errors.New(fmt.Sprintf("%s: no such file or directory", value)) } } return nil }
func SetArgs(r *util.Repo, hypervisor, image string, args string) error { file := r.ImagePath(hypervisor, image) cmd := exec.Command("qemu-nbd", file) stdout, err := cmd.StdoutPipe() if err != nil { return err } stderr, err := cmd.StderrPipe() if err != nil { return err } err = cmd.Start() if err != nil { return err } go io.Copy(os.Stdout, stdout) go io.Copy(os.Stderr, stderr) conn, err := util.ConnectAndWait("tcp", "localhost:10809") if err != nil { return err } session := &nbd.NbdSession{ Conn: conn, Handle: 0, } if err := session.Handshake(); err != nil { return err } padding := 512 - (len(args) % 512) data := append([]byte(args), make([]byte, padding)...) if err := session.Write(512, data); err != nil { return err } if err := session.Flush(); err != nil { return err } if err := session.Disconnect(); err != nil { return err } conn.Close() cmd.Wait() return nil }
func UploadFiles(r *util.Repo, hypervisor string, image string, config *util.Config, verbose bool) error { file := r.ImagePath(hypervisor, image) vmconfig := &VMConfig{ Image: file, Verbose: verbose, Memory: 64, Networking: "nat", NatRules: []nat.Rule{nat.Rule{GuestPort: "10000", HostPort: "10000"}}, BackingFile: false, } cmd, err := LaunchVM(vmconfig) if err != nil { return err } defer cmd.Process.Kill() conn, err := util.ConnectAndWait("tcp", "localhost:10000") if err != nil { return err } if _, err = os.Stat(config.Rootfs); !os.IsNotExist(err) { err = filepath.Walk(config.Rootfs, func(src string, info os.FileInfo, _ error) error { if info.IsDir() { return nil } dst := strings.Replace(src, config.Rootfs, "", -1) if verbose { fmt.Println(src + " --> " + dst) } return copyFile(conn, src, dst) }) } for dst, src := range config.Files { err = copyFile(conn, src, dst) if verbose { fmt.Println(src + " --> " + dst) } if err != nil { return err } } cpio.WritePadded(conn, cpio.ToWireFormat("TRAILER!!!", 0, 0)) conn.Close() return cmd.Wait() }
func UploadRPM(r *util.Repo, hypervisor string, image string, template *core.Template, verbose bool, mem string) error { file := r.ImagePath(hypervisor, image) size, err := util.ParseMemSize(mem) if err != nil { return err } vmconfig := &qemu.VMConfig{ Image: file, Verbose: verbose, Memory: size, Networking: "nat", NatRules: []nat.Rule{nat.Rule{GuestPort: "10000", HostPort: "10000"}}, BackingFile: false, } vm, err := qemu.LaunchVM(vmconfig) if err != nil { return err } defer vm.Process.Kill() conn, err := util.ConnectAndWait("tcp", "localhost:10000") if err != nil { return err } cmd := exec.Command("rpm2cpio", template.RpmBase.Filename()) cmd.Stdout = conn err = cmd.Start() if err != nil { return err } defer cmd.Wait() err = vm.Wait() conn.Close() return err }
func UploadFiles(r *util.Repo, hypervisor string, image string, config *util.Config, verbose bool) error { file := r.ImagePath(hypervisor, image) vmconfig := &VMConfig{ Image: file, Verbose: verbose, Memory: 64, NatRules: []nat.Rule{nat.Rule{GuestPort: "10000", HostPort: "10000"}}, BackingFile: false, } cmd, err := LaunchVM(vmconfig) if err != nil { return err } defer cmd.Process.Kill() time.Sleep(1 * time.Second) conn, err := net.Dial("tcp", "localhost:10000") if err != nil { return err } for key, value := range config.Files { fi, err := os.Stat(value) if err != nil { return err } cpio.WritePadded(conn, cpio.ToWireFormat(key, cpio.C_ISREG, fi.Size())) b, err := ioutil.ReadFile(value) cpio.WritePadded(conn, b) } cpio.WritePadded(conn, cpio.ToWireFormat("TRAILER!!!", 0, 0)) conn.Close() return cmd.Wait() }
func Build(r *util.Repo, hypervisor string, image string, verbose bool) error { config, err := util.ReadConfig("Capstanfile") if err != nil { return err } fmt.Printf("Building %s...\n", image) err = os.MkdirAll(filepath.Dir(r.ImagePath(hypervisor, image)), 0777) if err != nil { return err } if config.RpmBase != nil { config.RpmBase.Download() } if config.Build != "" { args := strings.Fields(config.Build) cmd := exec.Command(args[0], args[1:]...) out, err := cmd.CombinedOutput() if err != nil { fmt.Println(string(out)) return err } } err = checkConfig(config, r, hypervisor) if err != nil { return err } cmd := exec.Command("cp", r.ImagePath(hypervisor, config.Base), r.ImagePath(hypervisor, image)) _, err = cmd.Output() if err != nil { return err } err = qemu.SetArgs(r, hypervisor, image, "/tools/cpiod.so") if err != nil { return err } if config.RpmBase != nil { err = qemu.UploadRPM(r, hypervisor, image, config, verbose) if err != nil { return err } } err = qemu.UploadFiles(r, hypervisor, image, config, verbose) if err != nil { return err } err = qemu.SetArgs(r, hypervisor, image, config.Cmdline) if err != nil { return err } return nil }
func Build(r *util.Repo, image *core.Image, template *core.Template, verbose bool, mem string) error { if err := os.MkdirAll(filepath.Dir(r.ImagePath(image.Hypervisor, image.Name)), 0777); err != nil { return err } fmt.Printf("Building %s...\n", image.Name) if template.Build != "" { args := strings.Fields(template.Build) cmd := exec.Command(args[0], args[1:]...) out, err := cmd.CombinedOutput() if err != nil { fmt.Println(string(out)) return err } } if err := checkConfig(template, r, image.Hypervisor); err != nil { return err } if template.RpmBase != nil { template.RpmBase.Download() } cmd := util.CopyFile(r.ImagePath(image.Hypervisor, template.Base), r.ImagePath(image.Hypervisor, image.Name)) _, err := cmd.Output() if err != nil { return err } cmdline := "/tools/cpiod.so" if verbose { cmdline = "--verbose" + cmdline } if err := SetArgs(r, image.Hypervisor, image.Name, "/tools/cpiod.so"); err != nil { return err } if template.RpmBase != nil { if err := UploadRPM(r, image.Hypervisor, image.Name, template, verbose, mem); err != nil { return err } } if err := UploadFiles(r, image.Hypervisor, image.Name, template, verbose, mem); err != nil { return err } return SetArgs(r, image.Hypervisor, image.Name, template.Cmdline) }
func Run(repo *util.Repo, config *RunConfig) error { var path string var cmd *exec.Cmd // Start an existing instance if config.ImageName == "" && config.InstanceName != "" { instanceName, instancePlatform := util.SearchInstance(config.InstanceName) if instanceName != "" { defer fmt.Println("") fmt.Printf("Created instance: %s\n", instanceName) // Do not set RawTerm for gce if instancePlatform != "gce" { util.RawTerm() defer util.ResetTerm() } var err error switch instancePlatform { case "qemu": c, _ := qemu.LoadConfig(instanceName) cmd, err = qemu.LaunchVM(c) case "vbox": c, _ := vbox.LoadConfig(instanceName) cmd, err = vbox.LaunchVM(c) case "vmw": c, _ := vmw.LoadConfig(instanceName) cmd, err = vmw.LaunchVM(c) case "gce": c, _ := gce.LoadConfig(instanceName) cmd, err = gce.LaunchVM(c) } if err != nil { return err } if cmd != nil { return cmd.Wait() } return nil } else { // The InstanceName is actually a ImageName // so, cmd like "capstan run cloudius/osv" will work config.ImageName = config.InstanceName config.InstanceName = strings.Replace(config.InstanceName, "/", "-", -1) return Run(repo, config) } // Both ImageName and InstanceName are specified } else if config.ImageName != "" && config.InstanceName != "" { if _, err := os.Stat(config.ImageName); os.IsNotExist(err) { if repo.ImageExists(config.Hypervisor, config.ImageName) { path = repo.ImagePath(config.Hypervisor, config.ImageName) } else if image.IsCloudImage(config.ImageName) { path = config.ImageName } else if util.IsRemoteImage(config.ImageName) { err := Pull(repo, config.Hypervisor, config.ImageName) if err != nil { return err } path = repo.ImagePath(config.Hypervisor, config.ImageName) } else { return fmt.Errorf("%s: no such image", config.ImageName) } if config.Hypervisor == "gce" && !image.IsCloudImage(config.ImageName) { str, err := ioutil.ReadFile(path) if err != nil { return err } path = string(str) } } else { path = config.ImageName } deleteInstance(config.InstanceName) // Valid only when Capstanfile is present } else if config.ImageName == "" && config.InstanceName == "" { config.ImageName = repo.DefaultImage() config.InstanceName = config.ImageName if config.ImageName == "" { return fmt.Errorf("No Capstanfile found, unable to run.") } if !repo.ImageExists(config.Hypervisor, config.ImageName) { if !util.ConfigExists("Capstanfile") { return fmt.Errorf("%s: no such image", config.ImageName) } err := Build(repo, config.Hypervisor, config.ImageName, config.Verbose) if err != nil { return err } } path = repo.ImagePath(config.Hypervisor, config.ImageName) deleteInstance(config.InstanceName) // Cmdline option is not valid } else { usage() return nil } format, err := image.Probe(path) if err != nil { return err } if format == image.Unknown { return fmt.Errorf("%s: image format not recognized, unable to run it.", path) } size, err := util.ParseMemSize(config.Memory) if err != nil { return err } defer fmt.Println("") id := config.InstanceName fmt.Printf("Created instance: %s\n", id) // Do not set RawTerm for gce if config.Hypervisor != "gce" { util.RawTerm() defer util.ResetTerm() } switch config.Hypervisor { case "qemu": dir := filepath.Join(os.Getenv("HOME"), ".capstan/instances/qemu", id) bridge := config.Bridge if bridge == "" { bridge = "virbr0" } config := &qemu.VMConfig{ Name: id, Image: path, Verbose: true, Memory: size, Cpus: config.Cpus, Networking: config.Networking, Bridge: bridge, NatRules: config.NatRules, BackingFile: true, InstanceDir: dir, Monitor: filepath.Join(dir, "osv.monitor"), ConfigFile: filepath.Join(dir, "osv.config"), } cmd, err = qemu.LaunchVM(config) case "vbox": if format != image.VDI && format != image.VMDK { return fmt.Errorf("%s: image format of %s is not supported, unable to run it.", config.Hypervisor, path) } dir := filepath.Join(util.HomePath(), ".capstan/instances/vbox", id) bridge := config.Bridge if bridge == "" { bridge = "vboxnet0" } config := &vbox.VMConfig{ Name: id, Dir: filepath.Join(util.HomePath(), ".capstan/instances/vbox"), Image: path, Memory: size, Cpus: config.Cpus, Networking: config.Networking, Bridge: bridge, NatRules: config.NatRules, ConfigFile: filepath.Join(dir, "osv.config"), } cmd, err = vbox.LaunchVM(config) case "gce": if format != image.GCE_TARBALL && format != image.GCE_GS { return fmt.Errorf("%s: image format of %s is not supported, unable to run it.", config.Hypervisor, path) } dir := filepath.Join(util.HomePath(), ".capstan/instances/gce", id) c := &gce.VMConfig{ Name: id, Image: id, Network: "default", MachineType: "n1-standard-1", Zone: "us-central1-a", ConfigFile: filepath.Join(dir, "osv.config"), InstanceDir: dir, } if format == image.GCE_TARBALL { c.CloudStoragePath = strings.TrimSuffix(config.GCEUploadDir, "/") + "/" + id + ".tar.gz" c.Tarball = path } else { c.CloudStoragePath = path c.Tarball = "" } cmd, err = gce.LaunchVM(c) case "vmw": if format != image.VMDK { return fmt.Errorf("%s: image format of %s is not supported, unable to run it.", config.Hypervisor, path) } dir := filepath.Join(util.HomePath(), ".capstan/instances/vmw", id) config := &vmw.VMConfig{ Name: id, Dir: dir, Image: filepath.Join(dir, "osv.vmdk"), Memory: size, Cpus: config.Cpus, NatRules: config.NatRules, VMXFile: filepath.Join(dir, "osv.vmx"), InstanceDir: dir, OriginalVMDK: path, ConfigFile: filepath.Join(dir, "osv.config"), } cmd, err = vmw.LaunchVM(config) default: err = fmt.Errorf("%s: is not a supported hypervisor", config.Hypervisor) } if err != nil { return err } if cmd != nil { return cmd.Wait() } else { return nil } }
func UploadFiles(r *util.Repo, hypervisor string, image string, t *core.Template, verbose bool, mem string) error { file := r.ImagePath(hypervisor, image) size, err := util.ParseMemSize(mem) if err != nil { return err } vmconfig := &qemu.VMConfig{ Image: file, Verbose: verbose, Memory: size, Networking: "nat", NatRules: []nat.Rule{nat.Rule{GuestPort: "10000", HostPort: "10000"}}, BackingFile: false, } cmd, err := qemu.VMCommand(vmconfig) if err != nil { return err } stdout, err := cmd.StdoutPipe() if err != nil { return err } if err := cmd.Start(); err != nil { return err } defer cmd.Process.Kill() scanner := bufio.NewScanner(stdout) for scanner.Scan() { text := scanner.Text() if verbose { fmt.Println(text) } if text == "Waiting for connection from host..." { break } } if verbose { go io.Copy(os.Stdout, stdout) } conn, err := util.ConnectAndWait("tcp", "localhost:10000") if err != nil { return err } rootfsFiles := make(map[string]string) if _, err = os.Stat(t.Rootfs); !os.IsNotExist(err) { err = filepath.Walk(t.Rootfs, func(src string, info os.FileInfo, _ error) error { dst := strings.Replace(src, t.Rootfs, "", 1) if dst != "" { rootfsFiles[dst] = src } return nil }) } fmt.Println("Uploading files...") bar := pb.New(len(rootfsFiles) + len(t.Files)) if !verbose { bar.Start() } for dst, src := range rootfsFiles { err = copyFile(conn, src, dst) if verbose { fmt.Println(src + " --> " + dst) } else { bar.Increment() } if err != nil { return err } } for dst, src := range t.Files { err = copyFile(conn, src, dst) if verbose { fmt.Println(src + " --> " + dst) } else { bar.Increment() } if err != nil { return err } } cpio.WritePadded(conn, cpio.ToWireFormat("TRAILER!!!", 0, 0)) conn.Close() return cmd.Wait() }
func Pull(r *util.Repo, hypervisor string, image string) error { if util.IsRemoteImage(image) { return r.DownloadImage(hypervisor, image) } return r.PullImage(image) }
func Run(repo *util.Repo, config *RunConfig) error { var path string if config.ImageName != "" { if _, err := os.Stat(config.ImageName); os.IsNotExist(err) { if repo.ImageExists(config.Hypervisor, config.ImageName) { path = repo.ImagePath(config.Hypervisor, config.ImageName) } else if util.IsRemoteImage(config.ImageName) { err := Pull(repo, config.Hypervisor, config.ImageName) if err != nil { return err } path = repo.ImagePath(config.Hypervisor, config.ImageName) } else { return fmt.Errorf("%s: no such image", config.ImageName) } } else { path = config.ImageName } } else { config.ImageName = repo.DefaultImage() if config.ImageName == "" { return fmt.Errorf("No Capstanfile found, unable to run.") } if !repo.ImageExists(config.Hypervisor, config.ImageName) { if !util.ConfigExists("Capstanfile") { return fmt.Errorf("%s: no such image", config.ImageName) } err := Build(repo, config.Hypervisor, config.ImageName, config.Verbose) if err != nil { return err } } path = repo.ImagePath(config.Hypervisor, config.ImageName) } format, err := image.Probe(path) if err != nil { return err } if format == image.Unknown { return fmt.Errorf("%s: image format not recognized, unable to run it.", path) } size, err := util.ParseMemSize(config.Memory) if err != nil { return err } var cmd *exec.Cmd switch config.Hypervisor { case "qemu": id := util.ID() dir := filepath.Join(os.Getenv("HOME"), ".capstan/instances/qemu", id) config := &qemu.VMConfig{ Name: id, Image: path, Verbose: true, Memory: size, Cpus: config.Cpus, NatRules: config.NatRules, BackingFile: true, InstanceDir: dir, Monitor: filepath.Join(dir, "osv.monitor"), } fmt.Printf("Created instance: %s\n", id) tio, _ := util.RawTerm() defer util.ResetTerm(tio) cmd, err = qemu.LaunchVM(config) defer qemu.DeleteVM(config) case "vbox": if format != image.VDI && format != image.VMDK { return fmt.Errorf("%s: image format of %s is not supported, unable to run it.", config.Hypervisor, path) } id := util.ID() config := &vbox.VMConfig{ Name: id, Dir: filepath.Join(util.HomePath(), ".capstan/instances/vbox"), Image: path, Memory: size, Cpus: config.Cpus, NatRules: config.NatRules, } fmt.Printf("Created instance: %s\n", id) tio, _ := util.RawTerm() defer util.ResetTerm(tio) cmd, err = vbox.LaunchVM(config) defer vbox.DeleteVM(config) case "gce": id := util.ID() bucket := "osvimg" config := &gce.VMConfig{ Name: "osv-capstan-" + id, Image: "osv-capstan-" + id, Network: "default", MachineType: "n1-standard-1", Zone: "us-central1-a", CloudStoragePath: "gs://" + bucket + "/osv-capstan-" + id + ".tar.gz", Tarball: path, } cmd, err = gce.LaunchVM(config) case "vmw": id := util.ID() if format != image.VMDK { return fmt.Errorf("%s: image format of %s is not supported, unable to run it.", config.Hypervisor, path) } dir := filepath.Join(util.HomePath(), ".capstan/instances/vmw", id) config := &vmw.VMConfig{ Name: id, Dir: dir, Image: filepath.Join(dir, "osv.vmdk"), Memory: size, Cpus: config.Cpus, NatRules: config.NatRules, VMXFile: filepath.Join(dir, "osv.vmx"), InstanceDir: dir, OriginalVMDK: path, } fmt.Printf("Created instance: %s\n", id) tio, _ := util.RawTerm() defer util.ResetTerm(tio) cmd, err = vmw.LaunchVM(config) defer vmw.DeleteVM(config) default: err = fmt.Errorf("%s: is not a supported hypervisor", config.Hypervisor) } if err != nil { return err } if cmd != nil { return cmd.Wait() } else { return nil } }