added shutdown hook that is executed after the logs have been retrieved

Signed-off-by: Michelle Fernandez Bieber <mfbieber@gmail.com>
pull/31579/head
Michelle Fernandez Bieber 5 years ago committed by Benoit Tigeot
parent 722482b77d
commit 7a557580e1
No known key found for this signature in database
GPG Key ID: 8E6D4FC8AEBDA62C

@ -33,6 +33,24 @@ import (
// execHook executes all of the hooks for the given hook event. // execHook executes all of the hooks for the given hook event.
func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) error { func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) error {
shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, timeout, serverSideApply)
if err != nil {
if err := shutdown(); err != nil {
return err
}
return err
}
return shutdown()
}
type ExecuteShutdownHooks = func() error
func ShutdownNoOp() error {
return nil
}
// execHook executes all of the hooks for the given hook event and return a shutdownHook function to trigger deletions after doing other things like e.g. retrieving logs.
func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) (ExecuteShutdownHooks, error) {
executingHooks := []*release.Hook{} executingHooks := []*release.Hook{}
for _, h := range rl.Hooks { for _, h := range rl.Hooks {
@ -51,12 +69,12 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
cfg.hookSetDeletePolicy(h) cfg.hookSetDeletePolicy(h)
if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, waitStrategy, timeout); err != nil { if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, waitStrategy, timeout); err != nil {
return err return ShutdownNoOp, err
} }
resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), true) resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), true)
if err != nil { if err != nil {
return fmt.Errorf("unable to build kubernetes object for %s hook %s: %w", hook, h.Path, err) return ShutdownNoOp, fmt.Errorf("unable to build kubernetes object for %s hook %s: %w", hook, h.Path, err)
} }
// Record the time at which the hook was applied to the cluster // Record the time at which the hook was applied to the cluster
@ -77,12 +95,12 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
kube.ClientCreateOptionServerSideApply(serverSideApply, false)); err != nil { kube.ClientCreateOptionServerSideApply(serverSideApply, false)); err != nil {
h.LastRun.CompletedAt = time.Now() h.LastRun.CompletedAt = time.Now()
h.LastRun.Phase = release.HookPhaseFailed h.LastRun.Phase = release.HookPhaseFailed
return fmt.Errorf("warning: Hook %s %s failed: %w", hook, h.Path, err) return ShutdownNoOp, fmt.Errorf("warning: Hook %s %s failed: %w", hook, h.Path, err)
} }
waiter, err := cfg.KubeClient.GetWaiter(waitStrategy) waiter, err := cfg.KubeClient.GetWaiter(waitStrategy)
if err != nil { if err != nil {
return fmt.Errorf("unable to get waiter: %w", err) return ShutdownNoOp, fmt.Errorf("unable to get waiter: %w", err)
} }
// Watch hook resources until they have completed // Watch hook resources until they have completed
err = waiter.WatchUntilReady(resources, timeout) err = waiter.WatchUntilReady(resources, timeout)
@ -98,36 +116,39 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
} }
// If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted // If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted
// under failed condition. If so, then clear the corresponding resource object in the hook // under failed condition. If so, then clear the corresponding resource object in the hook
if errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, waitStrategy, timeout); errDeleting != nil { return func() error {
// We log the error here as we want to propagate the hook failure upwards to the release object. if errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, waitStrategy, timeout); errDeleting != nil {
log.Printf("error deleting the hook resource on hook failure: %v", errDeleting) // We log the error here as we want to propagate the hook failure upwards to the release object.
} log.Printf("error deleting the hook resource on hook failure: %v", errDeleting)
}
// If a hook is failed, check the annotation of the previous successful hooks to determine whether the hooks
// should be deleted under succeeded condition. // If a hook is failed, check the annotation of the previous successful hooks to determine whether the hooks
if err := cfg.deleteHooksByPolicy(executingHooks[0:i], release.HookSucceeded, waitStrategy, timeout); err != nil { // should be deleted under succeeded condition.
if err := cfg.deleteHooksByPolicy(executingHooks[0:i], release.HookSucceeded, waitStrategy, timeout); err != nil {
return err
}
return err return err
} }, err
return err
} }
h.LastRun.Phase = release.HookPhaseSucceeded h.LastRun.Phase = release.HookPhaseSucceeded
} }
// If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted return func() error {
// or output should be logged under succeeded condition. If so, then clear the corresponding resource object in each hook // If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted
for i := len(executingHooks) - 1; i >= 0; i-- { // or output should be logged under succeeded condition. If so, then clear the corresponding resource object in each hook
h := executingHooks[i] for i := len(executingHooks) - 1; i >= 0; i-- {
if err := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnSucceeded); err != nil { h := executingHooks[i]
// We log here as we still want to attempt hook resource deletion even if output logging fails. if err := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnSucceeded); err != nil {
log.Printf("error outputting logs for hook failure: %v", err) // We log here as we still want to attempt hook resource deletion even if output logging fails.
} log.Printf("error outputting logs for hook failure: %v", err)
if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, waitStrategy, timeout); err != nil { }
return err if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, waitStrategy, timeout); err != nil {
return err
}
} }
} return nil
}, nil
return nil
} }
// hookByWeight is a sorter for hooks // hookByWeight is a sorter for hooks

@ -57,24 +57,24 @@ func NewReleaseTesting(cfg *Configuration) *ReleaseTesting {
} }
// Run executes 'helm test' against the given release. // Run executes 'helm test' against the given release.
func (r *ReleaseTesting) Run(name string) (ri.Releaser, error) { func (r *ReleaseTesting) Run(name string) (ri.Releaser, ExecuteShutdownHooks, error) {
if err := r.cfg.KubeClient.IsReachable(); err != nil { if err := r.cfg.KubeClient.IsReachable(); err != nil {
return nil, err return nil, ShutdownNoOp, err
} }
if err := chartutil.ValidateReleaseName(name); err != nil { if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, fmt.Errorf("releaseTest: Release name is invalid: %s", name) return nil, ShutdownNoOp, fmt.Errorf("releaseTest: Release name is invalid: %s", name)
} }
// finds the non-deleted release with the given name // finds the non-deleted release with the given name
reli, err := r.cfg.Releases.Last(name) reli, err := r.cfg.Releases.Last(name)
if err != nil { if err != nil {
return reli, err return reli, ShutdownNoOp, err
} }
rel, err := releaserToV1Release(reli) rel, err := releaserToV1Release(reli)
if err != nil { if err != nil {
return rel, err return reli, ShutdownNoOp, err
} }
skippedHooks := []*release.Hook{} skippedHooks := []*release.Hook{}
@ -102,14 +102,16 @@ func (r *ReleaseTesting) Run(name string) (ri.Releaser, error) {
} }
serverSideApply := rel.ApplyMethod == string(release.ApplyMethodServerSideApply) serverSideApply := rel.ApplyMethod == string(release.ApplyMethodServerSideApply)
if err := r.cfg.execHook(rel, release.HookTest, kube.StatusWatcherStrategy, r.Timeout, serverSideApply); err != nil { shutdown, err := r.cfg.execHookWithDelayedShutdown(rel, release.HookTest, kube.StatusWatcherStrategy, r.Timeout, serverSideApply)
if err != nil {
rel.Hooks = append(skippedHooks, rel.Hooks...) rel.Hooks = append(skippedHooks, rel.Hooks...)
r.cfg.Releases.Update(rel) r.cfg.Releases.Update(reli)
return rel, err return reli, shutdown, err
} }
rel.Hooks = append(skippedHooks, rel.Hooks...) rel.Hooks = append(skippedHooks, rel.Hooks...)
return rel, r.cfg.Releases.Update(rel) return reli, shutdown, r.cfg.Releases.Update(reli)
} }
// GetPodLogs will write the logs for all test pods in the given release into // GetPodLogs will write the logs for all test pods in the given release into

@ -65,7 +65,8 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
client.Filters[action.ExcludeNameFilter] = append(client.Filters[action.ExcludeNameFilter], notName.ReplaceAllLiteralString(f, "")) client.Filters[action.ExcludeNameFilter] = append(client.Filters[action.ExcludeNameFilter], notName.ReplaceAllLiteralString(f, ""))
} }
} }
reli, runErr := client.Run(args[0]) reli, shutdown, runErr := client.Run(args[0])
defer shutdown()
// We only return an error if we weren't even able to get the // We only return an error if we weren't even able to get the
// release, otherwise we keep going so we can print status and logs // release, otherwise we keep going so we can print status and logs
// if requested // if requested

Loading…
Cancel
Save