// ConvergedTimer adds a timeout to a select call and blocks until then // TODO: this means we could eventually have per resource converged timeouts func (obj *converger) ConvergedTimer(uuid ConvergerUUID) <-chan time.Time { // be clever: if i'm already converged, this timeout should block which // avoids unnecessary new signals being sent! this avoids fast loops if // we have a low timeout, or in particular a timeout == 0 if uuid.IsConverged() { // blocks the case statement in select forever! return util.TimeAfterOrBlock(-1) } return util.TimeAfterOrBlock(obj.timeout) }
// passwordCallback is a function which returns the appropriate type of callback. func (obj *Remotes) passwordCallback(user, host string) func() (string, error) { timeout := nonInteractivePasswordTimeout // default if obj.interactive { // return after a timeout if not interactive timeout = -1 // unlimited when we asked for interactive mode! } cb := func() (string, error) { passchan := make(chan string) failchan := make(chan error) go func() { log.Printf("Remote: Prompting for %s@%s password...", user, host) fmt.Printf("Password: "******"", e case <-util.TimeAfterOrBlock(timeout): return "", fmt.Errorf("Interactive timeout reached!") } } return cb }
// InstallPackages installs a list of packages by packageID. func (bus *Conn) InstallPackages(packageIDs []string, transactionFlags uint64) error { ch := make(chan *dbus.Signal, PkBufferSize) // we need to buffer :( interfacePath, err := bus.CreateTransaction() // emits Destroy on close if err != nil { return err } var signals = []string{"Package", "ErrorCode", "Finished", "Destroy"} // "ItemProgress", "Status" ? bus.matchSignal(ch, interfacePath, PkIfaceTransaction, signals) obj := bus.GetBus().Object(PkIface, interfacePath) // pass in found transaction path call := obj.Call(FmtTransactionMethod("InstallPackages"), 0, transactionFlags, packageIDs) if call.Err != nil { return call.Err } timeout := -1 // disabled initially finished := false loop: for { select { case signal := <-ch: if signal.Path != interfacePath { log.Printf("PackageKit: Woops: Signal.Path: %+v", signal.Path) continue loop } if signal.Name == FmtTransactionMethod("ErrorCode") { return fmt.Errorf("PackageKit: Error: %v", signal.Body) } else if signal.Name == FmtTransactionMethod("Package") { // a package was installed... // only start the timer once we're here... timeout = PkSignalPackageTimeout } else if signal.Name == FmtTransactionMethod("Finished") { finished = true timeout = PkSignalDestroyTimeout // wait a bit } else if signal.Name == FmtTransactionMethod("Destroy") { return nil // success } else { return fmt.Errorf("PackageKit: Error: %v", signal.Body) } case <-util.TimeAfterOrBlock(timeout): if finished { log.Println("PackageKit: Timeout: InstallPackages: Waiting for 'Destroy'") return nil // got tired of waiting for Destroy } return fmt.Errorf("PackageKit: Timeout: InstallPackages: %v", strings.Join(packageIDs, ", ")) } } }
// CheckApply checks the resource state and applies the resource if the bool // input is true. It returns error info and if the state check passed or not. // TODO: expand the IfCmd to be a list of commands func (obj *ExecRes) CheckApply(apply bool) (checkok bool, err error) { log.Printf("%v[%v]: CheckApply(%t)", obj.Kind(), obj.GetName(), apply) // if there is a watch command, but no if command, run based on state if obj.WatchCmd != "" && obj.IfCmd == "" { if obj.isStateOK { return true, nil } // if there is no watcher, but there is an onlyif check, run it to see } else if obj.IfCmd != "" { // && obj.WatchCmd == "" // there is a watcher, but there is also an if command //} else if obj.IfCmd != "" && obj.WatchCmd != "" { if obj.PollInt > 0 { // && obj.WatchCmd == "" // XXX have the Watch() command output onlyif poll events... // XXX we can optimize by saving those results for returning here // return XXX } var cmdName string var cmdArgs []string if obj.IfShell == "" { // call without a shell // FIXME: are there still whitespace splitting issues? split := strings.Fields(obj.IfCmd) cmdName = split[0] //d, _ := os.Getwd() // TODO: how does this ever error ? //cmdName = path.Join(d, cmdName) cmdArgs = split[1:] } else { cmdName = obj.IfShell // usually bash, or sh cmdArgs = []string{"-c", obj.IfCmd} } err = exec.Command(cmdName, cmdArgs...).Run() if err != nil { // TODO: check exit value return true, nil // don't run } // if there is no watcher and no onlyif check, assume we should run } else { // if obj.WatchCmd == "" && obj.IfCmd == "" { // just run if state is dirty if obj.isStateOK { return true, nil } } // state is not okay, no work done, exit, but without error if !apply { return false, nil } // apply portion log.Printf("%v[%v]: Apply", obj.Kind(), obj.GetName()) var cmdName string var cmdArgs []string if obj.Shell == "" { // call without a shell // FIXME: are there still whitespace splitting issues? // TODO: we could make the split character user selectable...! split := strings.Fields(obj.Cmd) cmdName = split[0] //d, _ := os.Getwd() // TODO: how does this ever error ? //cmdName = path.Join(d, cmdName) cmdArgs = split[1:] } else { cmdName = obj.Shell // usually bash, or sh cmdArgs = []string{"-c", obj.Cmd} } cmd := exec.Command(cmdName, cmdArgs...) //cmd.Dir = "" // look for program in pwd ? var out bytes.Buffer cmd.Stdout = &out if err = cmd.Start(); err != nil { log.Printf("%v[%v]: Error starting Cmd: %v", obj.Kind(), obj.GetName(), err) return false, err } timeout := obj.Timeout if timeout == 0 { // zero timeout means no timer, so disable it timeout = -1 } done := make(chan error) go func() { done <- cmd.Wait() }() select { case err = <-done: if err != nil { log.Printf("%v[%v]: Error waiting for Cmd: %v", obj.Kind(), obj.GetName(), err) return false, err } case <-util.TimeAfterOrBlock(timeout): log.Printf("%v[%v]: Timeout waiting for Cmd", obj.Kind(), obj.GetName()) //cmd.Process.Kill() // TODO: is this necessary? return false, errors.New("Timeout waiting for Cmd!") } // TODO: if we printed the stdout while the command is running, this // would be nice, but it would require terminal log output that doesn't // interleave all the parallel parts which would mix it all up... if s := out.String(); s == "" { log.Printf("Exec[%v]: Command output is empty!", obj.Name) } else { log.Printf("Exec[%v]: Command output is:", obj.Name) log.Printf(out.String()) } // XXX: return based on exit value!! // the state tracking is for exec resources that can't "detect" their // state, and assume it's invalid when the Watch() function triggers. // if we apply state successfully, we should reset it here so that we // know that we have applied since the state was set not ok by event! obj.isStateOK = true // reset return false, nil // success }