Advertisement
Guest User

Untitled

a guest
May 24th, 2019
129
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Go 5.93 KB | None | 0 0
  1. func (installer *Installer) install(out http.ResponseWriter, in *http.Request) {
  2.     // TODO: this will be needed only for reataching with a new web installer instance
  3.     // which we don't support yet
  4.     installer.loadState()
  5.  
  6.     log.Printf("init: %s", installer)
  7.  
  8.     var install_run *exec.Cmd
  9.     var logFollower *exec.Cmd
  10.     var err error
  11.     var justWatch bool
  12.  
  13.     if installer.InstallerPID == 0 && installer.CurrentStep != "done" {
  14.         justWatch = false
  15.         // not running yet, launch
  16.         installer.saveFiles()
  17.  
  18.         // TODO: move existing log file (if we're coming from the nodes page)
  19.  
  20.         // touch the log file so tail can find it
  21.         logFile, err := os.Create("install.log")
  22.         if err != nil {
  23.             // TODO: error handling
  24.             log.Fatalln("error writing install.log:", err.Error())
  25.         }
  26.         logFile.Close()
  27.  
  28.         // now run the installer, get logs, show them in a progress page
  29.         // TODO: daemonize properly?
  30.         install_run = exec.Command("nohup", "bash", "-c", `for i in $(seq 1 50); do wait=$(( $RANDOM / 5000)); echo $wait; sleep $wait; done > install.log`)
  31.         err = install_run.Start()
  32.         if err != nil {
  33.             // TODO: we're poché'd
  34.             http.Error(out, "We're poché'd! "+err.Error(), 500)
  35.             return
  36.         }
  37.  
  38.         // NOTE: there's a minimal race condition here, were another installer
  39.         // gets to the install step before we get to write this, but we're not
  40.         // worrying about it yet
  41.  
  42.         // TODO: lock file?
  43.  
  44.         installer.CurrentStep = "install"
  45.         installer.InstallerPID = install_run.Process.Pid
  46.         log.Printf("running: %s", installer)
  47.         installer.saveState()
  48.     } else {
  49.         log.Printf("watching: %s", installer)
  50.         justWatch = true
  51.     }
  52.  
  53.     if installer.CurrentStep != "done" {
  54.         log.Printf("tailing: %s", installer)
  55.         // tail has a --pid option, but if we provide a bad PID (for intance, 0),
  56.         // it blocks forever, so we will have to kill it anyways. just do that
  57.         logFollower = exec.Command("tail", "--lines=+0", "--follow", "install.log")
  58.     } else {
  59.         log.Printf("cating: %s", installer)
  60.         logFollower = exec.Command("cat", "install.log")
  61.     }
  62.  
  63.     stdout, err := logFollower.StdoutPipe()
  64.     if err != nil {
  65.         // TODO: we're fried
  66.         http.Error(out, "We're fried! "+err.Error(), 500)
  67.         return
  68.     }
  69.  
  70.     // just in case tail fails for some other reason
  71.     stderr, err := logFollower.StderrPipe()
  72.     if err != nil {
  73.         // TODO: we're fried
  74.         http.Error(out, "We're deep fried! "+err.Error(), 500)
  75.         return
  76.     }
  77.  
  78.     err = logFollower.Start()
  79.     if err != nil {
  80.         // TODO: we're cooked
  81.         http.Error(out, "We're cooked! "+err.Error(), 500)
  82.         return
  83.     }
  84.  
  85.     /*
  86.      * the installer does not give much info on stdout, so it's better to read the install log
  87.      * instead of trying to emulate tail, we just call it. the problem is that tail never ends
  88.      * it keeps waiting to more data to be written on the file.
  89.      *
  90.      * but once the installer finishes, we're guaranteed no more data is going to be written,
  91.      * so we can just wait for thie installer to finish and kill tail.
  92.      *
  93.      * but
  94.      *
  95.      * when the installer finishes, logReader.Scan() will block, so we have to run it in parallel
  96.      * so we can actually call install_run.Wait()
  97.      */
  98.     scanLine := make(chan string)
  99.     done := make(chan bool)
  100.     wg := sync.WaitGroup{}
  101.     wg.Add(1)
  102.  
  103.     go func(word chan string, done chan bool, waitG *sync.WaitGroup) {
  104.         logReader := bufio.NewScanner(stdout)
  105.         for logReader.Scan() {
  106.             scanLine <- logReader.Text()
  107.         }
  108.         done <- true
  109.         wg.Done()
  110.     }(scanLine, done, &wg)
  111.  
  112.     go func(done chan bool) {
  113.  
  114.         // '\n' is not portable, but we don't care
  115.         for {
  116.             select {
  117.             case line := <-scanLine:
  118.  
  119.                 fmt.Fprintf(out, "<p class=\"text-monospace\" style=\"margin: 0px;\">"+line+"</p>\n")
  120.  
  121.                 flusher, hasFlusher := out.(http.Flusher)
  122.                 // in fact, out is always a Flusher, but let's keep things proper
  123.                 if hasFlusher && flusher != nil {
  124.                     flusher.Flush()
  125.                 }
  126.  
  127.                 fmt.Fprintf(out, "<p class=\"text-monospace\" style=\"margin: 0px;\">Done!</p>\n")
  128.             case <-done:
  129.                 return
  130.             }
  131.         }
  132.     }(done)
  133.  
  134.     if !justWatch {
  135.         log.Printf("waiting: %s", installer)
  136.         install_run.Wait()
  137.     } else if installer.CurrentStep != "done" {
  138.         // Process has a Wait() method, but only works if we are the parent,
  139.         // which is definitely not the case if we're reataching
  140.  
  141.         log.Printf("polling: %s", installer)
  142.         // so, watch over it
  143.         // FindProcess
  144.         for installer.CurrentStep != "done" {
  145.             log.Printf("polling: %s", installer)
  146.             time.Sleep(1 * time.Second)
  147.         }
  148.     }
  149.  
  150.     installer.CurrentStep = "done"
  151.     installer.InstallerPID = 0
  152.     installer.saveState()
  153.  
  154.     // close all listening on this before killing the process to avoid potential races
  155.  
  156.     err = logFollower.Process.Kill()
  157.     if err != nil {
  158.         // TODO: we're sauté'd
  159.         http.Error(out, "We're boiled! "+err.Error(), 500)
  160.         return
  161.     }
  162.  
  163.     Error, err := ioutil.ReadAll(stderr)
  164.     if err != nil {
  165.         http.Error(out, "YGBFKM: "+err.Error(), 500)
  166.         return
  167.     }
  168.  
  169.     err = logFollower.Wait()
  170.     wg.Wait()
  171.     if err != nil {
  172.         unexpectedFailure := true
  173.  
  174.         // what happens here is this
  175.         // err is a interface variable, meaning its value can be of any type that implements the error interface
  176.  
  177.         // to do anything useful with it, first you have to figure out if err is actually an ExitError
  178.         // for that you try to cast it and check if it sticks
  179.         if exitErr, isExitErr := err.(*exec.ExitError); isExitErr {
  180.             // now exitErr is of ExitError type, check the (implicitly named) ProcessState
  181.  
  182.             // but!
  183.  
  184.             // exitErr.ProcessState.ExitCode() exists only since 1.12, and we're flying 1.10
  185.             // so, dig a little bit more (get Sys(), cast, get the ExitStatus())
  186.             if exitErr.Sys().(syscall.WaitStatus).ExitStatus() == -1 {
  187.                 // I know -1 is when the process has not finished or killed by a signal
  188.                 // it *has* finished because I killed it
  189.                 // TODO: do I have a way to know *which* signal?
  190.                 unexpectedFailure = false
  191.             }
  192.         }
  193.  
  194.         if unexpectedFailure {
  195.             // TODO: we're boiled
  196.             http.Error(out, "We're sauté'd! "+string(Error)+err.Error(), 500)
  197.             return
  198.         }
  199.     }
  200. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement