func (s *RunHookSuite) TestRunHook(c *gc.C) { for i, t := range runHookTests { c.Logf("\ntest %d: %s; perm %v", i, t.summary, t.spec.perm) ctx, err := s.contextFactory.HookContext(hook.Info{Kind: hooks.ConfigChanged}) c.Assert(err, jc.ErrorIsNil) paths := runnertesting.NewRealPaths(c) rnr := runner.NewRunner(ctx, paths) var hookExists bool if t.spec.perm != 0 { spec := t.spec spec.dir = "hooks" spec.name = hookName c.Logf("makeCharm %#v", spec) makeCharm(c, spec, paths.GetCharmDir()) hookExists = true } t0 := time.Now() err = rnr.RunHook("something-happened") if t.err == "" && hookExists { c.Assert(err, jc.ErrorIsNil) } else if !hookExists { c.Assert(context.IsMissingHookError(err), jc.IsTrue) } else { c.Assert(err, gc.ErrorMatches, t.err) } if t.spec.background != "" && time.Now().Sub(t0) > 5*time.Second { c.Errorf("background process holding up hook execution") } } }
func (w *activeStatusWorker) runHook(code, info string) (runErr error) { unitTag := w.tag paths := uniter.NewPaths(w.config.DataDir(), unitTag) ctx := NewLimitedContext(unitTag.String()) ctx.SetEnvVars(map[string]string{ "JUJU_METER_STATUS": code, "JUJU_METER_INFO": info, }) r := newRunner(ctx, paths) unlock, err := w.acquireExecutionLock() if err != nil { return errors.Annotate(err, "failed to acquire machine lock") } defer func() { unlockErr := unlock() if unlockErr != nil { logger.Criticalf("hook run resulted in error %v; error overridden by unlock failure error", runErr) runErr = unlockErr } }() err = r.RunHook(string(hooks.MeterStatusChanged)) cause := errors.Cause(err) switch { case context.IsMissingHookError(cause): logger.Infof("skipped %q hook (missing)", string(hooks.MeterStatusChanged)) err = nil case err != nil: logger.Errorf("meter status worker encountered hook error: %v", err) return nil } return errors.Trace(w.stateFile.Write(code, info)) }
func (w *connectedStatusHandler) applyStatus(code, info string, abort <-chan struct{}) { logger.Tracef("applying meter status change: %q (%q)", code, info) err := w.config.Runner.RunHook(code, info, abort) cause := errors.Cause(err) switch { case context.IsMissingHookError(cause): logger.Infof("skipped %q hook (missing)", string(hooks.MeterStatusChanged)) case err != nil: logger.Errorf("meter status worker encountered hook error: %v", err) } }
// Execute runs the hook. // Execute is part of the Operation interface. func (rh *runHook) Execute(state State) (*State, error) { message := RunningHookMessage(rh.name) if err := rh.beforeHook(); err != nil { return nil, err } if err := rh.callbacks.SetExecutingStatus(message); err != nil { return nil, err } // The before hook may have updated unit status and we don't want that // to count so reset it here before running the hook. rh.runner.Context().ResetExecutionSetUnitStatus() ranHook := true step := Done err := rh.runner.RunHook(rh.name) cause := errors.Cause(err) switch { case context.IsMissingHookError(cause): ranHook = false err = nil case cause == context.ErrRequeueAndReboot: step = Queued fallthrough case cause == context.ErrReboot: err = ErrNeedsReboot case err == nil: default: logger.Errorf("hook %q failed: %v", rh.name, err) rh.callbacks.NotifyHookFailed(rh.name, rh.runner.Context()) return nil, ErrHookFailed } if ranHook { logger.Infof("ran %q hook", rh.name) rh.callbacks.NotifyHookCompleted(rh.name, rh.runner.Context()) } else { logger.Infof("skipped %q hook (missing)", rh.name) } var hasRunStatusSet bool var afterHookErr error if hasRunStatusSet, afterHookErr = rh.afterHook(state); afterHookErr != nil { return nil, afterHookErr } return stateChange{ Kind: RunHook, Step: step, Hook: &rh.info, HasRunStatusSet: hasRunStatusSet, }.apply(state), err }
// searchHook will search, in order, hooks suffixed with extensions // in windowsSuffixOrder. As windows cares about extensions to determine // how to execute a file, we will allow several suffixes, with powershell // being default. func searchHook(charmDir, hook string) (string, error) { hookFile := filepath.Join(charmDir, hook) if jujuos.HostOS() != jujuos.Windows { // we are not running on windows, // there is no need to look for suffixed hooks return lookPath(hookFile) } for _, suffix := range windowsSuffixOrder { file := fmt.Sprintf("%s%s", hookFile, suffix) foundHook, err := lookPath(file) if err != nil { if context.IsMissingHookError(err) { // look for next suffix continue } return "", err } return foundHook, nil } return "", context.NewMissingHookError(hook) }
func (runner *runner) runCharmHook(hookName string, env []string, charmLocation string) error { charmDir := runner.paths.GetCharmDir() hook, err := searchHook(charmDir, filepath.Join(charmLocation, hookName)) if err != nil { if context.IsMissingHookError(err) { // Missing hook is perfectly valid, but worth mentioning. logger.Infof("skipped %q hook (not implemented)", hookName) } return err } hookCmd := hookCommand(hook) ps := exec.Command(hookCmd[0], hookCmd[1:]...) ps.Env = env ps.Dir = charmDir outReader, outWriter, err := os.Pipe() if err != nil { return errors.Errorf("cannot make logging pipe: %v", err) } ps.Stdout = outWriter ps.Stderr = outWriter hookLogger := &hookLogger{ r: outReader, done: make(chan struct{}), logger: runner.getLogger(hookName), } go hookLogger.run() err = ps.Start() outWriter.Close() if err == nil { // Record the *os.Process of the hook runner.context.SetProcess(ps.Process) // Block until execution finishes err = ps.Wait() } hookLogger.stop() return errors.Trace(err) }