Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- func (installer *Installer) install(out http.ResponseWriter, in *http.Request) {
- // TODO: this will be needed only for reataching with a new web installer instance
- // which we don't support yet
- installer.loadState()
- log.Printf("init: %s", installer)
- var install_run *exec.Cmd
- var logFollower *exec.Cmd
- var err error
- var justWatch bool
- if installer.InstallerPID == 0 && installer.CurrentStep != "done" {
- justWatch = false
- // not running yet, launch
- installer.saveFiles()
- // TODO: move existing log file (if we're coming from the nodes page)
- // touch the log file so tail can find it
- logFile, err := os.Create("install.log")
- if err != nil {
- // TODO: error handling
- log.Fatalln("error writing install.log:", err.Error())
- }
- logFile.Close()
- // now run the installer, get logs, show them in a progress page
- // TODO: daemonize properly?
- install_run = exec.Command("nohup", "bash", "-c", `for i in $(seq 1 50); do wait=$(( $RANDOM / 5000)); echo $wait; sleep $wait; done > install.log`)
- err = install_run.Start()
- if err != nil {
- // TODO: we're poché'd
- http.Error(out, "We're poché'd! "+err.Error(), 500)
- return
- }
- // NOTE: there's a minimal race condition here, were another installer
- // gets to the install step before we get to write this, but we're not
- // worrying about it yet
- // TODO: lock file?
- installer.CurrentStep = "install"
- installer.InstallerPID = install_run.Process.Pid
- log.Printf("running: %s", installer)
- installer.saveState()
- } else {
- log.Printf("watching: %s", installer)
- justWatch = true
- }
- if installer.CurrentStep != "done" {
- log.Printf("tailing: %s", installer)
- // tail has a --pid option, but if we provide a bad PID (for intance, 0),
- // it blocks forever, so we will have to kill it anyways. just do that
- logFollower = exec.Command("tail", "--lines=+0", "--follow", "install.log")
- } else {
- log.Printf("cating: %s", installer)
- logFollower = exec.Command("cat", "install.log")
- }
- stdout, err := logFollower.StdoutPipe()
- if err != nil {
- // TODO: we're fried
- http.Error(out, "We're fried! "+err.Error(), 500)
- return
- }
- // just in case tail fails for some other reason
- stderr, err := logFollower.StderrPipe()
- if err != nil {
- // TODO: we're fried
- http.Error(out, "We're deep fried! "+err.Error(), 500)
- return
- }
- err = logFollower.Start()
- if err != nil {
- // TODO: we're cooked
- http.Error(out, "We're cooked! "+err.Error(), 500)
- return
- }
- /*
- * the installer does not give much info on stdout, so it's better to read the install log
- * instead of trying to emulate tail, we just call it. the problem is that tail never ends
- * it keeps waiting to more data to be written on the file.
- *
- * but once the installer finishes, we're guaranteed no more data is going to be written,
- * so we can just wait for thie installer to finish and kill tail.
- *
- * but
- *
- * when the installer finishes, logReader.Scan() will block, so we have to run it in parallel
- * so we can actually call install_run.Wait()
- */
- scanLine := make(chan string)
- done := make(chan bool)
- wg := sync.WaitGroup{}
- wg.Add(1)
- go func(word chan string, done chan bool, waitG *sync.WaitGroup) {
- logReader := bufio.NewScanner(stdout)
- for logReader.Scan() {
- scanLine <- logReader.Text()
- }
- done <- true
- wg.Done()
- }(scanLine, done, &wg)
- go func(done chan bool) {
- // '\n' is not portable, but we don't care
- for {
- select {
- case line := <-scanLine:
- fmt.Fprintf(out, "<p class=\"text-monospace\" style=\"margin: 0px;\">"+line+"</p>\n")
- flusher, hasFlusher := out.(http.Flusher)
- // in fact, out is always a Flusher, but let's keep things proper
- if hasFlusher && flusher != nil {
- flusher.Flush()
- }
- fmt.Fprintf(out, "<p class=\"text-monospace\" style=\"margin: 0px;\">Done!</p>\n")
- case <-done:
- return
- }
- }
- }(done)
- if !justWatch {
- log.Printf("waiting: %s", installer)
- install_run.Wait()
- } else if installer.CurrentStep != "done" {
- // Process has a Wait() method, but only works if we are the parent,
- // which is definitely not the case if we're reataching
- log.Printf("polling: %s", installer)
- // so, watch over it
- // FindProcess
- for installer.CurrentStep != "done" {
- log.Printf("polling: %s", installer)
- time.Sleep(1 * time.Second)
- }
- }
- installer.CurrentStep = "done"
- installer.InstallerPID = 0
- installer.saveState()
- // close all listening on this before killing the process to avoid potential races
- err = logFollower.Process.Kill()
- if err != nil {
- // TODO: we're sauté'd
- http.Error(out, "We're boiled! "+err.Error(), 500)
- return
- }
- Error, err := ioutil.ReadAll(stderr)
- if err != nil {
- http.Error(out, "YGBFKM: "+err.Error(), 500)
- return
- }
- err = logFollower.Wait()
- wg.Wait()
- if err != nil {
- unexpectedFailure := true
- // what happens here is this
- // err is a interface variable, meaning its value can be of any type that implements the error interface
- // to do anything useful with it, first you have to figure out if err is actually an ExitError
- // for that you try to cast it and check if it sticks
- if exitErr, isExitErr := err.(*exec.ExitError); isExitErr {
- // now exitErr is of ExitError type, check the (implicitly named) ProcessState
- // but!
- // exitErr.ProcessState.ExitCode() exists only since 1.12, and we're flying 1.10
- // so, dig a little bit more (get Sys(), cast, get the ExitStatus())
- if exitErr.Sys().(syscall.WaitStatus).ExitStatus() == -1 {
- // I know -1 is when the process has not finished or killed by a signal
- // it *has* finished because I killed it
- // TODO: do I have a way to know *which* signal?
- unexpectedFailure = false
- }
- }
- if unexpectedFailure {
- // TODO: we're boiled
- http.Error(out, "We're sauté'd! "+string(Error)+err.Error(), 500)
- return
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement