Example #1
0
// runHook executes the supplied hook.Info in an appropriate hook context. If
// the hook itself fails to execute, it returns errHookFailed.
func (u *Uniter) runHook(hi hook.Info) (err error) {
	// Prepare context.
	if err = hi.Validate(); err != nil {
		return err
	}

	hookName := string(hi.Kind)
	relationId := -1
	if hi.Kind.IsRelation() {
		relationId = hi.RelationId
		if hookName, err = u.relationers[relationId].PrepareHook(hi); err != nil {
			return err
		}
	}
	hctxId := fmt.Sprintf("%s:%s:%d", u.unit.Name(), hookName, u.rand.Int63())

	lockMessage := fmt.Sprintf("%s: running hook %q", u.unit.Name(), hookName)
	if err = u.acquireHookLock(lockMessage); err != nil {
		return err
	}
	defer u.hookLock.Unlock()

	hctx, err := u.getHookContext(hctxId, relationId, hi.RemoteUnit)
	if err != nil {
		return err
	}
	srv, socketPath, err := u.startJujucServer(hctx)
	if err != nil {
		return err
	}
	defer srv.Close()

	// Run the hook.
	if err := u.writeState(RunHook, Pending, &hi, nil); err != nil {
		return err
	}
	logger.Infof("running %q hook", hookName)
	ranHook := true
	err = hctx.RunHook(hookName, u.charmPath, u.toolsDir, socketPath)
	if IsMissingHookError(err) {
		ranHook = false
	} else if err != nil {
		logger.Errorf("hook failed: %s", err)
		u.notifyHookFailed(hookName, hctx)
		return errHookFailed
	}
	if err := u.writeState(RunHook, Done, &hi, nil); err != nil {
		return err
	}
	if ranHook {
		logger.Infof("ran %q hook", hookName)
		u.notifyHookCompleted(hookName, hctx)
	} else {
		logger.Infof("skipped %q hook (missing)", hookName)
	}
	return u.commitHook(hi)
}
Example #2
0
// NewRunHook is part of the Factory interface.
func (f *factory) NewRunHook(hookInfo hook.Info) (Operation, error) {
	if err := hookInfo.Validate(); err != nil {
		return nil, err
	}
	return &runHook{
		info:          hookInfo,
		callbacks:     f.config.Callbacks,
		runnerFactory: f.config.RunnerFactory,
	}, nil
}
Example #3
0
func (s *storageResolver) nextHookOp(
	tag names.StorageTag,
	snap remotestate.StorageSnapshot,
	opFactory operation.Factory,
) (operation.Operation, error) {
	logger.Debugf("next hook op for %v: %+v", tag, snap)
	if !snap.Attached {
		return nil, resolver.ErrNoOperation
	}
	storageAttachment, ok := s.storage.storageAttachments[tag]
	if !ok {
		return nil, resolver.ErrNoOperation
	}
	switch snap.Life {
	case params.Alive:
		if storageAttachment.attached {
			// Storage attachments currently do not change
			// (apart from lifecycle) after being provisioned.
			// We don't process unprovisioned storage here,
			// so there's nothing to do.
			return nil, resolver.ErrNoOperation
		}
	case params.Dying:
		if !storageAttachment.attached {
			// Nothing to do: attachment is dying, but
			// the storage-attached hook has not been
			// consumed.
			return nil, resolver.ErrNoOperation
		}
	case params.Dead:
		// Storage must have been Dying to become Dead;
		// no further action is required.
		return nil, resolver.ErrNoOperation
	}

	hookInfo := hook.Info{
		StorageId: tag.Id(),
	}
	if snap.Life == params.Alive {
		hookInfo.Kind = hooks.StorageAttached
	} else {
		hookInfo.Kind = hooks.StorageDetaching
	}
	context := &contextStorage{
		tag:      tag,
		kind:     storage.StorageKind(snap.Kind),
		location: snap.Location,
	}
	storageAttachment.ContextStorageAttachment = context
	s.storage.storageAttachments[tag] = storageAttachment

	logger.Debugf("queued hook: %v", hookInfo)
	return opFactory.NewRunHook(hookInfo)
}
Example #4
0
// NewHookRunner exists to satisfy the Factory interface.
func (f *factory) NewHookRunner(hookInfo hook.Info) (Runner, error) {
	if err := hookInfo.Validate(); err != nil {
		return nil, errors.Trace(err)
	}

	ctx, err := f.contextFactory.HookContext(hookInfo)
	if err != nil {
		return nil, errors.Trace(err)
	}
	runner := NewRunner(ctx, f.paths)
	return runner, nil
}
Example #5
0
// Commit updates relation state to include the fact of the hook's execution,
// records the impact of start and collect-metrics hooks, and queues follow-up
// config-changed hooks to directly follow install and upgrade-charm hooks.
// Commit is part of the Operation interface.
func (rh *runHook) Commit(state State) (*State, error) {
	if err := rh.callbacks.CommitHook(rh.info); err != nil {
		return nil, err
	}

	change := stateChange{
		Kind: Continue,
		Step: Pending,
	}

	var hi *hook.Info = &hook.Info{Kind: hooks.ConfigChanged}
	switch rh.info.Kind {
	case hooks.ConfigChanged:
		if state.Started {
			break
		}
		hi.Kind = hooks.Start
		fallthrough
	case hooks.UpgradeCharm:
		change = stateChange{
			Kind: RunHook,
			Step: Queued,
			Hook: hi,
		}
	}

	newState := change.apply(state)

	switch rh.info.Kind {
	case hooks.Start:
		newState.Started = true
	case hooks.Stop:
		newState.Stopped = true
	case hooks.CollectMetrics:
		newState.CollectMetricsTime = time.Now().Unix()
	case hooks.UpdateStatus:
		newState.UpdateStatusTime = time.Now().Unix()
	}

	return newState, nil
}
Example #6
0
func (s *RelationerSuite) assertHook(c *gc.C, expect hook.Info) {
	s.BackingState.StartSync()
	// We must ensure the local state dir exists first.
	c.Assert(s.dir.Ensure(), gc.IsNil)
	select {
	case hi, ok := <-s.hooks:
		c.Assert(ok, gc.Equals, true)
		expect.ChangeVersion = hi.ChangeVersion
		c.Assert(hi, gc.DeepEquals, expect)
		c.Assert(s.dir.Write(hi), gc.Equals, nil)
	case <-time.After(coretesting.LongWait):
		c.Fatalf("timed out waiting for %#v", expect)
	}
}
Example #7
0
func (s *RelationerSuite) TestPrepareCommitHooks(c *gc.C) {
	r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks)
	err := r.Join()
	c.Assert(err, gc.IsNil)
	ctx := r.Context()
	c.Assert(ctx.UnitNames(), gc.HasLen, 0)

	// Check preparing an invalid hook changes nothing.
	changed := hook.Info{
		Kind:          hooks.RelationChanged,
		RemoteUnit:    "u/1",
		ChangeVersion: 7,
	}
	_, err = r.PrepareHook(changed)
	c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`)
	c.Assert(ctx.UnitNames(), gc.HasLen, 0)
	c.Assert(s.dir.State().Members, gc.HasLen, 0)

	// Check preparing a valid hook updates the context, but not persistent
	// relation state.
	joined := hook.Info{
		Kind:       hooks.RelationJoined,
		RemoteUnit: "u/1",
	}
	name, err := r.PrepareHook(joined)
	c.Assert(err, gc.IsNil)
	c.Assert(s.dir.State().Members, gc.HasLen, 0)
	c.Assert(name, gc.Equals, "ring-relation-joined")
	c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"})

	// Check that preparing the following hook fails as before...
	_, err = r.PrepareHook(changed)
	c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`)
	c.Assert(s.dir.State().Members, gc.HasLen, 0)
	c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"})

	// ...but that committing the previous hook updates the persistent
	// relation state...
	err = r.CommitHook(joined)
	c.Assert(err, gc.IsNil)
	c.Assert(s.dir.State().Members, gc.DeepEquals, map[string]int64{"u/1": 0})
	c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"})

	// ...and allows us to prepare the next hook...
	name, err = r.PrepareHook(changed)
	c.Assert(err, gc.IsNil)
	c.Assert(name, gc.Equals, "ring-relation-changed")
	c.Assert(s.dir.State().Members, gc.DeepEquals, map[string]int64{"u/1": 0})
	c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"})

	// ...and commit it.
	err = r.CommitHook(changed)
	c.Assert(err, gc.IsNil)
	c.Assert(s.dir.State().Members, gc.DeepEquals, map[string]int64{"u/1": 7})
	c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1"})

	// To verify implied behaviour above, prepare a new joined hook with
	// missing membership information, and check relation context
	// membership is updated appropriately...
	joined.RemoteUnit = "u/2"
	joined.ChangeVersion = 3
	name, err = r.PrepareHook(joined)
	c.Assert(err, gc.IsNil)
	c.Assert(s.dir.State().Members, gc.HasLen, 1)
	c.Assert(name, gc.Equals, "ring-relation-joined")
	c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1", "u/2"})

	// ...and so is relation state on commit.
	err = r.CommitHook(joined)
	c.Assert(err, gc.IsNil)
	c.Assert(s.dir.State().Members, gc.DeepEquals, map[string]int64{"u/1": 7, "u/2": 3})
	c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1", "u/2"})
}
Example #8
0
func (s *RelationerSuite) TestPrepareCommitHooks(c *gc.C) {
	r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks)
	err := r.Join()
	c.Assert(err, jc.ErrorIsNil)

	assertMembers := func(expect map[string]int64) {
		c.Assert(s.dir.State().Members, jc.DeepEquals, expect)
		expectNames := make([]string, 0, len(expect))
		for name := range expect {
			expectNames = append(expectNames, name)
		}
		c.Assert(r.ContextInfo().MemberNames, jc.SameContents, expectNames)
	}
	assertMembers(map[string]int64{})

	// Check preparing an invalid hook changes nothing.
	changed := hook.Info{
		Kind:          hooks.RelationChanged,
		RemoteUnit:    "u/1",
		ChangeVersion: 7,
	}
	_, err = r.PrepareHook(changed)
	c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`)
	assertMembers(map[string]int64{})

	// Check preparing a valid hook updates neither the context nor persistent
	// relation state.
	joined := hook.Info{
		Kind:       hooks.RelationJoined,
		RemoteUnit: "u/1",
	}
	name, err := r.PrepareHook(joined)
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(name, gc.Equals, "ring-relation-joined")
	assertMembers(map[string]int64{})

	// Check that preparing the following hook fails as before...
	_, err = r.PrepareHook(changed)
	c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`)
	assertMembers(map[string]int64{})

	// ...but that committing the previous hook updates the persistent
	// relation state...
	err = r.CommitHook(joined)
	c.Assert(err, jc.ErrorIsNil)
	assertMembers(map[string]int64{"u/1": 0})

	// ...and allows us to prepare the next hook...
	name, err = r.PrepareHook(changed)
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(name, gc.Equals, "ring-relation-changed")
	assertMembers(map[string]int64{"u/1": 0})

	// ...and commit it.
	err = r.CommitHook(changed)
	c.Assert(err, jc.ErrorIsNil)
	assertMembers(map[string]int64{"u/1": 7})

	// To verify implied behaviour above, prepare a new joined hook with
	// missing membership information, and check relation context
	// membership is stil not updated...
	joined.RemoteUnit = "u/2"
	joined.ChangeVersion = 3
	name, err = r.PrepareHook(joined)
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(name, gc.Equals, "ring-relation-joined")
	assertMembers(map[string]int64{"u/1": 7})

	// ...until commit, at which point so is relation state.
	err = r.CommitHook(joined)
	c.Assert(err, jc.ErrorIsNil)
	assertMembers(map[string]int64{"u/1": 7, "u/2": 3})
}
Example #9
0
func (s *storageResolver) nextHookOp(
	tag names.StorageTag,
	snap remotestate.StorageSnapshot,
	opFactory operation.Factory,
) (operation.Operation, error) {

	logger.Debugf("next hook op for %v: %+v", tag, snap)

	if snap.Life == params.Dead {
		// Storage must have been Dying to become Dead;
		// no further action is required.
		return nil, resolver.ErrNoOperation
	}

	hookInfo := hook.Info{StorageId: tag.Id()}
	switch snap.Life {
	case params.Alive:
		storageAttachment, ok := s.storage.storageAttachments[tag]
		if ok && storageAttachment.attached {
			// Once the storage is attached, we only care about
			// lifecycle state changes.
			return nil, resolver.ErrNoOperation
		}
		// The storage-attached hook has not been committed, so add the
		// storage to the pending set.
		s.storage.pending.Add(tag)
		if !snap.Attached {
			// The storage attachment has not been provisioned yet,
			// so just ignore it for now. We'll be notified again
			// when it has been provisioned.
			return nil, resolver.ErrNoOperation
		}
		// The storage is alive, but we haven't previously run the
		// "storage-attached" hook. Do so now.
		hookInfo.Kind = hooks.StorageAttached
	case params.Dying:
		storageAttachment, ok := s.storage.storageAttachments[tag]
		if !ok || !storageAttachment.attached {
			// Nothing to do: attachment is dying, but
			// the storage-attached hook has not been
			// issued.
			return nil, resolver.ErrNoOperation
		}
		// The storage is dying, but we haven't previously run the
		// "storage-detached" hook. Do so now.
		hookInfo.Kind = hooks.StorageDetaching
	}

	// Update the local state to reflect what we're about to report
	// to a hook.
	stateFile, err := readStateFile(s.storage.storageStateDir, tag)
	if err != nil {
		return nil, errors.Trace(err)
	}
	s.storage.storageAttachments[tag] = storageAttachment{
		stateFile, &contextStorage{
			tag:      tag,
			kind:     storage.StorageKind(snap.Kind),
			location: snap.Location,
		},
	}

	return opFactory.NewRunHook(hookInfo)
}
Example #10
0
// runHook executes the supplied hook.Info in an appropriate hook context. If
// the hook itself fails to execute, it returns errHookFailed.
func (u *Uniter) runHook(hi hook.Info) (err error) {
	// Prepare context.
	if err = hi.Validate(); err != nil {
		return err
	}

	hookName := string(hi.Kind)
	actionParams := map[string]interface{}(nil)

	relationId := -1
	if hi.Kind.IsRelation() {
		relationId = hi.RelationId
		if hookName, err = u.relationers[relationId].PrepareHook(hi); err != nil {
			return err
		}
	} else if hi.Kind == hooks.ActionRequested {
		action, err := u.st.Action(names.NewActionTag(hi.ActionId))
		if err != nil {
			return err
		}
		actionParams = action.Params()
		hookName = action.Name()
	}
	hctxId := fmt.Sprintf("%s:%s:%d", u.unit.Name(), hookName, u.rand.Int63())

	lockMessage := fmt.Sprintf("%s: running hook %q", u.unit.Name(), hookName)
	if err = u.acquireHookLock(lockMessage); err != nil {
		return err
	}
	defer u.hookLock.Unlock()

	hctx, err := u.getHookContext(hctxId, relationId, hi.RemoteUnit, actionParams)
	if err != nil {
		return err
	}
	srv, socketPath, err := u.startJujucServer(hctx)
	if err != nil {
		return err
	}
	defer srv.Close()

	// Run the hook.
	if err := u.writeState(RunHook, Pending, &hi, nil); err != nil {
		return err
	}
	logger.Infof("running %q hook", hookName)

	ranHook := true
	// The reason for the switch at this point is that once inside RunHook,
	// we don't know whether we're running an Action or a regular Hook.
	// RunAction simply calls the exact same method as RunHook, but with
	// the location as "actions" instead of "hooks".
	if hi.Kind == hooks.ActionRequested {
		err = hctx.RunAction(hookName, u.charmPath, u.toolsDir, socketPath)
	} else {
		err = hctx.RunHook(hookName, u.charmPath, u.toolsDir, socketPath)
	}

	if IsMissingHookError(err) {
		ranHook = false
	} else if err != nil {
		logger.Errorf("hook failed: %s", err)
		u.notifyHookFailed(hookName, hctx)
		return errHookFailed
	}
	if err := u.writeState(RunHook, Done, &hi, nil); err != nil {
		return err
	}
	if ranHook {
		logger.Infof("ran %q hook", hookName)
		u.notifyHookCompleted(hookName, hctx)
	} else {
		logger.Infof("skipped %q hook (missing)", hookName)
	}
	return u.commitHook(hi)
}
Example #11
0
// deploy deploys the supplied charm URL, and sets follow-up hook operation state
// as indicated by reason.
func (u *Uniter) deploy(curl *corecharm.URL, reason Op) error {
	if reason != Install && reason != Upgrade {
		panic(fmt.Errorf("%q is not a deploy operation", reason))
	}
	var hi *hook.Info
	if u.s != nil && (u.s.Op == RunHook || u.s.Op == Upgrade) {
		// If this upgrade interrupts a RunHook, we need to preserve the hook
		// info so that we can return to the appropriate error state. However,
		// if we're resuming (or have force-interrupted) an Upgrade, we also
		// need to preserve whatever hook info was preserved when we initially
		// started upgrading, to ensure we still return to the correct state.
		hi = u.s.Hook
	}
	if u.s == nil || u.s.OpStep != Done {
		// Get the new charm bundle before announcing intention to use it.
		logger.Infof("fetching charm %q", curl)
		sch, err := u.st.Charm(curl)
		if err != nil {
			return err
		}
		if err = u.deployer.Stage(sch, u.tomb.Dying()); err != nil {
			return err
		}

		// Set the new charm URL - this returns when the operation is complete,
		// at which point we can refresh the local copy of the unit to get a
		// version with the correct charm URL, and can go ahead and deploy
		// the charm proper.
		if err := u.f.SetCharm(curl); err != nil {
			return err
		}
		if err := u.unit.Refresh(); err != nil {
			return err
		}
		logger.Infof("deploying charm %q", curl)
		if err = u.writeState(reason, Pending, hi, curl); err != nil {
			return err
		}
		if err = u.deployer.Deploy(); err != nil {
			return err
		}
		if err = u.writeState(reason, Done, hi, curl); err != nil {
			return err
		}
	}
	logger.Infof("charm %q is deployed", curl)
	status := Queued
	if hi != nil {
		// If a hook operation was interrupted, restore it.
		status = Pending
	} else {
		// Otherwise, queue the relevant post-deploy hook.
		hi = &hook.Info{}
		switch reason {
		case Install:
			hi.Kind = hooks.Install
		case Upgrade:
			hi.Kind = hooks.UpgradeCharm
		}
	}
	return u.writeState(RunHook, status, hi, nil)
}
Example #12
0
// NewHookRunner exists to satisfy the Factory interface.
func (f *factory) NewHookRunner(hookInfo hook.Info) (Runner, error) {
	if err := hookInfo.Validate(); err != nil {
		return nil, errors.Trace(err)
	}

	ctx, err := f.coreContext()
	if err != nil {
		return nil, errors.Trace(err)
	}

	hookName := string(hookInfo.Kind)
	if hookInfo.Kind.IsRelation() {
		ctx.relationId = hookInfo.RelationId
		ctx.remoteUnitName = hookInfo.RemoteUnit
		relation, found := ctx.relations[hookInfo.RelationId]
		if !found {
			return nil, errors.Errorf("unknown relation id: %v", hookInfo.RelationId)
		}
		if hookInfo.Kind == hooks.RelationDeparted {
			relation.cache.RemoveMember(hookInfo.RemoteUnit)
		} else if hookInfo.RemoteUnit != "" {
			// Clear remote settings cache for changing remote unit.
			relation.cache.InvalidateMember(hookInfo.RemoteUnit)
		}
		hookName = fmt.Sprintf("%s-%s", relation.Name(), hookInfo.Kind)
	}
	if hookInfo.Kind.IsStorage() {
		ctx.storageTag = names.NewStorageTag(hookInfo.StorageId)
		if _, found := ctx.storage.Storage(ctx.storageTag); !found {
			return nil, errors.Errorf("unknown storage id: %v", hookInfo.StorageId)
		}
		storageName, err := names.StorageName(hookInfo.StorageId)
		if err != nil {
			return nil, errors.Trace(err)
		}
		hookName = fmt.Sprintf("%s-%s", storageName, hookName)
	}
	// Metrics are only sent from the collect-metrics hook.
	if hookInfo.Kind == hooks.CollectMetrics {
		ch, err := f.getCharm()
		if err != nil {
			return nil, errors.Trace(err)
		}
		ctx.definedMetrics = ch.Metrics()

		chURL, err := f.unit.CharmURL()
		if err != nil {
			return nil, errors.Trace(err)
		}

		charmMetrics := map[string]charm.Metric{}
		if ch.Metrics() != nil {
			charmMetrics = ch.Metrics().Metrics
		}
		ctx.metricsRecorder, err = metrics.NewJSONMetricRecorder(
			f.paths.GetMetricsSpoolDir(),
			charmMetrics,
			chURL.String())
		if err != nil {
			return nil, errors.Trace(err)
		}
	}
	ctx.id = f.newId(hookName)
	runner := NewRunner(ctx, f.paths)
	return runner, nil
}
Example #13
0
// runHook executes the supplied hook.Info in an appropriate hook context. If
// the hook itself fails to execute, it returns errHookFailed.
func (u *Uniter) runHook(hi hook.Info) (err error) {
	// Prepare context.
	if err = hi.Validate(); err != nil {
		return err
	}

	hookName := string(hi.Kind)
	actionParams := map[string]interface{}(nil)

	// This value is needed to pass results of Action param validation
	// in case of error or invalidation.  This is probably bad form; it
	// will be corrected in PR refactoring HookContext.
	// TODO(binary132): handle errors before grabbing hook context.
	var actionParamsErr error = nil

	relationId := -1
	if hi.Kind.IsRelation() {
		relationId = hi.RelationId
		if hookName, err = u.relationers[relationId].PrepareHook(hi); err != nil {
			return err
		}
	} else if hi.Kind == hooks.ActionRequested {
		action, err := u.st.Action(names.NewActionTag(hi.ActionId))
		if err != nil {
			return err
		}
		actionParams = action.Params()
		hookName = action.Name()
		_, actionParamsErr = u.validateAction(hookName, actionParams)
	}
	hctxId := fmt.Sprintf("%s:%s:%d", u.unit.Name(), hookName, u.rand.Int63())

	lockMessage := fmt.Sprintf("%s: running hook %q", u.unit.Name(), hookName)
	if err = u.acquireHookLock(lockMessage); err != nil {
		return err
	}
	defer u.hookLock.Unlock()

	hctx, err := u.getHookContext(hctxId, relationId, hi.RemoteUnit, actionParams)
	if err != nil {
		return err
	}

	srv, socketPath, err := u.startJujucServer(hctx)
	if err != nil {
		return err
	}
	defer srv.Close()

	// Run the hook.
	if err := u.writeState(RunHook, Pending, &hi, nil); err != nil {
		return err
	}
	logger.Infof("running %q hook", hookName)

	ranHook := true
	// The reason for the conditional at this point is that once inside
	// RunHook, we don't know whether we're running an Action or a regular
	// Hook.  RunAction simply calls the exact same method as RunHook, but
	// with the location as "actions" instead of "hooks".
	if hi.Kind == hooks.ActionRequested {
		if actionParamsErr != nil {
			logger.Errorf("action %q param validation failed: %s", hookName, actionParamsErr.Error())
			u.notifyHookFailed(hookName, hctx)
			return u.commitHook(hi)
		}
		err = hctx.RunAction(hookName, u.charmPath, u.toolsDir, socketPath)
	} else {
		err = hctx.RunHook(hookName, u.charmPath, u.toolsDir, socketPath)
	}

	// Since the Action validation error was separated, regular error pathways
	// will still occur correctly.
	if IsMissingHookError(err) {
		ranHook = false
	} else if err != nil {
		logger.Errorf("hook failed: %s", err)
		u.notifyHookFailed(hookName, hctx)
		return errHookFailed
	}
	if err := u.writeState(RunHook, Done, &hi, nil); err != nil {
		return err
	}
	if ranHook {
		logger.Infof("ran %q hook", hookName)
		u.notifyHookCompleted(hookName, hctx)
	} else {
		logger.Infof("skipped %q hook (missing)", hookName)
	}
	return u.commitHook(hi)
}