Commit d46d63a8 authored by Michelle Noorali's avatar Michelle Noorali

feat(*): add helm test command mvp

* This is a simple mvp which processes a test definition with the
hook annotation for test when you run `helm test [release]`
* helm client cmd, proto def, tiller logic
parent 8d75b0c0
...@@ -32,6 +32,7 @@ message Hook { ...@@ -32,6 +32,7 @@ message Hook {
POST_UPGRADE = 6; POST_UPGRADE = 6;
PRE_ROLLBACK = 7; PRE_ROLLBACK = 7;
POST_ROLLBACK = 8; POST_ROLLBACK = 8;
RELEASE_TEST = 9;
} }
string name = 1; string name = 1;
// Kind is the Kubernetes kind. // Kind is the Kubernetes kind.
......
...@@ -18,6 +18,7 @@ package hapi.release; ...@@ -18,6 +18,7 @@ package hapi.release;
import "hapi/release/hook.proto"; import "hapi/release/hook.proto";
import "hapi/release/info.proto"; import "hapi/release/info.proto";
import "hapi/release/test_suite.proto";
import "hapi/chart/config.proto"; import "hapi/chart/config.proto";
import "hapi/chart/chart.proto"; import "hapi/chart/chart.proto";
...@@ -50,4 +51,7 @@ message Release { ...@@ -50,4 +51,7 @@ message Release {
// Namespace is the kubernetes namespace of the release. // Namespace is the kubernetes namespace of the release.
string namespace = 8; string namespace = 8;
// TestSuite provides results on the last test run on a release
hapi.release.TestSuite test_suite = 9;
} }
// Copyright 2016 The Kubernetes Authors All rights reserved.
//
// 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.
syntax = "proto3";
package hapi.release;
import "google/protobuf/timestamp.proto";
option go_package = "release";
message TestResult {
enum Status {
UNKNOWN = 0;
SUCCESS = 1;
FAILURE = 2;
}
string name = 1;
Status status = 2;
string info = 3;
google.protobuf.Timestamp last_run = 4;
}
// Copyright 2016 The Kubernetes Authors All rights reserved.
//
// 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.
syntax = "proto3";
package hapi.release;
import "google/protobuf/timestamp.proto";
import "hapi/release/test_result.proto";
option go_package = "release";
// TestSuite comprises of the last run of the pre-defined test suite of a release version
message TestSuite {
// LastRun indicates the date/time this test was last run.
google.protobuf.Timestamp last_run = 1;
// Results are the results of each segment of the test
repeated hapi.release.TestResult results = 2;
}
...@@ -22,6 +22,7 @@ import "hapi/release/release.proto"; ...@@ -22,6 +22,7 @@ import "hapi/release/release.proto";
import "hapi/release/info.proto"; import "hapi/release/info.proto";
import "hapi/release/status.proto"; import "hapi/release/status.proto";
import "hapi/version/version.proto"; import "hapi/version/version.proto";
import "hapi/release/test_suite.proto";
option go_package = "services"; option go_package = "services";
...@@ -78,6 +79,11 @@ service ReleaseService { ...@@ -78,6 +79,11 @@ service ReleaseService {
// ReleaseHistory retrieves a releasse's history. // ReleaseHistory retrieves a releasse's history.
rpc GetHistory(GetHistoryRequest) returns (GetHistoryResponse) { rpc GetHistory(GetHistoryRequest) returns (GetHistoryResponse) {
} }
//TODO: move this to a test release service or rename to RunReleaseTest
// TestRelease runs the tests for a given release
rpc RunReleaseTest(TestReleaseRequest) returns (TestReleaseResponse) {
}
} }
// ListReleasesRequest requests a list of releases. // ListReleasesRequest requests a list of releases.
...@@ -304,3 +310,17 @@ message GetHistoryRequest { ...@@ -304,3 +310,17 @@ message GetHistoryRequest {
message GetHistoryResponse { message GetHistoryResponse {
repeated hapi.release.Release releases = 1; repeated hapi.release.Release releases = 1;
} }
// TestReleaseRequest is a request to get the status of a release.
message TestReleaseRequest {
// Name is the name of the release
string name = 1;
// timeout specifies the max amount of time any kubernetes client command can run.
int64 timeout = 2;
}
// TestReleaseResponse
message TestReleaseResponse {
// TODO: change to repeated hapi.release.Release.Test results = 1; (for stream)
hapi.release.TestSuite result = 1;
}
...@@ -122,6 +122,7 @@ func newRootCmd(out io.Writer) *cobra.Command { ...@@ -122,6 +122,7 @@ func newRootCmd(out io.Writer) *cobra.Command {
newInitCmd(out), newInitCmd(out),
newResetCmd(nil, out), newResetCmd(nil, out),
newVersionCmd(nil, out), newVersionCmd(nil, out),
newReleaseTestCmd(nil, out),
// Hidden documentation generator command: 'helm docs' // Hidden documentation generator command: 'helm docs'
newDocsCmd(out), newDocsCmd(out),
......
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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 main
import (
"fmt"
"io"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
//"k8s.io/helm/pkg/proto/hapi/release"
)
const releaseTestDesc = `
Th test command runs the tests for a release.
The argument this command takes is the name of a deployed release.
The tests to be run are defined in the chart that was installed.
`
type releaseTestCmd struct {
name string
out io.Writer
client helm.Interface
timeout int64
}
func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command {
rlsTest := &releaseTestCmd{
out: out,
client: c,
}
cmd := &cobra.Command{
Use: "test [RELEASE]",
Short: "test a release",
Long: releaseTestDesc,
PersistentPreRunE: setupConnection,
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "release name"); err != nil {
return err
}
rlsTest.name = args[0]
rlsTest.client = ensureHelmClient(rlsTest.client)
return rlsTest.run()
},
}
f := cmd.Flags()
f.Int64Var(&rlsTest.timeout, "timeout", 300, "time in seconds to wait for any individual kubernetes operation (like Jobs for hooks)")
return cmd
}
func (t *releaseTestCmd) run() error {
res, err := t.client.ReleaseTest(t.name, helm.ReleaseTestTimeout(t.timeout))
if err != nil {
return prettyError(err)
}
table := uitable.New()
table.MaxColWidth = 50
table.AddRow("NAME", "Result", "Info")
//TODO: change Result to Suite
for _, r := range res.Result.Results {
table.AddRow(r.Name, r.Status, r.Info)
}
fmt.Fprintln(t.out, table.String()) //TODO: or no tests found
return nil
}
apiVersion: v1
kind: Pod
metadata:
name: "{{.Release.Name}}-service-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: curl
image: radial/busyboxplus:curl
command: ['curl']
args: [ '{{ template "fullname" .}}:{{default 80 .Values.httpPort}}' ]
restartPolicy: Never
---
apiVersion: v1
kind: Pod
metadata:
name: "{{.Release.Name}}-service-failing-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: curl
image: radial/busyboxplus:curl
command: ['curl']
args: [ '{{ template "fullname" .}}-service:{{default 80 .Values.httpPort}}' ]
restartPolicy: Never
...@@ -244,6 +244,19 @@ func (h *Client) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.Get ...@@ -244,6 +244,19 @@ func (h *Client) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.Get
return h.history(ctx, req) return h.history(ctx, req)
} }
// ReleaseTest executes a pre-defined test on a release
func (h *Client) ReleaseTest(rlsName string, opts ...ReleaseTestOption) (*rls.TestReleaseResponse, error) {
for _, opt := range opts {
opt(&h.opts)
}
req := &h.opts.testReq
req.Name = rlsName
ctx := NewContext()
return h.test(ctx, req)
}
// Executes tiller.ListReleases RPC. // Executes tiller.ListReleases RPC.
func (h *Client) list(ctx context.Context, req *rls.ListReleasesRequest) (*rls.ListReleasesResponse, error) { func (h *Client) list(ctx context.Context, req *rls.ListReleasesRequest) (*rls.ListReleasesResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
...@@ -356,3 +369,15 @@ func (h *Client) history(ctx context.Context, req *rls.GetHistoryRequest) (*rls. ...@@ -356,3 +369,15 @@ func (h *Client) history(ctx context.Context, req *rls.GetHistoryRequest) (*rls.
rlc := rls.NewReleaseServiceClient(c) rlc := rls.NewReleaseServiceClient(c)
return rlc.GetHistory(ctx, req) return rlc.GetHistory(ctx, req)
} }
// Executes tiller.TestRelease RPC.
func (h *Client) test(ctx context.Context, req *rls.TestReleaseRequest) (*rls.TestReleaseResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
return nil, err
}
defer c.Close()
rlc := rls.NewReleaseServiceClient(c)
return rlc.RunReleaseTest(ctx, req)
}
...@@ -34,4 +34,5 @@ type Interface interface { ...@@ -34,4 +34,5 @@ type Interface interface {
ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error) ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error)
ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error)
GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error) GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error)
ReleaseTest(rlsName string, opts ...ReleaseTestOption) (*rls.TestReleaseResponse, error)
} }
...@@ -66,6 +66,8 @@ type options struct { ...@@ -66,6 +66,8 @@ type options struct {
histReq rls.GetHistoryRequest histReq rls.GetHistoryRequest
// resetValues instructs Tiller to reset values to their defaults. // resetValues instructs Tiller to reset values to their defaults.
resetValues bool resetValues bool
// release test options are applied directly to the test release history request
testReq rls.TestReleaseRequest
} }
// Host specifies the host address of the Tiller release server, (default = ":44134"). // Host specifies the host address of the Tiller release server, (default = ":44134").
...@@ -174,6 +176,13 @@ func DeleteTimeout(timeout int64) DeleteOption { ...@@ -174,6 +176,13 @@ func DeleteTimeout(timeout int64) DeleteOption {
} }
} }
// TestTimeout specifies the number of seconds before kubernetes calls timeout
func ReleaseTestTimeout(timeout int64) ReleaseTestOption {
return func(opts *options) {
opts.testReq.Timeout = timeout
}
}
// RollbackTimeout specifies the number of seconds before kubernetes calls timeout // RollbackTimeout specifies the number of seconds before kubernetes calls timeout
func RollbackTimeout(timeout int64) RollbackOption { func RollbackTimeout(timeout int64) RollbackOption {
return func(opts *options) { return func(opts *options) {
...@@ -364,3 +373,7 @@ func NewContext() context.Context { ...@@ -364,3 +373,7 @@ func NewContext() context.Context {
md := metadata.Pairs("x-helm-api-client", version.Version) md := metadata.Pairs("x-helm-api-client", version.Version)
return metadata.NewContext(context.TODO(), md) return metadata.NewContext(context.TODO(), md)
} }
// ReleaseTestOption allows configuring optional request data for
// issuing a TestRelease rpc.
type ReleaseTestOption func(*options)
...@@ -33,6 +33,7 @@ import ( ...@@ -33,6 +33,7 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
conditions "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
...@@ -305,6 +306,10 @@ func perform(c *Client, namespace string, infos Result, fn ResourceActorFunc) er ...@@ -305,6 +306,10 @@ func perform(c *Client, namespace string, infos Result, fn ResourceActorFunc) er
if len(infos) == 0 { if len(infos) == 0 {
return ErrNoObjectsVisited return ErrNoObjectsVisited
} }
if err != nil {
return err
}
for _, info := range infos { for _, info := range infos {
if err := fn(info); err != nil { if err := fn(info); err != nil {
return err return err
...@@ -610,3 +615,43 @@ func scrubValidationError(err error) error { ...@@ -610,3 +615,43 @@ func scrubValidationError(err error) error {
} }
return err return err
} }
func (c *Client) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) {
infos, err := c.Build(namespace, reader)
if err != nil {
return api.PodUnknown, err
}
info := infos[0]
// TODO: should we be checking kind before hand? probably yes.
// TODO: add validation to linter: any manifest with a test hook has to be a pod kind?
kind := info.Mapping.GroupVersionKind.Kind
if kind != "Pod" {
return api.PodUnknown, fmt.Errorf("%s is not a Pod", info.Name)
}
if err := watchPodUntilComplete(timeout, info); err != nil {
return api.PodUnknown, err
}
if err := info.Get(); err != nil {
return api.PodUnknown, err
}
status := info.Object.(*api.Pod).Status.Phase
return status, nil
}
func watchPodUntilComplete(timeout time.Duration, info *resource.Info) error {
w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion)
if err != nil {
return err
}
log.Printf("Watching pod %s for completion with timeout of %v", info.Name, timeout)
_, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) {
return conditions.PodCompleted(e)
})
return err
}
...@@ -305,6 +305,51 @@ func TestPerform(t *testing.T) { ...@@ -305,6 +305,51 @@ func TestPerform(t *testing.T) {
} }
} }
func TestWaitAndGetCompletedPodStatus(t *testing.T) {
f, tf, codec, ns := cmdtesting.NewAPIFactory()
actions := make(map[string]string)
testPodList := newPodList("bestpod")
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
p, m := req.URL.Path, req.Method
actions[p] = m
count := 0
switch {
case p == "/namespaces/test/pods/bestpod" && m == "GET":
return newResponse(200, &testPodList.Items[0])
case p == "/watch/namespaces/test/pods/bestpod" && m == "GET":
//TODO: fix response
count = count + 1
if count == 1 {
//returns event running
return newResponse(200, &testPodList.Items[0])
}
if count == 2 {
//return event succeeded
return newResponse(200, &testPodList.Items[0])
}
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
c := &Client{Factory: f}
//stub watchUntil to return no error
status, err := c.WaitAndGetCompletedPodStatus("test", objBody(codec, &testPodList), 30*time.Second)
if err != nil {
t.Fatal(err)
}
if status != api.PodSucceeded {
t.Fatal("Expected %s, got %s", api.PodSucceeded, status)
}
}
func TestReal(t *testing.T) { func TestReal(t *testing.T) {
t.Skip("This is a live test, comment this line to run") t.Skip("This is a live test, comment this line to run")
c := New(nil) c := New(nil)
......
...@@ -10,12 +10,16 @@ It is generated from these files: ...@@ -10,12 +10,16 @@ It is generated from these files:
hapi/release/info.proto hapi/release/info.proto
hapi/release/release.proto hapi/release/release.proto
hapi/release/status.proto hapi/release/status.proto
hapi/release/test_result.proto
hapi/release/test_suite.proto
It has these top-level messages: It has these top-level messages:
Hook Hook
Info Info
Release Release
Status Status
TestResult
TestSuite
*/ */
package release package release
...@@ -47,6 +51,7 @@ const ( ...@@ -47,6 +51,7 @@ const (
Hook_POST_UPGRADE Hook_Event = 6 Hook_POST_UPGRADE Hook_Event = 6
Hook_PRE_ROLLBACK Hook_Event = 7 Hook_PRE_ROLLBACK Hook_Event = 7
Hook_POST_ROLLBACK Hook_Event = 8 Hook_POST_ROLLBACK Hook_Event = 8
Hook_RELEASE_TEST Hook_Event = 9
) )
var Hook_Event_name = map[int32]string{ var Hook_Event_name = map[int32]string{
...@@ -59,6 +64,7 @@ var Hook_Event_name = map[int32]string{ ...@@ -59,6 +64,7 @@ var Hook_Event_name = map[int32]string{
6: "POST_UPGRADE", 6: "POST_UPGRADE",
7: "PRE_ROLLBACK", 7: "PRE_ROLLBACK",
8: "POST_ROLLBACK", 8: "POST_ROLLBACK",
9: "RELEASE_TEST",
} }
var Hook_Event_value = map[string]int32{ var Hook_Event_value = map[string]int32{
"UNKNOWN": 0, "UNKNOWN": 0,
...@@ -70,6 +76,7 @@ var Hook_Event_value = map[string]int32{ ...@@ -70,6 +76,7 @@ var Hook_Event_value = map[string]int32{
"POST_UPGRADE": 6, "POST_UPGRADE": 6,
"PRE_ROLLBACK": 7, "PRE_ROLLBACK": 7,
"POST_ROLLBACK": 8, "POST_ROLLBACK": 8,
"RELEASE_TEST": 9,
} }
func (x Hook_Event) String() string { func (x Hook_Event) String() string {
...@@ -112,26 +119,26 @@ func init() { ...@@ -112,26 +119,26 @@ func init() {
func init() { proto.RegisterFile("hapi/release/hook.proto", fileDescriptor0) } func init() { proto.RegisterFile("hapi/release/hook.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 321 bytes of a gzipped FileDescriptorProto // 334 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8f, 0x4d, 0x6e, 0xea, 0x30, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x90, 0xcd, 0x6e, 0xe2, 0x30,
0x14, 0x85, 0x5f, 0x20, 0x24, 0x70, 0xe1, 0xd1, 0xd4, 0x93, 0x5a, 0x4c, 0x8a, 0x18, 0x31, 0x72, 0x14, 0x85, 0x27, 0x10, 0x12, 0x30, 0x0c, 0xe3, 0xf1, 0x66, 0x2c, 0x36, 0x83, 0x58, 0xb1, 0x72,
0x2a, 0xaa, 0x2e, 0x00, 0x8a, 0xd5, 0x56, 0x44, 0x01, 0x99, 0xa0, 0x4a, 0x9d, 0x20, 0xa3, 0x1a, 0x2a, 0xaa, 0x3e, 0x40, 0x28, 0x56, 0x5b, 0x11, 0x05, 0xe4, 0x04, 0x55, 0xea, 0x26, 0x32, 0xaa,
0x88, 0x20, 0x71, 0x44, 0x4c, 0xd7, 0xd3, 0xf5, 0x75, 0x15, 0x95, 0x9d, 0x1f, 0x75, 0x76, 0xfd, 0x81, 0x08, 0x12, 0x47, 0xc4, 0xf4, 0xc1, 0xfa, 0x7c, 0x5d, 0x54, 0x76, 0x7e, 0xd4, 0xdd, 0xcd,
0xdd, 0xcf, 0xc7, 0x3e, 0x70, 0x77, 0xe4, 0x59, 0xec, 0x5f, 0xc4, 0x59, 0xf0, 0x5c, 0xf8, 0x47, 0x77, 0xbf, 0x1c, 0xfb, 0x18, 0xfc, 0x3b, 0xf1, 0x22, 0xf5, 0xae, 0xe2, 0x22, 0x78, 0x29, 0xbc,
0x29, 0x4f, 0x24, 0xbb, 0x48, 0x25, 0x51, 0x4f, 0x2f, 0x48, 0xb9, 0x18, 0xdc, 0x1f, 0xa4, 0x3c, 0x93, 0x94, 0x67, 0x52, 0x5c, 0xa5, 0x92, 0x68, 0xa4, 0x17, 0xa4, 0x5e, 0x4c, 0xfe, 0x1f, 0xa5,
0x9c, 0x85, 0x6f, 0x76, 0xbb, 0xeb, 0xde, 0x57, 0x71, 0x22, 0x72, 0xc5, 0x93, 0xac, 0xd0, 0x47, 0x3c, 0x5e, 0x84, 0x67, 0x76, 0xfb, 0xdb, 0xc1, 0x53, 0x69, 0x26, 0x4a, 0xc5, 0xb3, 0xa2, 0xd2,
0x3f, 0x0d, 0xb0, 0x5f, 0xa5, 0x3c, 0x21, 0x04, 0x76, 0xca, 0x13, 0x81, 0xad, 0xa1, 0x35, 0xee, 0x67, 0x5f, 0x1d, 0x60, 0x3f, 0x4b, 0x79, 0x46, 0x08, 0xd8, 0x39, 0xcf, 0x04, 0xb6, 0xa6, 0xd6,
0x30, 0x33, 0x6b, 0x76, 0x8a, 0xd3, 0x4f, 0xdc, 0x28, 0x98, 0x9e, 0x35, 0xcb, 0xb8, 0x3a, 0xe2, 0x7c, 0xc0, 0xcc, 0xac, 0xd9, 0x39, 0xcd, 0xdf, 0x71, 0xa7, 0x62, 0x7a, 0xd6, 0xac, 0xe0, 0xea,
0x66, 0xc1, 0xf4, 0x8c, 0x06, 0xd0, 0x4e, 0x78, 0x1a, 0xef, 0x45, 0xae, 0xb0, 0x6d, 0x78, 0x7d, 0x84, 0xbb, 0x15, 0xd3, 0x33, 0x9a, 0x80, 0x7e, 0xc6, 0xf3, 0xf4, 0x20, 0x4a, 0x85, 0x6d, 0xc3,
0x46, 0x0f, 0xe0, 0x88, 0x2f, 0x91, 0xaa, 0x1c, 0xb7, 0x86, 0xcd, 0x71, 0x7f, 0x82, 0xc9, 0xdf, 0xdb, 0x6f, 0x74, 0x07, 0x1c, 0xf1, 0x21, 0x72, 0x55, 0xe2, 0xde, 0xb4, 0x3b, 0x1f, 0x2f, 0x30,
0x0f, 0x12, 0xfd, 0x36, 0xa1, 0x5a, 0x60, 0xa5, 0x87, 0x9e, 0xa0, 0x7d, 0xe6, 0xb9, 0xda, 0x5e, 0xf9, 0x79, 0x41, 0xa2, 0xcf, 0x26, 0x54, 0x0b, 0xac, 0xf6, 0xd0, 0x03, 0xe8, 0x5f, 0x78, 0xa9,
0xae, 0x29, 0x76, 0x86, 0xd6, 0xb8, 0x3b, 0x19, 0x90, 0xa2, 0x06, 0xa9, 0x6a, 0x90, 0xa8, 0xaa, 0x92, 0xeb, 0x2d, 0xc7, 0xce, 0xd4, 0x9a, 0x0f, 0x17, 0x13, 0x52, 0xd5, 0x20, 0x4d, 0x0d, 0x12,
0xc1, 0x5c, 0xed, 0xb2, 0x6b, 0x3a, 0xfa, 0xb6, 0xa0, 0x65, 0x82, 0x50, 0x17, 0xdc, 0x4d, 0xb8, 0x37, 0x35, 0x98, 0xab, 0x5d, 0x76, 0xcb, 0x67, 0x9f, 0x16, 0xe8, 0x99, 0x20, 0x34, 0x04, 0xee,
0x08, 0x97, 0xef, 0xa1, 0xf7, 0x0f, 0xdd, 0x40, 0x77, 0xc5, 0xe8, 0xf6, 0x2d, 0x5c, 0x47, 0xd3, 0x2e, 0x5c, 0x87, 0x9b, 0xd7, 0x10, 0xfe, 0x42, 0x7f, 0xc0, 0x70, 0xcb, 0x68, 0xf2, 0x12, 0x46,
0x20, 0xf0, 0x2c, 0xe4, 0x41, 0x6f, 0xb5, 0x5c, 0x47, 0x35, 0x69, 0xa0, 0x3e, 0x80, 0x56, 0xe6, 0xb1, 0x1f, 0x04, 0xd0, 0x42, 0x10, 0x8c, 0xb6, 0x9b, 0x28, 0x6e, 0x49, 0x07, 0x8d, 0x01, 0xd0,
0x34, 0xa0, 0x11, 0xf5, 0x9a, 0xe6, 0x8a, 0x36, 0x4a, 0x60, 0x57, 0x19, 0x9b, 0xd5, 0x0b, 0x9b, 0xca, 0x8a, 0x06, 0x34, 0xa6, 0xb0, 0x6b, 0x7e, 0xd1, 0x46, 0x0d, 0xec, 0x26, 0x63, 0xb7, 0x7d,
0xce, 0xa9, 0xd7, 0xaa, 0x33, 0x2a, 0xe2, 0x18, 0xc2, 0xe8, 0x96, 0x2d, 0x83, 0x60, 0x36, 0x7d, 0x62, 0xfe, 0x8a, 0xc2, 0x5e, 0x9b, 0xd1, 0x10, 0xc7, 0x10, 0x46, 0x13, 0xb6, 0x09, 0x82, 0xa5,
0x5e, 0x78, 0x2e, 0xba, 0x85, 0xff, 0xc6, 0xa9, 0x51, 0x7b, 0xd6, 0xf9, 0x70, 0xcb, 0xde, 0x3b, 0xff, 0xb8, 0x86, 0x2e, 0xfa, 0x0b, 0x7e, 0x1b, 0xa7, 0x45, 0x7d, 0x2d, 0x31, 0x1a, 0x50, 0x3f,
0xc7, 0x54, 0x79, 0xfc, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xa4, 0x2e, 0x6f, 0xbd, 0xc8, 0x01, 0x00, 0xa2, 0x49, 0x4c, 0xa3, 0x18, 0x0e, 0x96, 0x83, 0x37, 0xb7, 0x7e, 0x89, 0xbd, 0x63, 0xca, 0xdd,
0x00, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x35, 0xb7, 0x2a, 0x22, 0xda, 0x01, 0x00, 0x00,
} }
...@@ -35,6 +35,8 @@ type Release struct { ...@@ -35,6 +35,8 @@ type Release struct {
Version int32 `protobuf:"varint,7,opt,name=version" json:"version,omitempty"` Version int32 `protobuf:"varint,7,opt,name=version" json:"version,omitempty"`
// Namespace is the kubernetes namespace of the release. // Namespace is the kubernetes namespace of the release.
Namespace string `protobuf:"bytes,8,opt,name=namespace" json:"namespace,omitempty"` Namespace string `protobuf:"bytes,8,opt,name=namespace" json:"namespace,omitempty"`
// TestSuite provides results on the last test run on a release
TestSuite *TestSuite `protobuf:"bytes,9,opt,name=test_suite,json=testSuite" json:"test_suite,omitempty"`
} }
func (m *Release) Reset() { *m = Release{} } func (m *Release) Reset() { *m = Release{} }
...@@ -70,6 +72,13 @@ func (m *Release) GetHooks() []*Hook { ...@@ -70,6 +72,13 @@ func (m *Release) GetHooks() []*Hook {
return nil return nil
} }
func (m *Release) GetTestSuite() *TestSuite {
if m != nil {
return m.TestSuite
}
return nil
}
func init() { func init() {
proto.RegisterType((*Release)(nil), "hapi.release.Release") proto.RegisterType((*Release)(nil), "hapi.release.Release")
} }
...@@ -77,21 +86,24 @@ func init() { ...@@ -77,21 +86,24 @@ func init() {
func init() { proto.RegisterFile("hapi/release/release.proto", fileDescriptor2) } func init() { proto.RegisterFile("hapi/release/release.proto", fileDescriptor2) }
var fileDescriptor2 = []byte{ var fileDescriptor2 = []byte{
// 256 bytes of a gzipped FileDescriptorProto // 290 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x90, 0xbf, 0x4e, 0xc3, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x90, 0xbf, 0x4e, 0xc3, 0x30,
0x0c, 0xc6, 0x95, 0x36, 0x7f, 0x1a, 0xc3, 0x82, 0x07, 0xb0, 0x22, 0x86, 0x88, 0x01, 0x22, 0x86, 0x10, 0xc6, 0x95, 0x36, 0x7f, 0x9a, 0x83, 0x85, 0x1b, 0xa8, 0x15, 0x81, 0x14, 0x31, 0x40, 0xc4,
0x54, 0x82, 0x37, 0x80, 0x05, 0xd6, 0x1b, 0xd9, 0x8e, 0xe8, 0x42, 0x4e, 0xa5, 0xe7, 0x28, 0x17, 0x90, 0x4a, 0x20, 0xf1, 0x00, 0xb0, 0xc0, 0x6a, 0x98, 0x58, 0x90, 0x89, 0x1c, 0x62, 0x95, 0xda,
0xf1, 0x2c, 0x3c, 0x2e, 0xba, 0x3f, 0x85, 0x94, 0x2e, 0x4e, 0xec, 0xdf, 0xa7, 0xcf, 0xdf, 0x19, 0x51, 0x6c, 0x78, 0x4e, 0x1e, 0x09, 0xf9, 0x4f, 0x68, 0x42, 0x17, 0xc7, 0x77, 0xbf, 0x2f, 0xf7,
0xaa, 0x41, 0x8e, 0x7a, 0x3b, 0xa9, 0x4f, 0x25, 0xad, 0x3a, 0x7c, 0xdb, 0x71, 0xe2, 0x99, 0xf1, 0x7d, 0x3e, 0x28, 0x3a, 0xd6, 0x8b, 0xcd, 0xc0, 0x3f, 0x39, 0xd3, 0x7c, 0xfc, 0xd6, 0xfd, 0xa0,
0xdc, 0xb1, 0x36, 0xce, 0xaa, 0xab, 0x23, 0xe5, 0xc0, 0xbc, 0x0b, 0xb2, 0x7f, 0x40, 0x9b, 0x9e, 0x8c, 0xc2, 0x63, 0xcb, 0xea, 0xd0, 0x2b, 0xd6, 0x33, 0x65, 0xa7, 0xd4, 0xd6, 0xcb, 0xfe, 0x01,
0x8f, 0x40, 0x37, 0xc8, 0x69, 0xde, 0x76, 0x6c, 0x7a, 0xfd, 0x11, 0xc1, 0xe5, 0x12, 0xb8, 0x1a, 0x21, 0x5b, 0x15, 0xc0, 0xf9, 0x0c, 0x18, 0xae, 0xcd, 0x9b, 0xfe, 0x12, 0x86, 0xcf, 0xfe, 0x6b,
0xe6, 0x37, 0xdf, 0x2b, 0x28, 0x44, 0xf0, 0x41, 0x84, 0xd4, 0xc8, 0xbd, 0xa2, 0xa4, 0x4e, 0x9a, 0x3a, 0x36, 0x98, 0x4d, 0xa3, 0x64, 0x2b, 0x3e, 0x02, 0x38, 0x9d, 0x02, 0x7b, 0xfa, 0xfe, 0xc5,
0x52, 0xf8, 0x7f, 0xbc, 0x85, 0xd4, 0xd9, 0xd3, 0xaa, 0x4e, 0x9a, 0xb3, 0x07, 0x6c, 0x97, 0xf9, 0xcf, 0x02, 0x32, 0xea, 0xa7, 0x21, 0x42, 0x2c, 0xd9, 0x8e, 0x93, 0xa8, 0x8c, 0xaa, 0x9c, 0xba,
0xda, 0x57, 0xd3, 0xb3, 0xf0, 0x1c, 0xef, 0x20, 0xf3, 0xb6, 0xb4, 0xf6, 0xc2, 0x8b, 0x20, 0x0c, 0x3b, 0x5e, 0x42, 0x6c, 0xdd, 0xc9, 0xa2, 0x8c, 0xaa, 0xa3, 0x1b, 0xac, 0xa7, 0xf1, 0xeb, 0x27,
0x9b, 0x9e, 0x5d, 0x15, 0x81, 0xe3, 0x3d, 0xe4, 0x21, 0x18, 0xa5, 0x4b, 0xcb, 0xa8, 0xf4, 0x44, 0xd9, 0x2a, 0xea, 0x38, 0x5e, 0x41, 0xe2, 0xc6, 0x92, 0xa5, 0x13, 0x9e, 0x78, 0xa1, 0x77, 0x7a,
0x44, 0x05, 0x56, 0xb0, 0xd9, 0x4b, 0xa3, 0x7b, 0x65, 0x67, 0xca, 0x7c, 0xa8, 0xdf, 0x1e, 0x1b, 0xb0, 0x27, 0xf5, 0x1c, 0xaf, 0x21, 0xf5, 0xc1, 0x48, 0x3c, 0x1d, 0x19, 0x94, 0x8e, 0xd0, 0xa0,
0xc8, 0xdc, 0x41, 0x2c, 0xe5, 0xf5, 0xfa, 0x34, 0xd9, 0x0b, 0xf3, 0x4e, 0x04, 0x01, 0x12, 0x14, 0xc0, 0x02, 0x56, 0x3b, 0x26, 0x45, 0xcb, 0xb5, 0x21, 0x89, 0x0b, 0xf5, 0x57, 0x63, 0x05, 0x89,
0x5f, 0x6a, 0xb2, 0x9a, 0x0d, 0x15, 0x75, 0xd2, 0x64, 0xe2, 0xd0, 0xe2, 0x35, 0x94, 0xee, 0x91, 0xdd, 0x97, 0x26, 0x69, 0xb9, 0x3c, 0x4c, 0xf6, 0xa8, 0xd4, 0x96, 0x7a, 0x01, 0x12, 0xc8, 0xbe,
0x76, 0x94, 0x9d, 0xa2, 0x8d, 0x5f, 0xf0, 0x37, 0x78, 0x2a, 0xdf, 0x8a, 0x68, 0xf7, 0x9e, 0xfb, 0xf9, 0xa0, 0x85, 0x92, 0x24, 0x2b, 0xa3, 0x2a, 0xa1, 0x63, 0x89, 0x67, 0x90, 0xdb, 0x47, 0xea,
0x63, 0x3d, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x8f, 0xec, 0x97, 0xbb, 0x01, 0x00, 0x00, 0x9e, 0x35, 0x9c, 0xac, 0x9c, 0xc1, 0xbe, 0x81, 0x77, 0x00, 0xfb, 0xfd, 0x92, 0xdc, 0xa5, 0x5d,
0xcf, 0x6d, 0x5e, 0xb8, 0x36, 0xcf, 0x16, 0xd3, 0xdc, 0x8c, 0xd7, 0xfb, 0xfc, 0x35, 0x0b, 0xfc,
0x3d, 0x75, 0x4b, 0xbe, 0xfd, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xea, 0xe6, 0xdb, 0x71, 0x12, 0x02,
0x00, 0x00,
} }
// Code generated by protoc-gen-go.
// source: hapi/release/test_result.proto
// DO NOT EDIT!
package release
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_protobuf "github.com/golang/protobuf/ptypes/timestamp"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
type TestResult_Status int32
const (
TestResult_UNKNOWN TestResult_Status = 0
TestResult_SUCCESS TestResult_Status = 1
TestResult_FAILURE TestResult_Status = 2
)
var TestResult_Status_name = map[int32]string{
0: "UNKNOWN",
1: "SUCCESS",
2: "FAILURE",
}
var TestResult_Status_value = map[string]int32{
"UNKNOWN": 0,
"SUCCESS": 1,
"FAILURE": 2,
}
func (x TestResult_Status) String() string {
return proto.EnumName(TestResult_Status_name, int32(x))
}
func (TestResult_Status) EnumDescriptor() ([]byte, []int) { return fileDescriptor4, []int{0, 0} }
type TestResult struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Status TestResult_Status `protobuf:"varint,2,opt,name=status,enum=hapi.release.TestResult_Status" json:"status,omitempty"`
Info string `protobuf:"bytes,3,opt,name=info" json:"info,omitempty"`
LastRun *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=last_run,json=lastRun" json:"last_run,omitempty"`
}
func (m *TestResult) Reset() { *m = TestResult{} }
func (m *TestResult) String() string { return proto.CompactTextString(m) }
func (*TestResult) ProtoMessage() {}
func (*TestResult) Descriptor() ([]byte, []int) { return fileDescriptor4, []int{0} }
func (m *TestResult) GetLastRun() *google_protobuf.Timestamp {
if m != nil {
return m.LastRun
}
return nil
}
func init() {
proto.RegisterType((*TestResult)(nil), "hapi.release.TestResult")
proto.RegisterEnum("hapi.release.TestResult_Status", TestResult_Status_name, TestResult_Status_value)
}
func init() { proto.RegisterFile("hapi/release/test_result.proto", fileDescriptor4) }
var fileDescriptor4 = []byte{
// 244 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8e, 0x41, 0x4b, 0xc3, 0x30,
0x18, 0x86, 0xcd, 0x1c, 0xad, 0xcb, 0x44, 0x4a, 0x4e, 0x65, 0x07, 0x57, 0x76, 0xea, 0x29, 0x81,
0x89, 0x78, 0xd6, 0x31, 0x41, 0x94, 0x0a, 0xe9, 0x8a, 0xe0, 0x45, 0x32, 0xf8, 0x36, 0x0b, 0x6d,
0x53, 0x9a, 0x2f, 0x3f, 0xd5, 0xff, 0x23, 0x49, 0x5a, 0xf4, 0xf6, 0xbd, 0xbc, 0x6f, 0x9e, 0x3c,
0xf4, 0xf6, 0x5b, 0xf5, 0xb5, 0x18, 0xa0, 0x01, 0x65, 0x40, 0x20, 0x18, 0xfc, 0x1a, 0xc0, 0xd8,
0x06, 0x79, 0x3f, 0x68, 0xd4, 0xec, 0xda, 0xf5, 0x7c, 0xec, 0x57, 0xeb, 0xb3, 0xd6, 0xe7, 0x06,
0x84, 0xef, 0x8e, 0xf6, 0x24, 0xb0, 0x6e, 0xc1, 0xa0, 0x6a, 0xfb, 0x30, 0xdf, 0xfc, 0x10, 0x4a,
0x0f, 0x60, 0x50, 0x7a, 0x06, 0x63, 0x74, 0xde, 0xa9, 0x16, 0x52, 0x92, 0x91, 0x7c, 0x21, 0xfd,
0xcd, 0x1e, 0x68, 0x64, 0x50, 0xa1, 0x35, 0xe9, 0x2c, 0x23, 0xf9, 0xcd, 0x76, 0xcd, 0xff, 0x7f,
0xc1, 0xff, 0x5e, 0xf3, 0xd2, 0xcf, 0xe4, 0x38, 0x77, 0xb0, 0xba, 0x3b, 0xe9, 0xf4, 0x32, 0xc0,
0xdc, 0xcd, 0xee, 0xe9, 0x55, 0xa3, 0x9c, 0xb3, 0xed, 0xd2, 0x79, 0x46, 0xf2, 0xe5, 0x76, 0xc5,
0x83, 0x23, 0x9f, 0x1c, 0xf9, 0x61, 0x72, 0x94, 0xb1, 0xdb, 0x4a, 0xdb, 0x6d, 0x04, 0x8d, 0x02,
0x9c, 0x2d, 0x69, 0x5c, 0x15, 0xaf, 0xc5, 0xfb, 0x47, 0x91, 0x5c, 0xb8, 0x50, 0x56, 0xbb, 0xdd,
0xbe, 0x2c, 0x13, 0xe2, 0xc2, 0xf3, 0xe3, 0xcb, 0x5b, 0x25, 0xf7, 0xc9, 0xec, 0x69, 0xf1, 0x19,
0x8f, 0x82, 0xc7, 0xc8, 0x83, 0xef, 0x7e, 0x03, 0x00, 0x00, 0xff, 0xff, 0x4c, 0x44, 0x22, 0xbb,
0x3a, 0x01, 0x00, 0x00,
}
// Code generated by protoc-gen-go.
// source: hapi/release/test_suite.proto
// DO NOT EDIT!
package release
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_protobuf "github.com/golang/protobuf/ptypes/timestamp"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// TestSuite comprises of the last run of the pre-defined test suite of a release version
type TestSuite struct {
// LastRun indicates the date/time this test was last run.
LastRun *google_protobuf.Timestamp `protobuf:"bytes,1,opt,name=last_run,json=lastRun" json:"last_run,omitempty"`
// Results are the results of each segment of the test
Results []*TestResult `protobuf:"bytes,2,rep,name=results" json:"results,omitempty"`
}
func (m *TestSuite) Reset() { *m = TestSuite{} }
func (m *TestSuite) String() string { return proto.CompactTextString(m) }
func (*TestSuite) ProtoMessage() {}
func (*TestSuite) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{0} }
func (m *TestSuite) GetLastRun() *google_protobuf.Timestamp {
if m != nil {
return m.LastRun
}
return nil
}
func (m *TestSuite) GetResults() []*TestResult {
if m != nil {
return m.Results
}
return nil
}
func init() {
proto.RegisterType((*TestSuite)(nil), "hapi.release.TestSuite")
}
func init() { proto.RegisterFile("hapi/release/test_suite.proto", fileDescriptor5) }
var fileDescriptor5 = []byte{
// 183 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x8e, 0xc1, 0x8a, 0x83, 0x30,
0x14, 0x45, 0x71, 0x06, 0xc6, 0x31, 0xce, 0xca, 0x95, 0x08, 0xd3, 0x4a, 0x57, 0xae, 0x5e, 0xc0,
0xd2, 0x1f, 0xe8, 0x27, 0xa4, 0xae, 0xba, 0x29, 0x11, 0x5e, 0xad, 0x10, 0x8d, 0xf8, 0x5e, 0xfa,
0xfd, 0x25, 0x46, 0xa1, 0xd0, 0xf5, 0x39, 0xdc, 0x73, 0xc5, 0xff, 0x43, 0x4f, 0xbd, 0x9c, 0xd1,
0xa0, 0x26, 0x94, 0x8c, 0xc4, 0x37, 0x72, 0x3d, 0x23, 0x4c, 0xb3, 0x65, 0x9b, 0xfd, 0x79, 0x0c,
0x2b, 0x2e, 0xf6, 0x9d, 0xb5, 0x9d, 0x41, 0xb9, 0xb0, 0xd6, 0xdd, 0x25, 0xf7, 0x03, 0x12, 0xeb,
0x61, 0x0a, 0x7a, 0xb1, 0xfb, 0x5c, 0x9b, 0x91, 0x9c, 0xe1, 0xc0, 0x0f, 0x4f, 0x91, 0x34, 0x48,
0x7c, 0xf1, 0x85, 0xec, 0x24, 0x7e, 0x8d, 0xf6, 0x86, 0x1b, 0xf3, 0xa8, 0x8c, 0xaa, 0xb4, 0x2e,
0x20, 0x04, 0x60, 0x0b, 0x40, 0xb3, 0x05, 0x54, 0xec, 0x5d, 0xe5, 0xc6, 0xac, 0x16, 0x71, 0xd8,
0xa4, 0xfc, 0xab, 0xfc, 0xae, 0xd2, 0x3a, 0x87, 0xf7, 0x93, 0xe0, 0x03, 0x6a, 0x11, 0xd4, 0x26,
0x9e, 0x93, 0x6b, 0xbc, 0xe2, 0xf6, 0x67, 0xd9, 0x3e, 0xbe, 0x02, 0x00, 0x00, 0xff, 0xff, 0x05,
0x00, 0xf5, 0xbb, 0xf9, 0x00, 0x00, 0x00,
}
This diff is collapsed.
...@@ -24,6 +24,7 @@ package environment ...@@ -24,6 +24,7 @@ package environment
import ( import (
"io" "io"
"time"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/engine" "k8s.io/helm/pkg/engine"
...@@ -31,6 +32,7 @@ import ( ...@@ -31,6 +32,7 @@ import (
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver" "k8s.io/helm/pkg/storage/driver"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
) )
...@@ -135,6 +137,8 @@ type KubeClient interface { ...@@ -135,6 +137,8 @@ type KubeClient interface {
Update(namespace string, originalReader, modifiedReader io.Reader, recreate bool, timeout int64, shouldWait bool) error Update(namespace string, originalReader, modifiedReader io.Reader, recreate bool, timeout int64, shouldWait bool) error
Build(namespace string, reader io.Reader) (kube.Result, error) Build(namespace string, reader io.Reader) (kube.Result, error)
//TODO: insert description
WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error)
} }
// PrintingKubeClient implements KubeClient, but simply prints the reader to // PrintingKubeClient implements KubeClient, but simply prints the reader to
......
...@@ -40,6 +40,7 @@ const ( ...@@ -40,6 +40,7 @@ const (
postUpgrade = "post-upgrade" postUpgrade = "post-upgrade"
preRollback = "pre-rollback" preRollback = "pre-rollback"
postRollback = "post-rollback" postRollback = "post-rollback"
releaseTest = "test"
) )
var events = map[string]release.Hook_Event{ var events = map[string]release.Hook_Event{
...@@ -51,6 +52,7 @@ var events = map[string]release.Hook_Event{ ...@@ -51,6 +52,7 @@ var events = map[string]release.Hook_Event{
postUpgrade: release.Hook_POST_UPGRADE, postUpgrade: release.Hook_POST_UPGRADE,
preRollback: release.Hook_PRE_ROLLBACK, preRollback: release.Hook_PRE_ROLLBACK,
postRollback: release.Hook_POST_ROLLBACK, postRollback: release.Hook_POST_ROLLBACK,
releaseTest: release.Hook_RELEASE_TEST,
} }
type simpleHead struct { type simpleHead struct {
......
...@@ -1064,3 +1064,29 @@ func validateManifest(c environment.KubeClient, ns string, manifest []byte) erro ...@@ -1064,3 +1064,29 @@ func validateManifest(c environment.KubeClient, ns string, manifest []byte) erro
_, err := c.Build(ns, r) _, err := c.Build(ns, r)
return err return err
} }
// RunTestRelease runs a pre-defined test on a given release
func (s *ReleaseServer) RunReleaseTest(c ctx.Context, req *services.TestReleaseRequest) (*services.TestReleaseResponse, error) {
res := &services.TestReleaseResponse{}
if !ValidName.MatchString(req.Name) {
return nil, errMissingRelease
}
// finds the non-deleted release with the given name
r, err := s.env.Releases.Last(req.Name)
if err != nil {
return nil, err
}
kubeCli := s.env.KubeClient
testSuite, err := runReleaseTestSuite(r.Hooks, kubeCli, r.Name, r.Namespace, req.Timeout)
if err != nil {
return nil, err
}
r.TestSuite = testSuite
res.Result = testSuite
return res, nil
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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 tiller
import (
"bytes"
"fmt"
"log"
"time"
"github.com/ghodss/yaml"
"k8s.io/kubernetes/pkg/api"
"k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/tiller/environment"
"k8s.io/helm/pkg/timeconv"
)
// change name to runReleaseTestSuite
func runReleaseTestSuite(hooks []*release.Hook, kube environment.KubeClient, name, namespace string, timeout int64) (*release.TestSuite, error) {
suite := &release.TestSuite{}
suite.LastRun = timeconv.Now()
results := []*release.TestResult{}
tests, err := prepareTests(hooks, name)
if err != nil {
return suite, err
}
for _, h := range tests {
var sh simpleHead
err := yaml.Unmarshal([]byte(h), &sh)
if err != nil {
//handle err better
return nil, err
}
ts := &release.TestResult{Name: sh.Metadata.Name}
// should this be lower? should we even be saving time to hook?
// TODO: should be start time really
ts.LastRun = timeconv.Now()
resourceCreated := true
b := bytes.NewBufferString(h)
if err := kube.Create(namespace, b); err != nil {
log.Printf("Could not create %s(%s): %v", ts.Name, sh.Kind, err)
ts.Info = err.Error()
//TODO: status option should be constant not random int
ts.Status = 2
resourceCreated = false
}
status := api.PodUnknown
resourceCleanExit := true
if resourceCreated {
b.Reset()
b.WriteString(h)
status, err = kube.WaitAndGetCompletedPodStatus(namespace, b, time.Duration(timeout)*time.Second)
if err != nil {
log.Printf("Error getting status for %s(%s): %s", ts.Name, sh.Kind, err)
ts.Info = err.Error()
ts.Status = 0
resourceCleanExit = false
}
}
// TODO: maybe better suited as a switch statement and include
// PodUnknown, PodFailed, PodRunning, and PodPending scenarios
if resourceCreated && resourceCleanExit && status == api.PodSucceeded {
ts.Status = 1
} else if resourceCreated && resourceCleanExit && status == api.PodFailed {
ts.Status = 2
}
results = append(results, ts)
log.Printf("Test %s(%s) complete", ts.Name, sh.Kind)
//TODO: recordTests() - add test results to configmap with standardized name
}
suite.Results = results
log.Printf("Finished running test suite for %s", name)
return suite, nil
}
func filterTests(hooks []*release.Hook, releaseName string) ([]*release.Hook, error) {
testHooks := []*release.Hook{}
notFoundErr := fmt.Errorf("no tests found for release %s", releaseName)
if len(hooks) == 0 {
return nil, notFoundErr
}
code, ok := events[releaseTest]
if !ok {
return nil, fmt.Errorf("unknown hook %q", releaseTest)
}
found := false
for _, h := range hooks {
for _, e := range h.Events {
if e == code {
found = true
testHooks = append(testHooks, h)
continue
}
}
}
//TODO: probably don't need to check found
if found == false && len(testHooks) == 0 {
return nil, notFoundErr
}
return testHooks, nil
}
func prepareTests(hooks []*release.Hook, releaseName string) ([]string, error) {
testHooks, err := filterTests(hooks, releaseName)
if err != nil {
return nil, err
}
tests := []string{}
for _, h := range testHooks {
individualTests := splitManifests(h.Manifest)
for _, t := range individualTests {
tests = append(tests, t)
}
}
return tests, nil
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment