// GetOverlay fetches an overlay from nanobox.io and untars it into dst func GetOverlay(overlay, dst string) { // extract a user and archive (desired engine) user, archive := ExtractArchive(overlay) // extract an engine and version from the archive engine, version := ExtractEngine(archive) // res, err := GetEngine(user, engine, version) if err != nil { config.Fatal("[util/engine/engine] http.Get() failed", err.Error()) } defer res.Body.Close() // switch res.StatusCode / 100 { case 2, 3: break case 4, 5: os.Stderr.WriteString(stylish.ErrBullet("Unable to fetch '%v' overlay, exiting...", engine)) os.Exit(1) } // if err := fileutil.Untar(dst, res.Body); err != nil { config.Fatal("[util/engine/engine] file.Untar() failed", err.Error()) } }
// RemoveDomain func RemoveDomain() { var contents string // open hosts file f, err := os.OpenFile("/etc/hosts", os.O_RDWR, 0644) if err != nil { config.Fatal("[util/file/hosts] os.OpenFile() failed", err.Error()) } defer f.Close() // remove entry from /etc/hosts scanner := bufio.NewScanner(f) for scanner.Scan() { // if the line contain the entry skip it if strings.HasPrefix(scanner.Text(), config.Nanofile.IP) { continue } // add each line back into the file contents += fmt.Sprintf("%s\n", scanner.Text()) } // trim the contents to avoid any extra newlines contents = strings.TrimSpace(contents) // write back the contents of the hosts file minus the removed entry if err := ioutil.WriteFile("/etc/hosts", []byte(contents), 0644); err != nil { config.Fatal("[util/file/hosts] ioutil.WriteFile failed", err.Error()) } }
// Status returns the current status of the VM; this command needs to be run // in a way independant of a Vagrantfile to ensure that the status will always // be available func Status() (status string) { // default status of the VM status = "not created" // attempt to get the uuid; don't handle the error here because there are some // other conditions we want lumped together b, _ := ioutil.ReadFile(fmt.Sprintf("%v/.vagrant/machines/%v/%v/index_uuid", config.AppDir, config.Nanofile.Name, config.Nanofile.Provider)) // set uuid (this will be "" if the above returned an error) uuid := string(b) // attempt to get the machine data b, err = ioutil.ReadFile(config.Home + "/.vagrant.d/data/machine-index/index") // an error here (os.PathError) means that the files was not found, causing // meaning no VMs have been created. Returning here will indicate a VM needs // to be created due to the default status above if uuid == "" || err != nil { return } // get the initial data machineIndex := make(map[string]json.RawMessage) if err := json.Unmarshal(b, &machineIndex); err != nil { config.Fatal("[util/vagrant/status] json.Unmarshal() machineIndex failed", err.Error()) } // read the machines from machineIndex machines := make(map[string]json.RawMessage) if err := json.Unmarshal(machineIndex["machines"], &machines); err != nil { config.Fatal("[util/vagrant/status] json.Unmarshal() machines failed", err.Error()) } // attempt to pull the machine based on the uuid machine := struct { Name string `json:"name"` Provider string `json:"provider"` State string `json:"state"` Vagrantfile string `json:"vagrantfile_path"` }{} if m, ok := machines[uuid]; ok { if err := json.Unmarshal(m, &machine); err != nil { config.Fatal("[util/vagrant/status] json.Unmarshal() machine failed", err.Error()) } } // if machine.State != "" { status = machine.State } return }
// AddDomain func AddDomain() { // open hosts file f, err := os.OpenFile("/etc/hosts", os.O_RDWR|os.O_APPEND, 0644) if err != nil { config.Fatal("[util/file/hosts] os.OpenFile() failed", err.Error()) } defer f.Close() // write the entry to the file entry := fmt.Sprintf("\n%-15v %s # '%v' private network (added by nanobox)", config.Nanofile.IP, config.Nanofile.Domain, config.Nanofile.Name) if _, err := f.WriteString(entry); err != nil { config.Fatal("[util/file/hosts] file.WriteString() failed", err.Error()) } }
// destroy func destroy(ccmd *cobra.Command, args []string) { // PreRun: runnable // if the command is being run with --remove-entry, it means an entry needs // to be removed from the hosts file and execution yielded back to the parent if removeEntry { hosts.RemoveDomain() os.Exit(0) // this exits the sudoed (child) destroy, not the parent proccess } // destroy the vm; this needs to happen before cleaning up the app to ensure // there is a Vagrantfile to run the command with (otherwise it will just get // re-created) fmt.Printf(stylish.Bullet("Destroying nanobox...")) fmt.Printf(stylish.Bullet("Nanobox may require admin privileges to modify your /etc/hosts and /etc/exports files.")) if err := vagrant.Destroy(); err != nil { // dont care if the project no longer exists... thats what we're doing anyway if err != err.(*os.PathError) { vagrant.Fatal("[commands/destroy] vagrant.Destroy() failed", err.Error()) } } // remove app; this needs to happen after the VM is destroyed so that the app // isn't just created again upon running the vagrant command fmt.Printf(stylish.Bullet("Deleting nanobox files (%s)", config.AppDir)) if err := os.RemoveAll(config.AppDir); err != nil { config.Fatal("[commands/destroy] os.RemoveAll() failed", err.Error()) } // attempt to remove the entry regardless of whether its there or not util.PrivilegeExec("dev destroy --remove-entry", fmt.Sprintf("Removing %s domain from /etc/hosts", config.Nanofile.Domain)) }
// Listen connects a to mist, subscribes tags, and listens for 'model' updates func Listen(tags []string, handle func(string) error) error { // only subscribe if a subscription doesn't already exist if _, ok := subscriptions[strings.Join(tags, "")]; ok { return nil } // connect client := connect() defer client.Close() // subscribe subscribe(client, tags) defer delete(subscriptions, strings.Join(tags, "")) // add tags to list of subscriptions subscriptions[strings.Join(tags, "")] = struct{}{} // model := Model{} for msg := range client.Messages() { // unmarshal the incoming Message if err := json.Unmarshal([]byte(msg.Data), &model); err != nil { config.Fatal("[util/server/mist/mist] json.Unmarshal() failed - ", err.Error()) } // handle the status; when the handler returns false, it's time to break the // stream return handle(model.Document.Status) } return nil }
// initialize func initialize(ccmd *cobra.Command, args []string) { // PreRun: runnable // check to see if a box needs to be installed box.Install(nil, args) // creates a project folder at ~/.nanobox/apps/<name> where the Vagrantfile and // .vagrant dir will live for each app if err := os.MkdirAll(config.AppDir, 0755); err != nil { config.Fatal("[commands/init] os.Mkdir() failed", err.Error()) } // set up a dedicated vagrant logger vagrant.NewLogger(config.AppDir + "/vagrant.log") // set up a dedicated server logger server.NewLogger(config.AppDir + "/server.log") // 'parse' the .vmfile (either creating one, or parsing it) config.VMfile = config.ParseVMfile() // // generate a Vagrantfile at ~/.nanobox/apps/<app-name>/Vagrantfile // only if one doesn't already exist (unless forced) if !config.Force { if _, err := os.Stat(config.AppDir + "/Vagrantfile"); err == nil { return } } vagrant.Init() }
// uninstall func uninstall(ccmd *cobra.Command, args []string) { // switch printutil.Prompt("Are you sure you want to uninstall nanobox (y/N)? ") { // don't uninstall by default default: fmt.Println("Nanobox has not been uninstalled!") return // if yes continue to uninstall case "Yes", "yes", "Y", "y": break } fmt.Println("Uninstalling nanobox... ") // do we need to do more here than just this? // - shutdown/destroy all vms? // - remove virtualbox/vagrant? // - probably need to remove nanobox binary // if err := os.RemoveAll(config.Root); err != nil { config.Fatal("[install] os.Remove() failed", err.Error()) } fmt.Println("Nanobox has been successfully uninstalled!") }
// updateImages func updateImages(ccmd *cobra.Command, args []string) { // PreRun: boot fmt.Printf(stylish.Bullet("Updating nanobox docker images...")) // stream update output go Mist.Stream([]string{"log", "deploy"}, Mist.PrintLogStream) // listen for status updates errch := make(chan error) go func() { errch <- Mist.Listen([]string{"job", "imageupdate"}, Mist.ImageUpdates) }() // run an image update if err := Server.Update(""); err != nil { config.Fatal("[commands/update-images] server.Update() failed - ", err.Error()) } // wait for a status update (blocking) err := <-errch // if err != nil { fmt.Printf(err.Error()) return } // PostRun: halt }
// getIgnoreDirs func getIgnoreDirs() { res, err := http.Get(fmt.Sprintf("%s/libdirs", config.ServerURL)) if err != nil { config.Fatal("[util/notify/notify] htto.Get() failed - ", err.Error()) } defer res.Body.Close() // b, err := ioutil.ReadAll(res.Body) if err != nil { config.Fatal("[util/notify/notify] ioutil.ReadAll() failed - ", err.Error()) } if err := json.Unmarshal(b, &ignoreDirs); err != nil { config.Fatal("[util/notify/notify] json.Unmarshal() failed - ", err.Error()) } }
// connect connects 'mist' to the server running on the guest machine func connect() mistClient.Client { mistClient, err := mistClient.NewRemoteClient(config.MistURI) if err != nil { config.Fatal("[util/server/mist/mist] mist.NewRemoteClient() failed - ", err.Error()) } return mistClient }
// Exists ensure vagrant is installed func Exists() (exists bool) { var err error // check if vagrant is installed if _, err = exec.LookPath("vagrant"); err == nil { // initilize Vagrant incase it hasn't been; there is a chance that Vagrant has // never been used meaning there won't be a .vagrant.d folder, so we initialize // vagrant just to ensure it's ready to be used with nanobox by running any // vagrant command (in this case "vagrant -v"). if b, err := exec.Command("vagrant", "-v").CombinedOutput(); err != nil { config.Fatal("[util/vagrant/vagrant] exec.Command() failed", string(b)) } // read setup_version to determine if the version of vagrant is too old // (< 1.5.0) and needs to be migrated b, err := ioutil.ReadFile(filepath.Join(config.Home, ".vagrant.d", "setup_version")) if err != nil { config.Fatal("[util/vagrant/vagrant] ioutil.ReadFile() failed", err.Error()) } // convert the []byte value from the file into a float 'version' version, err := strconv.ParseFloat(string(b), 64) if err != nil { config.Fatal("[util/vagrant/vagrant] strconv.ParseFloat() failed", err.Error()) } // if the current version of vagrant is less than a 'working version' (1.5) // give instructions on how to update if version < 1.5 { fmt.Println(` Nanobox has detected that you are using an old version of Vagrant (<1.5). Before you can continue you'll need to run "vagrant update" and follow the instructions to update Vagrant. `) // exit here to allow for upgrade os.Exit(0) } // if all checks pass exists = true } return }
// Stream connects to mist, subscribes tags, and logs Messages func Stream(tags []string, handle func(Log)) { // add log level to tags tags = append(tags, config.LogLevel) // if this subscription already exists, exit; this prevents double subscriptions if _, ok := subscriptions[strings.Join(tags, "")]; ok { return } // connect to mist client, err := mistClient.NewRemoteClient(config.MistURI) if err != nil { config.Fatal("[util/server/mist/mist] mist.NewRemoteClient() failed - ", err.Error()) } defer client.Close() // this is a bandaid to fix a race condition in mist when immediatly subscribing // after connecting a client; once this is fixed in mist this can be removed <-time.After(time.Second * 1) // subscribe if err := client.Subscribe(tags); err != nil { config.Fatal("[util/server/mist/mist] client.Subscribe() failed - ", err.Error()) } defer delete(subscriptions, strings.Join(tags, "")) // add tags to list of subscriptions subscriptions[strings.Join(tags, "")] = struct{}{} // for msg := range client.Messages() { // log := Log{} // unmarshal the incoming Message if err := json.Unmarshal([]byte(msg.Data), &log); err != nil { config.Fatal("[util/server/mist/mist] json.Unmarshal() failed - ", err.Error()) } // handle(log) } }
// NewLogger sets the vagrant logger to the given path func NewLogger(path string) { var err error // create a file logger if Log, err = lumber.NewAppendLogger(path); err != nil { config.Fatal("[util/server/log] lumber.NewAppendLogger() failed", err.Error()) } }
// Listen connects a to mist, subscribes tags, and listens for 'model' updates func Listen(tags []string, handle func(string) error) error { // only subscribe if a subscription doesn't already exist if _, ok := subscriptions[strings.Join(tags, "")]; ok { return nil } // connect to mist client, err := mistClient.NewRemoteClient(config.MistURI) if err != nil { config.Fatal("[util/server/mist/mist] mist.NewRemoteClient() failed - ", err.Error()) } defer client.Close() // this is a bandaid to fix a race condition in mist when immediatly subscribing // after connecting a client; once this is fixed in mist this can be removed <-time.After(time.Second * 1) // subscribe if err := client.Subscribe(tags); err != nil { config.Fatal("[util/server/mist/mist] client.Subscribe() failed - ", err.Error()) } defer delete(subscriptions, strings.Join(tags, "")) // add tags to list of subscriptions subscriptions[strings.Join(tags, "")] = struct{}{} // model := Model{} for msg := range client.Messages() { // unmarshal the incoming Message if err := json.Unmarshal([]byte(msg.Data), &model); err != nil { config.Fatal("[util/server/mist/mist] json.Unmarshal() failed - ", err.Error()) } // handle the status; when the handler returns false, it's time to break the // stream return handle(model.Document.Status) } return nil }
// halt func halt(ccmd *cobra.Command, args []string) { // server.Unlock() // if err := server.Suspend(); err != nil { config.Fatal("[commands/halt] server.Suspend() failed", err.Error()) } // if err := vagrant.Suspend(); err != nil { config.Fatal("[commands/halt] vagrant.Suspend() failed", err.Error()) } // // os.Exit(0) }
// GetTTYSize func GetTTYSize(fd uintptr) (int, int) { ws, err := term.GetWinsize(fd) if err != nil { config.Fatal("[util/server/exec] term.GetWinsize() failed", err.Error()) } // return int(ws.Width), int(ws.Height) }
// Lock opens a 'lock' with the server; this is done so that nanobox can know how // many clients are currenctly connected to the server func Lock() { conn, err := net.Dial("tcp", config.ServerURI) if err != nil { config.Fatal("[util/server/locks] net.Dial() failed", err.Error()) } conn.Write([]byte(fmt.Sprintf("PUT /lock? HTTP/1.1\r\n\r\n"))) // lock = conn }
// NewLogger sets the vagrant logger to the given path func NewLogger(path string) { var err error // create a file logger (append if already exists) if Log, err = lumber.NewAppendLogger(path); err != nil { config.Fatal("[util/vagrant/log] lumber.NewAppendLogger() failed", err.Error()) } logFile = path }
// Prompt will prompt for input from the shell and return a trimmed response func Prompt(p string, v ...interface{}) string { reader := bufio.NewReader(os.Stdin) // fmt.Print(colorstring.Color(fmt.Sprintf(p, v...))) input, err := reader.ReadString('\n') if err != nil { config.Fatal("[util/print] reader.ReadString() failed - ", err.Error()) } return strings.TrimSpace(input) }
// NewLogger sets the vagrant logger to the given path func NewLogger(path string) { var err error // create a file logger (append if already exists) if Log, err = lumber.NewAppendLogger(path); err != nil { config.Fatal("[util/server/log] lumber.NewAppendLogger() failed", err.Error()) } logFile = path fmt.Printf(stylish.Bullet("Created %s", path)) }
// PrivilegeExec runs a command as sudo func PrivilegeExec(command, msg string) { fmt.Printf(stylish.Bullet(msg)) // cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("sudo %v %v", os.Args[0], command)) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr // run command if err := cmd.Run(); err != nil { config.Fatal("[util/util_unix]", err.Error()) } }
// PrivilegeExec runs a command, but assumes your already running as adminsitrator func PrivilegeExec(command, msg string) { fmt.Printf(stylish.Bullet(msg)) // cmd := exec.Command(os.Args[0], strings.Split(command, " ")...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr // run command if err := cmd.Run(); err != nil { config.Fatal("[commands/commands_windows]", err.Error()) } }
// Download downloads a file func Download(path string, w io.Writer) error { res, err := http.Get(path) if err != nil { return err } defer res.Body.Close() b, err := ioutil.ReadAll(res.Body) if err != nil { config.Fatal("[util/file/file] ioutil.ReadAll() failed", err.Error()) } w.Write(b) return nil }
// watchDir gets run as a walk func, searching for directories to add watchers to func watchDir(path string, fi os.FileInfo, err error) error { // don't walk any directory that is an ignore dir if isIgnoreDir(fi.Name()) { return filepath.SkipDir } // recursively add watchers to directores only (fsnotify will watch all files // in an added directory). Also, dont watch any files/dirs on the ignore list if fi.Mode().IsDir() { if err = watcher.Add(path); err != nil { config.Fatal("[util/notify/notify] watcher.Add() failed - ", err.Error()) } } return nil }
// init func init() { // check for a ~/.nanobox/.auth file and create one if it's not found authfile = filepath.Clean(config.Root + "/.auth") if _, err := os.Stat(authfile); err != nil { f, err := os.Create(authfile) if err != nil { config.Fatal("[auth/auth] os.Create() failed", err.Error()) } defer f.Close() } creds = &credentials{} // if err := config.ParseConfig(authfile, creds); err != nil { fmt.Printf("Nanobox failed to parse the .auth file.\n") os.Exit(1) } }
// HasDomain func HasDomain() (has bool) { // open the /etc/hosts file for scanning... f, err := os.Open("/etc/hosts") if err != nil { config.Fatal("[util/file/hosts] os.Open() failed", err.Error()) } defer f.Close() // scan hosts file looking for an entry corresponding to this app... scanner := bufio.NewScanner(f) for scanner.Scan() { // if an entry with the IP is detected, indicate that it's not needed if strings.HasPrefix(scanner.Text(), config.Nanofile.IP) { has = true } } return }
// isContainer func isContainer(args []string) bool { // fetch services to see if the command is trying to run on a specific container var services []server.Service if _, err := server.Get("/services", &services); err != nil { config.Fatal("[commands/exec] server.Get() failed", err.Error()) } // make an exception for build1, as it wont show up on the list, but will always exist if args[0] == "build1" { return true } // look for a match for _, service := range services { if args[0] == service.Name { return true } } return false }
// authenticate func authenticate(Userslug, password string) (string, string) { fmt.Printf("\nAttempting login for %v... ", Userslug) // get auth_token user, err := api.GetAuthToken(Userslug, password) if err != nil { printutil.Color("[red]failure![reset]") fmt.Println("Unable to login... please verify your username and password are correct.") os.Exit(1) } // if err := saveCredentials(user.ID, user.AuthenticationToken); err != nil { config.Fatal("[auth/auth] saveCredentials failed", err.Error()) } // printutil.Color("[green]success![reset]") return user.ID, user.AuthenticationToken }
// update func update(ccmd *cobra.Command, args []string) { update, err := updatable() if err != nil { config.Error("Unable to determing if updates are available", err.Error()) return } // if the md5s don't match or it's been forced, update switch { case update, config.Force: if err := runUpdate(); err != nil { if _, ok := err.(*os.LinkError); ok { fmt.Println(`Nanobox was unable to update, try again with admin privilege (ex. "sudo nanobox update")`) } else { config.Fatal("[commands/update] runUpdate() failed", err.Error()) } } default: fmt.Printf(stylish.SubBullet("[√] Nanobox is up-to-date")) } }