mirror of https://github.com/helm/helm
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
parent
533a369464
commit
72127c391c
@ -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
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
@ -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 }
|
|
Loading…
Reference in new issue