feat(test): define tests as Jobs and allow arbitrary supporting resources

This updates commands install, upgrade, delete, and test to share the
same implementation for hook execution.

BREAKING CHANGES:
- The `test-failure` hook annotation is removed.

Signed-off-by: Jacob LeGrone <git@jacob.work>
pull/6054/head
Jacob LeGrone 6 years ago
parent 533a369464
commit 72127c391c
No known key found for this signature in database
GPG Key ID: 5FD0852F235368C1

@ -16,16 +16,13 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"io" "io"
"time" "time"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require" "helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action" "helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/release"
) )
const releaseTestRunHelp = ` const releaseTestRunHelp = `
@ -44,27 +41,7 @@ func newReleaseTestRunCmd(cfg *action.Configuration, out io.Writer) *cobra.Comma
Long: releaseTestRunHelp, Long: releaseTestRunHelp,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
c, errc := client.Run(args[0]) return client.Run(args[0])
testErr := &testErr{}
for {
select {
case err := <-errc:
if err != nil && testErr.failed > 0 {
return testErr.Error()
}
return err
case res, ok := <-c:
if !ok {
break
}
if res.Status == release.TestRunFailure {
testErr.failed++
}
fmt.Fprintf(out, res.Msg+"\n")
}
}
}, },
} }
@ -74,11 +51,3 @@ func newReleaseTestRunCmd(cfg *action.Configuration, out io.Writer) *cobra.Comma
return cmd return cmd
} }
type testErr struct {
failed int
}
func (err *testErr) Error() error {
return errors.Errorf("%v test(s) failed", err.failed)
}

@ -89,7 +89,7 @@ var manifestWithTestHook = `kind: Pod
metadata: metadata:
name: finding-nemo, name: finding-nemo,
annotations: annotations:
"helm.sh/hook": test-success "helm.sh/hook": test
spec: spec:
containers: containers:
- name: nemo-test - name: nemo-test
@ -231,7 +231,7 @@ func namedReleaseStub(name string, status release.Status) *release.Release {
Path: "finding-nemo", Path: "finding-nemo",
Manifest: manifestWithTestHook, Manifest: manifestWithTestHook,
Events: []release.HookEvent{ Events: []release.HookEvent{
release.HookReleaseTestSuccess, release.HookTest,
}, },
}, },
}, },

@ -0,0 +1,106 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action
import (
"bytes"
"sort"
"time"
"github.com/pkg/errors"
"helm.sh/helm/pkg/release"
)
// execHook executes all of the hooks for the given hook event.
func (cfg *Configuration) execHook(hs []*release.Hook, hook release.HookEvent, timeout time.Duration) error {
executingHooks := []*release.Hook{}
for _, h := range hs {
for _, e := range h.Events {
if e == hook {
executingHooks = append(executingHooks, h)
}
}
}
sort.Sort(hookByWeight(executingHooks))
for _, h := range executingHooks {
if err := deleteHookByPolicy(cfg, h, release.HookBeforeHookCreation); err != nil {
return err
}
b := bytes.NewBufferString(h.Manifest)
if err := cfg.KubeClient.Create(b); err != nil {
return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path)
}
b.Reset()
b.WriteString(h.Manifest)
if err := cfg.KubeClient.WatchUntilReady(b, timeout); err != nil {
// If a hook is failed, checkout 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
if err := deleteHookByPolicy(cfg, h, release.HookFailed); err != nil {
return err
}
return err
}
}
// If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted
// under succeeded condition. If so, then clear the corresponding resource object in each hook
for _, h := range executingHooks {
if err := deleteHookByPolicy(cfg, h, release.HookSucceeded); err != nil {
return err
}
h.LastRun = time.Now()
}
return nil
}
// hookByWeight is a sorter for hooks
type hookByWeight []*release.Hook
func (x hookByWeight) Len() int { return len(x) }
func (x hookByWeight) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x hookByWeight) Less(i, j int) bool {
if x[i].Weight == x[j].Weight {
return x[i].Name < x[j].Name
}
return x[i].Weight < x[j].Weight
}
// deleteHookByPolicy deletes a hook if the hook policy instructs it to
func deleteHookByPolicy(cfg *Configuration, h *release.Hook, policy release.HookDeletePolicy) error {
if hookHasDeletePolicy(h, policy) {
b := bytes.NewBufferString(h.Manifest)
return cfg.KubeClient.Delete(b)
}
return nil
}
// hookHasDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices
// supported by helm. If so, mark the hook as one should be deleted.
func hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool {
for _, v := range h.DeletePolicies {
if policy == v {
return true
}
}
return false
}

@ -25,7 +25,6 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"sort"
"strings" "strings"
"text/template" "text/template"
"time" "time"
@ -40,7 +39,6 @@ import (
"helm.sh/helm/pkg/downloader" "helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/engine" "helm.sh/helm/pkg/engine"
"helm.sh/helm/pkg/getter" "helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/hooks"
kubefake "helm.sh/helm/pkg/kube/fake" kubefake "helm.sh/helm/pkg/kube/fake"
"helm.sh/helm/pkg/release" "helm.sh/helm/pkg/release"
"helm.sh/helm/pkg/releaseutil" "helm.sh/helm/pkg/releaseutil"
@ -198,7 +196,7 @@ func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) {
// pre-install hooks // pre-install hooks
if !i.DisableHooks { if !i.DisableHooks {
if err := i.execHook(rel.Hooks, hooks.PreInstall); err != nil { if err := i.execHook(rel.Hooks, release.HookPreInstall); err != nil {
return i.failRelease(rel, fmt.Errorf("failed pre-install: %s", err)) return i.failRelease(rel, fmt.Errorf("failed pre-install: %s", err))
} }
} }
@ -220,7 +218,7 @@ func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) {
} }
if !i.DisableHooks { if !i.DisableHooks {
if err := i.execHook(rel.Hooks, hooks.PostInstall); err != nil { if err := i.execHook(rel.Hooks, release.HookPostInstall); err != nil {
return i.failRelease(rel, fmt.Errorf("failed post-install: %s", err)) return i.failRelease(rel, fmt.Errorf("failed post-install: %s", err))
} }
} }
@ -466,86 +464,8 @@ func (i *Install) validateManifest(manifest io.Reader) error {
} }
// execHook executes all of the hooks for the given hook event. // execHook executes all of the hooks for the given hook event.
func (i *Install) execHook(hs []*release.Hook, hook string) error { func (i *Install) execHook(hs []*release.Hook, hook release.HookEvent) error {
executingHooks := []*release.Hook{} return i.cfg.execHook(hs, hook, i.Timeout)
for _, h := range hs {
for _, e := range h.Events {
if string(e) == hook {
executingHooks = append(executingHooks, h)
}
}
}
sort.Sort(hookByWeight(executingHooks))
for _, h := range executingHooks {
if err := deleteHookByPolicy(i.cfg, h, hooks.BeforeHookCreation); err != nil {
return err
}
b := bytes.NewBufferString(h.Manifest)
if err := i.cfg.KubeClient.Create(b); err != nil {
return errors.Wrapf(err, "warning: Release %s %s %s failed", i.ReleaseName, hook, h.Path)
}
b.Reset()
b.WriteString(h.Manifest)
if err := i.cfg.KubeClient.WatchUntilReady(b, i.Timeout); err != nil {
// If a hook is failed, checkout 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
if err := deleteHookByPolicy(i.cfg, h, hooks.HookFailed); err != nil {
return err
}
return err
}
}
// If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted
// under succeeded condition. If so, then clear the corresponding resource object in each hook
for _, h := range executingHooks {
if err := deleteHookByPolicy(i.cfg, h, hooks.HookSucceeded); err != nil {
return err
}
h.LastRun = time.Now()
}
return nil
}
// deletePolices represents a mapping between the key in the annotation for label deleting policy and its real meaning
// FIXME: Can we refactor this out?
var deletePolices = map[string]release.HookDeletePolicy{
hooks.HookSucceeded: release.HookSucceeded,
hooks.HookFailed: release.HookFailed,
hooks.BeforeHookCreation: release.HookBeforeHookCreation,
}
// hookHasDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices
// supported by helm. If so, mark the hook as one should be deleted.
func hookHasDeletePolicy(h *release.Hook, policy string) bool {
dp, ok := deletePolices[policy]
if !ok {
return false
}
for _, v := range h.DeletePolicies {
if dp == v {
return true
}
}
return false
}
// hookByWeight is a sorter for hooks
type hookByWeight []*release.Hook
func (x hookByWeight) Len() int { return len(x) }
func (x hookByWeight) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x hookByWeight) Less(i, j int) bool {
if x[i].Weight == x[j].Weight {
return x[i].Name < x[j].Name
}
return x[i].Weight < x[j].Weight
} }
// NameAndChart returns the name and chart that should be used. // NameAndChart returns the name and chart that should be used.

@ -22,7 +22,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/pkg/release" "helm.sh/helm/pkg/release"
reltesting "helm.sh/helm/pkg/releasetesting"
) )
// ReleaseTesting is the action for testing a release. // ReleaseTesting is the action for testing a release.
@ -43,52 +42,16 @@ 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) (<-chan *release.TestReleaseResponse, <-chan error) { func (r *ReleaseTesting) Run(name string) error {
errc := make(chan error, 1)
if err := validateReleaseName(name); err != nil { if err := validateReleaseName(name); err != nil {
errc <- errors.Errorf("releaseTest: Release name is invalid: %s", name) return errors.Errorf("releaseTest: Release name is invalid: %s", name)
return nil, errc
} }
// finds the non-deleted release with the given name // finds the non-deleted release with the given name
rel, err := r.cfg.Releases.Last(name) rel, err := r.cfg.Releases.Last(name)
if err != nil { if err != nil {
errc <- err return err
return nil, errc
} }
ch := make(chan *release.TestReleaseResponse, 1) return r.cfg.execHook(rel.Hooks, release.HookTest, r.Timeout)
testEnv := &reltesting.Environment{
Namespace: rel.Namespace,
KubeClient: r.cfg.KubeClient,
Timeout: r.Timeout,
Messages: ch,
}
r.cfg.Log("running tests for release %s", rel.Name)
tSuite := reltesting.NewTestSuite(rel)
go func() {
defer close(errc)
defer close(ch)
if err := tSuite.Run(testEnv); err != nil {
errc <- errors.Wrapf(err, "error running test suite for %s", rel.Name)
return
}
rel.Info.LastTestSuiteRun = &release.TestSuite{
StartedAt: tSuite.StartedAt,
CompletedAt: tSuite.CompletedAt,
Results: tSuite.Results,
}
if r.Cleanup {
testEnv.DeleteTestPods(tSuite.TestManifests)
}
if err := r.cfg.Releases.Update(rel); err != nil {
r.cfg.Log("test: Failed to store updated release: %s", err)
}
}()
return ch, errc
} }

@ -19,12 +19,10 @@ package action
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"sort"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/pkg/hooks"
"helm.sh/helm/pkg/release" "helm.sh/helm/pkg/release"
) )
@ -140,7 +138,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
// pre-rollback hooks // pre-rollback hooks
if !r.DisableHooks { if !r.DisableHooks {
if err := r.execHook(targetRelease.Hooks, hooks.PreRollback); err != nil { if err := r.execHook(targetRelease.Hooks, release.HookPreRollback); err != nil {
return targetRelease, err return targetRelease, err
} }
} else { } else {
@ -173,7 +171,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
// post-rollback hooks // post-rollback hooks
if !r.DisableHooks { if !r.DisableHooks {
if err := r.execHook(targetRelease.Hooks, hooks.PostRollback); err != nil { if err := r.execHook(targetRelease.Hooks, release.HookPostRollback); err != nil {
return targetRelease, err return targetRelease, err
} }
} }
@ -195,59 +193,6 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
} }
// execHook executes all of the hooks for the given hook event. // execHook executes all of the hooks for the given hook event.
func (r *Rollback) execHook(hs []*release.Hook, hook string) error { func (r *Rollback) execHook(hs []*release.Hook, hook release.HookEvent) error {
timeout := r.Timeout return r.cfg.execHook(hs, hook, r.Timeout)
executingHooks := []*release.Hook{}
for _, h := range hs {
for _, e := range h.Events {
if string(e) == hook {
executingHooks = append(executingHooks, h)
}
}
}
sort.Sort(hookByWeight(executingHooks))
for _, h := range executingHooks {
if err := deleteHookByPolicy(r.cfg, h, hooks.BeforeHookCreation); err != nil {
return err
}
b := bytes.NewBufferString(h.Manifest)
if err := r.cfg.KubeClient.Create(b); err != nil {
return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path)
}
b.Reset()
b.WriteString(h.Manifest)
if err := r.cfg.KubeClient.WatchUntilReady(b, timeout); err != nil {
// If a hook is failed, checkout 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
if err := deleteHookByPolicy(r.cfg, h, hooks.HookFailed); err != nil {
return err
}
return err
}
}
// If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted
// under succeeded condition. If so, then clear the corresponding resource object in each hook
for _, h := range executingHooks {
if err := deleteHookByPolicy(r.cfg, h, hooks.HookSucceeded); err != nil {
return err
}
h.LastRun = time.Now()
}
return nil
}
// deleteHookByPolicy deletes a hook if the hook policy instructs it to
func deleteHookByPolicy(cfg *Configuration, h *release.Hook, policy string) error {
if hookHasDeletePolicy(h, policy) {
b := bytes.NewBufferString(h.Manifest)
return cfg.KubeClient.Delete(b)
}
return nil
} }

@ -18,13 +18,11 @@ package action
import ( import (
"bytes" "bytes"
"sort"
"strings" "strings"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/pkg/hooks"
"helm.sh/helm/pkg/kube" "helm.sh/helm/pkg/kube"
"helm.sh/helm/pkg/release" "helm.sh/helm/pkg/release"
"helm.sh/helm/pkg/releaseutil" "helm.sh/helm/pkg/releaseutil"
@ -94,7 +92,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
res := &release.UninstallReleaseResponse{Release: rel} res := &release.UninstallReleaseResponse{Release: rel}
if !u.DisableHooks { if !u.DisableHooks {
if err := u.execHook(rel.Hooks, hooks.PreDelete); err != nil { if err := u.execHook(rel.Hooks, release.HookPreDelete); err != nil {
return res, err return res, err
} }
} else { } else {
@ -111,7 +109,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
res.Info = kept res.Info = kept
if !u.DisableHooks { if !u.DisableHooks {
if err := u.execHook(rel.Hooks, hooks.PostDelete); err != nil { if err := u.execHook(rel.Hooks, release.HookPostDelete); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
} }
@ -162,51 +160,8 @@ func joinErrors(errs []error) string {
} }
// execHook executes all of the hooks for the given hook event. // execHook executes all of the hooks for the given hook event.
func (u *Uninstall) execHook(hs []*release.Hook, hook string) error { func (u *Uninstall) execHook(hs []*release.Hook, hook release.HookEvent) error {
executingHooks := []*release.Hook{} return u.cfg.execHook(hs, hook, u.Timeout)
for _, h := range hs {
for _, e := range h.Events {
if string(e) == hook {
executingHooks = append(executingHooks, h)
}
}
}
sort.Sort(hookByWeight(executingHooks))
for _, h := range executingHooks {
if err := deleteHookByPolicy(u.cfg, h, hooks.BeforeHookCreation); err != nil {
return err
}
b := bytes.NewBufferString(h.Manifest)
if err := u.cfg.KubeClient.Create(b); err != nil {
return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path)
}
b.Reset()
b.WriteString(h.Manifest)
if err := u.cfg.KubeClient.WatchUntilReady(b, u.Timeout); err != nil {
// If a hook is failed, checkout 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
if err := deleteHookByPolicy(u.cfg, h, hooks.HookFailed); err != nil {
return err
}
return err
}
}
// If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted
// under succeeded condition. If so, then clear the corresponding resource object in each hook
for _, h := range executingHooks {
if err := deleteHookByPolicy(u.cfg, h, hooks.HookSucceeded); err != nil {
return err
}
h.LastRun = time.Now()
}
return nil
} }
// deleteRelease deletes the release and returns manifests that were kept in the deletion process // deleteRelease deletes the release and returns manifests that were kept in the deletion process

@ -19,14 +19,12 @@ package action
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"sort"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/pkg/chart" "helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chartutil" "helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/hooks"
"helm.sh/helm/pkg/kube" "helm.sh/helm/pkg/kube"
"helm.sh/helm/pkg/release" "helm.sh/helm/pkg/release"
"helm.sh/helm/pkg/releaseutil" "helm.sh/helm/pkg/releaseutil"
@ -201,7 +199,7 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea
// pre-upgrade hooks // pre-upgrade hooks
if !u.DisableHooks { if !u.DisableHooks {
if err := u.execHook(upgradedRelease.Hooks, hooks.PreUpgrade); err != nil { if err := u.execHook(upgradedRelease.Hooks, release.HookPreUpgrade); err != nil {
return u.failRelease(upgradedRelease, fmt.Errorf("pre-upgrade hooks failed: %s", err)) return u.failRelease(upgradedRelease, fmt.Errorf("pre-upgrade hooks failed: %s", err))
} }
} else { } else {
@ -222,7 +220,7 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea
// post-upgrade hooks // post-upgrade hooks
if !u.DisableHooks { if !u.DisableHooks {
if err := u.execHook(upgradedRelease.Hooks, hooks.PostUpgrade); err != nil { if err := u.execHook(upgradedRelease.Hooks, release.HookPostUpgrade); err != nil {
return u.failRelease(upgradedRelease, fmt.Errorf("post-upgrade hooks failed: %s", err)) return u.failRelease(upgradedRelease, fmt.Errorf("post-upgrade hooks failed: %s", err))
} }
} }
@ -335,49 +333,6 @@ func validateManifest(c kube.Interface, manifest []byte) error {
} }
// execHook executes all of the hooks for the given hook event. // execHook executes all of the hooks for the given hook event.
func (u *Upgrade) execHook(hs []*release.Hook, hook string) error { func (u *Upgrade) execHook(hs []*release.Hook, hook release.HookEvent) error {
timeout := u.Timeout return u.cfg.execHook(hs, hook, u.Timeout)
executingHooks := []*release.Hook{}
for _, h := range hs {
for _, e := range h.Events {
if string(e) == hook {
executingHooks = append(executingHooks, h)
}
}
}
sort.Sort(hookByWeight(executingHooks))
for _, h := range executingHooks {
if err := deleteHookByPolicy(u.cfg, h, hooks.BeforeHookCreation); err != nil {
return err
}
b := bytes.NewBufferString(h.Manifest)
if err := u.cfg.KubeClient.Create(b); err != nil {
return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path)
}
b.Reset()
b.WriteString(h.Manifest)
if err := u.cfg.KubeClient.WatchUntilReady(b, timeout); err != nil {
// If a hook is failed, checkout 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
if err := deleteHookByPolicy(u.cfg, h, hooks.HookFailed); err != nil {
return err
}
return err
}
}
// If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted
// under succeeded condition. If so, then clear the corresponding resource object in each hook
for _, h := range executingHooks {
if err := deleteHookByPolicy(u.cfg, h, hooks.HookSucceeded); err != nil {
return err
}
h.LastRun = time.Now()
}
return nil
} }

@ -1,67 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package hooks
import (
"helm.sh/helm/pkg/release"
)
// HookAnno is the label name for a hook
const HookAnno = "helm.sh/hook"
// HookWeightAnno is the label name for a hook weight
const HookWeightAnno = "helm.sh/hook-weight"
// HookDeleteAnno is the label name for the delete policy for a hook
const HookDeleteAnno = "helm.sh/hook-delete-policy"
// Types of hooks
const (
PreInstall = "pre-install"
PostInstall = "post-install"
PreDelete = "pre-delete"
PostDelete = "post-delete"
PreUpgrade = "pre-upgrade"
PostUpgrade = "post-upgrade"
PreRollback = "pre-rollback"
PostRollback = "post-rollback"
ReleaseTestSuccess = "test-success"
ReleaseTestFailure = "test-failure"
)
// Type of policy for deleting the hook
const (
HookSucceeded = "hook-succeeded"
HookFailed = "hook-failed"
BeforeHookCreation = "before-hook-creation"
)
// FilterTestHooks filters the list of hooks are returns only testing hooks.
func FilterTestHooks(hooks []*release.Hook) []*release.Hook {
testHooks := []*release.Hook{}
for _, h := range hooks {
for _, e := range h.Events {
if e == release.HookReleaseTestSuccess || e == release.HookReleaseTestFailure {
testHooks = append(testHooks, h)
continue
}
}
}
return testHooks
}

@ -19,7 +19,6 @@ package kube // import "helm.sh/helm/pkg/kube"
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"log" "log"
"strings" "strings"
@ -487,29 +486,3 @@ func scrubValidationError(err error) error {
} }
return err return err
} }
// WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase
// and returns said phase (PodSucceeded or PodFailed qualify).
func (c *Client) WaitAndGetCompletedPodPhase(name string, timeout time.Duration) (v1.PodPhase, error) {
client, _ := c.KubernetesClientSet()
to := int64(timeout)
watcher, err := client.CoreV1().Pods(c.namespace()).Watch(metav1.ListOptions{
FieldSelector: fmt.Sprintf("metadata.name=%s", name),
TimeoutSeconds: &to,
})
for event := range watcher.ResultChan() {
p, ok := event.Object.(*v1.Pod)
if !ok {
return v1.PodUnknown, fmt.Errorf("%s not a pod", name)
}
switch p.Status.Phase {
case v1.PodFailed:
return v1.PodFailed, nil
case v1.PodSucceeded:
return v1.PodSucceeded, nil
}
}
return v1.PodUnknown, err
}

@ -21,7 +21,6 @@ import (
"io" "io"
"time" "time"
v1 "k8s.io/api/core/v1"
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/pkg/kube" "helm.sh/helm/pkg/kube"
@ -32,15 +31,14 @@ import (
// delegates all its calls to `PrintingKubeClient` // delegates all its calls to `PrintingKubeClient`
type FailingKubeClient struct { type FailingKubeClient struct {
PrintingKubeClient PrintingKubeClient
CreateError error DeleteError error
WaitError error WatchUntilReadyError error
GetError error UpdateError error
DeleteError error BuildError error
WatchUntilReadyError error BuildUnstructuredError error
UpdateError error CreateError error
BuildError error WaitError error
BuildUnstructuredError error GetError error
WaitAndGetCompletedPodPhaseError error
} }
// Create returns the configured error if set or prints // Create returns the configured error if set or prints
@ -106,11 +104,3 @@ func (f *FailingKubeClient) BuildUnstructured(r io.Reader) (kube.Result, error)
} }
return f.PrintingKubeClient.Build(r) return f.PrintingKubeClient.Build(r)
} }
// WaitAndGetCompletedPodPhase returns the configured error if set or prints
func (f *FailingKubeClient) WaitAndGetCompletedPodPhase(s string, d time.Duration) (v1.PodPhase, error) {
if f.WaitAndGetCompletedPodPhaseError != nil {
return v1.PodSucceeded, f.WaitAndGetCompletedPodPhaseError
}
return f.PrintingKubeClient.WaitAndGetCompletedPodPhase(s, d)
}

@ -20,7 +20,6 @@ import (
"io" "io"
"time" "time"
v1 "k8s.io/api/core/v1"
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/pkg/kube" "helm.sh/helm/pkg/kube"
@ -77,8 +76,3 @@ func (p *PrintingKubeClient) Build(_ io.Reader) (kube.Result, error) {
func (p *PrintingKubeClient) BuildUnstructured(_ io.Reader) (kube.Result, error) { func (p *PrintingKubeClient) BuildUnstructured(_ io.Reader) (kube.Result, error) {
return p.Build(nil) return p.Build(nil)
} }
// WaitAndGetCompletedPodPhase implements KubeClient WaitAndGetCompletedPodPhase.
func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(_ string, _ time.Duration) (v1.PodPhase, error) {
return v1.PodSucceeded, nil
}

@ -19,8 +19,6 @@ package kube
import ( import (
"io" "io"
"time" "time"
v1 "k8s.io/api/core/v1"
) )
// KubernetesClient represents a client capable of communicating with the Kubernetes API. // KubernetesClient represents a client capable of communicating with the Kubernetes API.
@ -57,10 +55,6 @@ type Interface interface {
Build(reader io.Reader) (Result, error) Build(reader io.Reader) (Result, error)
BuildUnstructured(reader io.Reader) (Result, error) BuildUnstructured(reader io.Reader) (Result, error)
// WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase
// and returns said phase (PodSucceeded or PodFailed qualify).
WaitAndGetCompletedPodPhase(name string, timeout time.Duration) (v1.PodPhase, error)
} }
var _ Interface = (*Client)(nil) var _ Interface = (*Client)(nil)

@ -1,10 +1,11 @@
/* /*
Copyright The Helm Authors. Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -15,23 +16,24 @@ limitations under the License.
package release package release
import "time" import (
"time"
)
// HookEvent specifies the hook event // HookEvent specifies the hook event
type HookEvent string type HookEvent string
// Hook event types // Hook event types
const ( const (
HookPreInstall HookEvent = "pre-install" HookPreInstall HookEvent = "pre-install"
HookPostInstall HookEvent = "post-install" HookPostInstall HookEvent = "post-install"
HookPreDelete HookEvent = "pre-delete" HookPreDelete HookEvent = "pre-delete"
HookPostDelete HookEvent = "post-delete" HookPostDelete HookEvent = "post-delete"
HookPreUpgrade HookEvent = "pre-upgrade" HookPreUpgrade HookEvent = "pre-upgrade"
HookPostUpgrade HookEvent = "post-upgrade" HookPostUpgrade HookEvent = "post-upgrade"
HookPreRollback HookEvent = "pre-rollback" HookPreRollback HookEvent = "pre-rollback"
HookPostRollback HookEvent = "post-rollback" HookPostRollback HookEvent = "post-rollback"
HookReleaseTestSuccess HookEvent = "release-test-success" HookTest HookEvent = "test"
HookReleaseTestFailure HookEvent = "release-test-failure"
) )
func (x HookEvent) String() string { return string(x) } func (x HookEvent) String() string { return string(x) }
@ -41,13 +43,22 @@ type HookDeletePolicy string
// Hook delete policy types // Hook delete policy types
const ( const (
HookSucceeded HookDeletePolicy = "succeeded" HookSucceeded HookDeletePolicy = "hook-succeeded"
HookFailed HookDeletePolicy = "failed" HookFailed HookDeletePolicy = "hook-failed"
HookBeforeHookCreation HookDeletePolicy = "before-hook-creation" HookBeforeHookCreation HookDeletePolicy = "before-hook-creation"
) )
func (x HookDeletePolicy) String() string { return string(x) } func (x HookDeletePolicy) String() string { return string(x) }
// HookAnnotation is the label name for a hook
const HookAnnotation = "helm.sh/hook"
// HookWeightAnnotation is the label name for a hook weight
const HookWeightAnnotation = "helm.sh/hook-weight"
// HookDeleteAnnotation is the label name for the delete policy for a hook
const HookDeleteAnnotation = "helm.sh/hook-delete-policy"
// Hook defines a hook object. // Hook defines a hook object.
type Hook struct { type Hook struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`

@ -1,120 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package releasetesting
import (
"bytes"
"fmt"
"log"
"time"
v1 "k8s.io/api/core/v1"
"helm.sh/helm/pkg/kube"
"helm.sh/helm/pkg/release"
)
// Environment encapsulates information about where test suite executes and returns results
type Environment struct {
Namespace string
KubeClient kube.Interface
Messages chan *release.TestReleaseResponse
Timeout time.Duration
}
func (env *Environment) createTestPod(test *test) error {
b := bytes.NewBufferString(test.manifest)
if err := env.KubeClient.Create(b); err != nil {
test.result.Info = err.Error()
test.result.Status = release.TestRunFailure
return err
}
return nil
}
func (env *Environment) getTestPodStatus(test *test) (v1.PodPhase, error) {
status, err := env.KubeClient.WaitAndGetCompletedPodPhase(test.name, env.Timeout)
if err != nil {
log.Printf("Error getting status for pod %s: %s", test.result.Name, err)
test.result.Info = err.Error()
test.result.Status = release.TestRunUnknown
return status, err
}
return status, err
}
func (env *Environment) streamResult(r *release.TestRun) error {
switch r.Status {
case release.TestRunSuccess:
if err := env.streamSuccess(r.Name); err != nil {
return err
}
case release.TestRunFailure:
if err := env.streamFailed(r.Name); err != nil {
return err
}
default:
if err := env.streamUnknown(r.Name, r.Info); err != nil {
return err
}
}
return nil
}
func (env *Environment) streamRunning(name string) error {
msg := "RUNNING: " + name
return env.streamMessage(msg, release.TestRunRunning)
}
func (env *Environment) streamError(info string) error {
msg := "ERROR: " + info
return env.streamMessage(msg, release.TestRunFailure)
}
func (env *Environment) streamFailed(name string) error {
msg := fmt.Sprintf("FAILED: %s, run `kubectl logs %s --namespace %s` for more info", name, name, env.Namespace)
return env.streamMessage(msg, release.TestRunFailure)
}
func (env *Environment) streamSuccess(name string) error {
msg := fmt.Sprintf("PASSED: %s", name)
return env.streamMessage(msg, release.TestRunSuccess)
}
func (env *Environment) streamUnknown(name, info string) error {
msg := fmt.Sprintf("UNKNOWN: %s: %s", name, info)
return env.streamMessage(msg, release.TestRunUnknown)
}
func (env *Environment) streamMessage(msg string, status release.TestRunStatus) error {
resp := &release.TestReleaseResponse{Msg: msg, Status: status}
env.Messages <- resp
return nil
}
// DeleteTestPods deletes resources given in testManifests
func (env *Environment) DeleteTestPods(testManifests []string) {
for _, testManifest := range testManifests {
err := env.KubeClient.Delete(bytes.NewBufferString(testManifest))
if err != nil {
env.streamError(err.Error())
}
}
}

@ -1,71 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package releasetesting
import (
"testing"
"github.com/pkg/errors"
"helm.sh/helm/pkg/release"
)
func TestCreateTestPodSuccess(t *testing.T) {
env := testEnvFixture()
test := testFixture()
if err := env.createTestPod(test); err != nil {
t.Errorf("Expected no error, got an error: %s", err)
}
}
func TestCreateTestPodFailure(t *testing.T) {
env := testEnvFixture()
env.KubeClient = &mockKubeClient{
err: errors.New("We ran out of budget and couldn't create finding-nemo"),
}
test := testFixture()
if err := env.createTestPod(test); err == nil {
t.Errorf("Expected error, got no error")
}
if test.result.Info == "" {
t.Errorf("Expected error to be saved in test result info but found empty string")
}
if test.result.Status != release.TestRunFailure {
t.Errorf("Expected test result status to be failure but got: %v", test.result.Status)
}
}
func TestStreamMessage(t *testing.T) {
env := testEnvFixture()
defer close(env.Messages)
expectedMessage := "testing streamMessage"
expectedStatus := release.TestRunSuccess
if err := env.streamMessage(expectedMessage, expectedStatus); err != nil {
t.Errorf("Expected no errors, got: %s", err)
}
got := <-env.Messages
if got.Msg != expectedMessage {
t.Errorf("Expected message: %s, got: %s", expectedMessage, got.Msg)
}
if got.Status != expectedStatus {
t.Errorf("Expected status: %v, got: %v", expectedStatus, got.Status)
}
}

@ -1,187 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package releasetesting
import (
"strings"
"time"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
"helm.sh/helm/pkg/hooks"
"helm.sh/helm/pkg/release"
util "helm.sh/helm/pkg/releaseutil"
)
// TestSuite what tests are run, results, and metadata
type TestSuite struct {
StartedAt time.Time
CompletedAt time.Time
TestManifests []string
Results []*release.TestRun
}
type test struct {
name string
manifest string
expectedSuccess bool
result *release.TestRun
}
// NewTestSuite takes a release object and returns a TestSuite object with test definitions
// extracted from the release
func NewTestSuite(rel *release.Release) *TestSuite {
return &TestSuite{
TestManifests: extractTestManifestsFromHooks(rel.Hooks),
Results: []*release.TestRun{},
}
}
// Run executes tests in a test suite and stores a result within a given environment
func (ts *TestSuite) Run(env *Environment) error {
ts.StartedAt = time.Now()
if len(ts.TestManifests) == 0 {
// TODO: make this better, adding test run status on test suite is weird
env.streamMessage("No Tests Found", release.TestRunUnknown)
}
for _, testManifest := range ts.TestManifests {
test, err := newTest(testManifest)
if err != nil {
return err
}
test.result.StartedAt = time.Now()
if err := env.streamRunning(test.name); err != nil {
return err
}
test.result.Status = release.TestRunRunning
resourceCreated := true
if err := env.createTestPod(test); err != nil {
resourceCreated = false
if streamErr := env.streamError(test.result.Info); streamErr != nil {
return err
}
}
resourceCleanExit := true
status := v1.PodUnknown
if resourceCreated {
status, err = env.getTestPodStatus(test)
if err != nil {
resourceCleanExit = false
if streamErr := env.streamError(test.result.Info); streamErr != nil {
return streamErr
}
}
}
if resourceCreated && resourceCleanExit {
if err := test.assignTestResult(status); err != nil {
return err
}
if err := env.streamResult(test.result); err != nil {
return err
}
}
test.result.CompletedAt = time.Now()
ts.Results = append(ts.Results, test.result)
}
ts.CompletedAt = time.Now()
return nil
}
func (t *test) assignTestResult(podStatus v1.PodPhase) error {
switch podStatus {
case v1.PodSucceeded:
if t.expectedSuccess {
t.result.Status = release.TestRunSuccess
} else {
t.result.Status = release.TestRunFailure
}
case v1.PodFailed:
if !t.expectedSuccess {
t.result.Status = release.TestRunSuccess
} else {
t.result.Status = release.TestRunFailure
}
default:
t.result.Status = release.TestRunUnknown
}
return nil
}
func expectedSuccess(hookTypes []string) (bool, error) {
for _, hookType := range hookTypes {
hookType = strings.ToLower(strings.TrimSpace(hookType))
if hookType == hooks.ReleaseTestSuccess {
return true, nil
} else if hookType == hooks.ReleaseTestFailure {
return false, nil
}
}
return false, errors.Errorf("no %s or %s hook found", hooks.ReleaseTestSuccess, hooks.ReleaseTestFailure)
}
func extractTestManifestsFromHooks(h []*release.Hook) []string {
testHooks := hooks.FilterTestHooks(h)
tests := []string{}
for _, h := range testHooks {
individualTests := util.SplitManifests(h.Manifest)
for _, t := range individualTests {
tests = append(tests, t)
}
}
return tests
}
func newTest(testManifest string) (*test, error) {
var sh util.SimpleHead
err := yaml.Unmarshal([]byte(testManifest), &sh)
if err != nil {
return nil, err
}
if sh.Kind != "Pod" {
return nil, errors.Errorf("%s is not a pod", sh.Metadata.Name)
}
hookTypes := sh.Metadata.Annotations[hooks.HookAnno]
expected, err := expectedSuccess(strings.Split(hookTypes, ","))
if err != nil {
return nil, err
}
name := strings.TrimSuffix(sh.Metadata.Name, ",")
return &test{
name: name,
manifest: testManifest,
expectedSuccess: expected,
result: &release.TestRun{
Name: name,
},
}, nil
}

@ -1,259 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package releasetesting
import (
"io"
"testing"
"time"
v1 "k8s.io/api/core/v1"
"helm.sh/helm/pkg/kube"
"helm.sh/helm/pkg/release"
)
const manifestWithTestSuccessHook = `
apiVersion: v1
kind: Pod
metadata:
name: finding-nemo,
annotations:
"helm.sh/hook": test-success
spec:
containers:
- name: nemo-test
image: fake-image
cmd: fake-command
`
const manifestWithTestFailureHook = `
apiVersion: v1
kind: Pod
metadata:
name: gold-rush,
annotations:
"helm.sh/hook": test-failure
spec:
containers:
- name: gold-finding-test
image: fake-gold-finding-image
cmd: fake-gold-finding-command
`
const manifestWithInstallHooks = `apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
annotations:
"helm.sh/hook": post-install,pre-delete
data:
name: value
`
func TestRun(t *testing.T) {
testManifests := []string{manifestWithTestSuccessHook, manifestWithTestFailureHook}
ts := testSuiteFixture(testManifests)
env := testEnvFixture()
go func() {
defer close(env.Messages)
if err := ts.Run(env); err != nil {
t.Error(err)
}
}()
for i := 0; i <= 4; i++ {
<-env.Messages
}
if _, ok := <-env.Messages; ok {
t.Errorf("Expected 4 messages streamed")
}
if ts.StartedAt.IsZero() {
t.Errorf("Expected StartedAt to not be nil. Got: %v", ts.StartedAt)
}
if ts.CompletedAt.IsZero() {
t.Errorf("Expected CompletedAt to not be nil. Got: %v", ts.CompletedAt)
}
if len(ts.Results) != 2 {
t.Errorf("Expected 2 test result. Got %v", len(ts.Results))
}
result := ts.Results[0]
if result.StartedAt.IsZero() {
t.Errorf("Expected test StartedAt to not be nil. Got: %v", result.StartedAt)
}
if result.CompletedAt.IsZero() {
t.Errorf("Expected test CompletedAt to not be nil. Got: %v", result.CompletedAt)
}
if result.Name != "finding-nemo" {
t.Errorf("Expected test name to be finding-nemo. Got: %v", result.Name)
}
if result.Status != release.TestRunSuccess {
t.Errorf("Expected test result to be successful, got: %v", result.Status)
}
result2 := ts.Results[1]
if result2.StartedAt.IsZero() {
t.Errorf("Expected test StartedAt to not be nil. Got: %v", result2.StartedAt)
}
if result2.CompletedAt.IsZero() {
t.Errorf("Expected test CompletedAt to not be nil. Got: %v", result2.CompletedAt)
}
if result2.Name != "gold-rush" {
t.Errorf("Expected test name to be gold-rush, Got: %v", result2.Name)
}
if result2.Status != release.TestRunFailure {
t.Errorf("Expected test result to be successful, got: %v", result2.Status)
}
}
func TestRunEmptyTestSuite(t *testing.T) {
ts := testSuiteFixture([]string{})
env := testEnvFixture()
go func() {
defer close(env.Messages)
if err := ts.Run(env); err != nil {
t.Error(err)
}
}()
msg := <-env.Messages
if msg.Msg != "No Tests Found" {
t.Errorf("Expected message 'No Tests Found', Got: %v", msg.Msg)
}
for range env.Messages {
}
if ts.StartedAt.IsZero() {
t.Errorf("Expected StartedAt to not be nil. Got: %v", ts.StartedAt)
}
if ts.CompletedAt.IsZero() {
t.Errorf("Expected CompletedAt to not be nil. Got: %v", ts.CompletedAt)
}
if len(ts.Results) != 0 {
t.Errorf("Expected 0 test result. Got %v", len(ts.Results))
}
}
func TestRunSuccessWithTestFailureHook(t *testing.T) {
ts := testSuiteFixture([]string{manifestWithTestFailureHook})
env := testEnvFixture()
env.KubeClient = &mockKubeClient{podFail: true}
go func() {
defer close(env.Messages)
if err := ts.Run(env); err != nil {
t.Error(err)
}
}()
for i := 0; i <= 4; i++ {
<-env.Messages
}
if _, ok := <-env.Messages; ok {
t.Errorf("Expected 4 messages streamed")
}
if ts.StartedAt.IsZero() {
t.Errorf("Expected StartedAt to not be nil. Got: %v", ts.StartedAt)
}
if ts.CompletedAt.IsZero() {
t.Errorf("Expected CompletedAt to not be nil. Got: %v", ts.CompletedAt)
}
if len(ts.Results) != 1 {
t.Errorf("Expected 1 test result. Got %v", len(ts.Results))
}
result := ts.Results[0]
if result.StartedAt.IsZero() {
t.Errorf("Expected test StartedAt to not be nil. Got: %v", result.StartedAt)
}
if result.CompletedAt.IsZero() {
t.Errorf("Expected test CompletedAt to not be nil. Got: %v", result.CompletedAt)
}
if result.Name != "gold-rush" {
t.Errorf("Expected test name to be gold-rush, Got: %v", result.Name)
}
if result.Status != release.TestRunSuccess {
t.Errorf("Expected test result to be successful, got: %v", result.Status)
}
}
func TestExtractTestManifestsFromHooks(t *testing.T) {
testManifests := extractTestManifestsFromHooks(hooksStub)
if len(testManifests) != 1 {
t.Errorf("Expected 1 test manifest, Got: %v", len(testManifests))
}
}
var hooksStub = []*release.Hook{
{
Manifest: manifestWithTestSuccessHook,
Events: []release.HookEvent{
release.HookReleaseTestSuccess,
},
},
{
Manifest: manifestWithInstallHooks,
Events: []release.HookEvent{
release.HookPostInstall,
},
},
}
func testFixture() *test {
return &test{
manifest: manifestWithTestSuccessHook,
result: &release.TestRun{},
}
}
func testSuiteFixture(testManifests []string) *TestSuite {
testResults := []*release.TestRun{}
ts := &TestSuite{
TestManifests: testManifests,
Results: testResults,
}
return ts
}
func testEnvFixture() *Environment {
return &Environment{
Namespace: "default",
KubeClient: &mockKubeClient{},
Timeout: 1,
Messages: make(chan *release.TestReleaseResponse, 1),
}
}
type mockKubeClient struct {
kube.Interface
podFail bool
err error
}
func (c *mockKubeClient) WaitAndGetCompletedPodPhase(_ string, _ time.Duration) (v1.PodPhase, error) {
if c.podFail {
return v1.PodFailed, nil
}
return v1.PodSucceeded, nil
}
func (c *mockKubeClient) Create(_ io.Reader) error { return c.err }
func (c *mockKubeClient) Delete(_ io.Reader) error { return nil }

@ -26,7 +26,6 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/pkg/chartutil" "helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/hooks"
"helm.sh/helm/pkg/release" "helm.sh/helm/pkg/release"
) )
@ -53,16 +52,16 @@ type result struct {
// TODO: Refactor this out. It's here because naming conventions were not followed through. // TODO: Refactor this out. It's here because naming conventions were not followed through.
// So fix the Test hook names and then remove this. // So fix the Test hook names and then remove this.
var events = map[string]release.HookEvent{ var events = map[string]release.HookEvent{
hooks.PreInstall: release.HookPreInstall, release.HookPreInstall.String(): release.HookPreInstall,
hooks.PostInstall: release.HookPostInstall, release.HookPostInstall.String(): release.HookPostInstall,
hooks.PreDelete: release.HookPreDelete, release.HookPreDelete.String(): release.HookPreDelete,
hooks.PostDelete: release.HookPostDelete, release.HookPostDelete.String(): release.HookPostDelete,
hooks.PreUpgrade: release.HookPreUpgrade, release.HookPreUpgrade.String(): release.HookPreUpgrade,
hooks.PostUpgrade: release.HookPostUpgrade, release.HookPostUpgrade.String(): release.HookPostUpgrade,
hooks.PreRollback: release.HookPreRollback, release.HookPreRollback.String(): release.HookPreRollback,
hooks.PostRollback: release.HookPostRollback, release.HookPostRollback.String(): release.HookPostRollback,
hooks.ReleaseTestSuccess: release.HookReleaseTestSuccess, release.HookTest.String(): release.HookTest,
hooks.ReleaseTestFailure: release.HookReleaseTestFailure, "test-success": release.HookTest,
} }
// SortManifests takes a map of filename/YAML contents, splits the file // SortManifests takes a map of filename/YAML contents, splits the file
@ -142,7 +141,7 @@ func (file *manifestFile) sort(result *result) error {
continue continue
} }
hookTypes, ok := entry.Metadata.Annotations[hooks.HookAnno] hookTypes, ok := entry.Metadata.Annotations[release.HookAnnotation]
if !ok { if !ok {
result.generic = append(result.generic, Manifest{ result.generic = append(result.generic, Manifest{
Name: file.path, Name: file.path,
@ -182,7 +181,7 @@ func (file *manifestFile) sort(result *result) error {
result.hooks = append(result.hooks, h) result.hooks = append(result.hooks, h)
operateAnnotationValues(entry, hooks.HookDeleteAnno, func(value string) { operateAnnotationValues(entry, release.HookDeleteAnnotation, func(value string) {
h.DeletePolicies = append(h.DeletePolicies, release.HookDeletePolicy(value)) h.DeletePolicies = append(h.DeletePolicies, release.HookDeletePolicy(value))
}) })
} }
@ -201,7 +200,7 @@ func hasAnyAnnotation(entry SimpleHead) bool {
// //
// If no weight is found, the assigned weight is 0 // If no weight is found, the assigned weight is 0
func calculateHookWeight(entry SimpleHead) int { func calculateHookWeight(entry SimpleHead) int {
hws := entry.Metadata.Annotations[hooks.HookWeightAnno] hws := entry.Metadata.Annotations[release.HookWeightAnnotation]
hw, err := strconv.Atoi(hws) hw, err := strconv.Atoi(hws)
if err != nil { if err != nil {
hw = 0 hw = 0

@ -116,7 +116,7 @@ metadata:
name: []string{"eighth", "example-test"}, name: []string{"eighth", "example-test"},
path: "eight", path: "eight",
kind: []string{"ConfigMap", "Pod"}, kind: []string{"ConfigMap", "Pod"},
hooks: map[string][]release.HookEvent{"eighth": nil, "example-test": {release.HookReleaseTestSuccess}}, hooks: map[string][]release.HookEvent{"eighth": nil, "example-test": {release.HookTest}},
manifest: `kind: ConfigMap manifest: `kind: ConfigMap
apiVersion: v1 apiVersion: v1
metadata: metadata:
@ -129,7 +129,7 @@ kind: Pod
metadata: metadata:
name: example-test name: example-test
annotations: annotations:
"helm.sh/hook": test-success "helm.sh/hook": test
`, `,
}, },
} }

@ -29,7 +29,7 @@ kind: Pod
metadata: metadata:
name: finding-nemo, name: finding-nemo,
annotations: annotations:
"helm.sh/hook": test-success "helm.sh/hook": test
spec: spec:
containers: containers:
- name: nemo-test - name: nemo-test
@ -42,7 +42,7 @@ kind: Pod
metadata: metadata:
name: finding-nemo, name: finding-nemo,
annotations: annotations:
"helm.sh/hook": test-success "helm.sh/hook": test
spec: spec:
containers: containers:
- name: nemo-test - name: nemo-test

Loading…
Cancel
Save