Merge branch 'master' of https://github.com/kubernetes/helm into move-to-pkg

pull/1938/head
Gergo Huszty 8 years ago
commit a32f71c735

@ -12,16 +12,19 @@ us a chance to try to fix the issue before it is exploited in the wild.
## Contributor License Agreements
We'd love to accept your patches! Before we can take them, we have to jump a couple of legal hurdles.
We'd love to accept your patches! Before we can take them, we have to jump a
couple of legal hurdles.
Please fill out either the individual or corporate Contributor License Agreement (CLA).
The Cloud Native Computing Foundation (CNCF) CLA [must be signed](https://github.com/kubernetes/community/blob/master/CLA.md) by all contributors.
Please fill out either the individual or corporate Contributor License
Agreement (CLA).
* If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html).
* If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html).
Once you are CLA'ed, we'll be able to accept your pull requests. For any issues that you face during this process,
please add a comment [here](https://github.com/kubernetes/kubernetes/issues/27796) explaining the issue and we will help get it sorted out.
Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests.
***NOTE***: Only original source code from you and other people that have signed the CLA can be accepted into the main repository.
***NOTE***: Only original source code from you and other people that have
signed the CLA can be accepted into the repository. This policy does not
apply to [third_party](third_party/) and [vendor](vendor/).
## How to Submit a Proposal

@ -1,7 +1,7 @@
DOCKER_REGISTRY ?= gcr.io
IMAGE_PREFIX ?= kubernetes-helm
SHORT_NAME ?= tiller
TARGETS = darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 windows/amd64
TARGETS = darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le windows/amd64
DIST_DIRS = find * -type d -exec
APP = helm

@ -81,6 +81,10 @@ including installing pre-releases.
- [History](docs/history.md) - A brief history of the project
- [Glossary](docs/glossary.md) - Decode the Helm vocabulary
## Roadmap
The [Helm roadmap is currently located on the wiki](https://github.com/kubernetes/helm/wiki/Roadmap).
## Community, discussion, contribution, and support
You can reach the Helm community and developers via the following channels:

@ -32,6 +32,7 @@ message Hook {
POST_UPGRADE = 6;
PRE_ROLLBACK = 7;
POST_ROLLBACK = 8;
RELEASE_TEST_SUCCESS = 9;
}
string name = 1;
// Kind is the Kubernetes kind.

@ -31,4 +31,7 @@ message Info {
// Deleted tracks when this object was deleted.
google.protobuf.Timestamp deleted = 4;
// Description is human-friendly "log entry" about this release.
string Description = 5;
}

@ -16,6 +16,8 @@ syntax = "proto3";
package hapi.release;
import "hapi/release/test_suite.proto";
import "google/protobuf/any.proto";
option go_package = "release";
@ -39,11 +41,15 @@ message Status {
Code code = 1;
google.protobuf.Any details = 2;
// Deprecated
// google.protobuf.Any details = 2;
// Cluster resources as kubectl would print them.
string resources = 3;
// Contains the rendered templates/NOTES.txt if available
string notes = 4;
// LastTestSuiteRun provides results on the last test run on a release
hapi.release.TestSuite last_test_suite_run = 5;
}

@ -0,0 +1,36 @@
// 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 TestRun {
enum Status {
UNKNOWN = 0;
SUCCESS = 1;
FAILURE = 2;
}
string name = 1;
Status status = 2;
string info = 3;
google.protobuf.Timestamp started_at = 4;
google.protobuf.Timestamp completed_at = 5;
}

@ -0,0 +1,34 @@
// 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_run.proto";
option go_package = "release";
// TestSuite comprises of the last run of the pre-defined test suite of a release version
message TestSuite {
// StartedAt indicates the date/time this test suite was kicked off
google.protobuf.Timestamp started_at = 1;
// CompletedAt indicates the date/time this test suite was completed
google.protobuf.Timestamp completed_at = 2;
// Results are the results of each segment of the test
repeated hapi.release.TestRun results = 3;
}

@ -78,6 +78,10 @@ service ReleaseService {
// ReleaseHistory retrieves a releasse's history.
rpc GetHistory(GetHistoryRequest) returns (GetHistoryResponse) {
}
// RunReleaseTest executes the tests defined of a named release
rpc RunReleaseTest(TestReleaseRequest) returns (stream TestReleaseResponse) {
}
}
// ListReleasesRequest requests a list of releases.
@ -304,3 +308,16 @@ message GetHistoryRequest {
message GetHistoryResponse {
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 represents a message from executing a test
message TestReleaseResponse {
string msg = 1;
}

@ -28,10 +28,15 @@ const dependencyUpDesc = `
Update the on-disk dependencies to mirror the requirements.yaml file.
This command verifies that the required charts, as expressed in 'requirements.yaml',
are present in 'charts/' and are at an acceptable version.
are present in 'charts/' and are at an acceptable version. It will pull down
the latest charts that satisfy the dependencies, and clean up old dependencies.
On successful update, this will generate a lock file that can be used to
rebuild the requirements to an exact version.
Dependencies are not required to be represented in 'requirements.yaml'. For that
reason, an update command will not remove charts unless they are (a) present
in the requirements.yaml file, but (b) at the wrong version.
`
// dependencyUpdateCmd describes a 'helm dependency update'

@ -98,7 +98,34 @@ func TestDependencyUpdateCmd(t *testing.T) {
t.Errorf("Failed hash match: expected %s, got %s", hash, h)
}
t.Logf("Results: %s", out.String())
// Now change the dependencies and update. This verifies that on update,
// old dependencies are cleansed and new dependencies are added.
reqfile := &chartutil.Requirements{
Dependencies: []*chartutil.Dependency{
{Name: "reqtest", Version: "0.1.0", Repository: srv.URL()},
{Name: "compressedchart", Version: "0.3.0", Repository: srv.URL()},
},
}
dir := filepath.Join(hh, chartname)
if err := writeRequirements(dir, reqfile); err != nil {
t.Fatal(err)
}
if err := duc.run(); err != nil {
output := out.String()
t.Logf("Output: %s", output)
t.Fatal(err)
}
// In this second run, we should see compressedchart-0.3.0.tgz, and not
// the 0.1.0 version.
expect = filepath.Join(hh, chartname, "charts/compressedchart-0.3.0.tgz")
if _, err := os.Stat(expect); err != nil {
t.Fatalf("Expected %q: %s", expect, err)
}
dontExpect := filepath.Join(hh, chartname, "charts/compressedchart-0.1.0.tgz")
if _, err := os.Stat(dontExpect); err == nil {
t.Fatalf("Unexpected %q", dontExpect)
}
}
// createTestingChart creates a basic chart that depends on reqtest-0.1.0
@ -117,8 +144,13 @@ func createTestingChart(dest, name, baseURL string) error {
req := &chartutil.Requirements{
Dependencies: []*chartutil.Dependency{
{Name: "reqtest", Version: "0.1.0", Repository: baseURL},
{Name: "compressedchart", Version: "0.1.0", Repository: baseURL},
},
}
return writeRequirements(dir, req)
}
func writeRequirements(dir string, req *chartutil.Requirements) error {
data, err := yaml.Marshal(req)
if err != nil {
return err

@ -33,6 +33,7 @@ import (
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/helm/cmd/helm/helmpath"
"k8s.io/helm/pkg/helm/portforwarder"
"k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/tiller/environment"
)
@ -49,6 +50,8 @@ var (
tillerHost string
tillerNamespace string
kubeContext string
// TODO refactor out this global var
tillerTunnel *kube.Tunnel
)
// flagDebug is a signal that the user wants additional output.
@ -120,7 +123,9 @@ func newRootCmd(out io.Writer) *cobra.Command {
newCompletionCmd(out),
newHomeCmd(out),
newInitCmd(out),
newResetCmd(nil, out),
newVersionCmd(nil, out),
newReleaseTestCmd(nil, out),
// Hidden documentation generator command: 'helm docs'
newDocsCmd(out),
@ -154,7 +159,12 @@ func markDeprecated(cmd *cobra.Command, notice string) *cobra.Command {
func setupConnection(c *cobra.Command, args []string) error {
if tillerHost == "" {
tunnel, err := newTillerPortForwarder(tillerNamespace, kubeContext)
config, client, err := getKubeClient(kubeContext)
if err != nil {
return err
}
tunnel, err := portforwarder.New(tillerNamespace, client, config)
if err != nil {
return err
}
@ -237,3 +247,9 @@ func getKubeClient(context string) (*restclient.Config, *internalclientset.Clien
}
return config, client, nil
}
// getKubeCmd is a convenience method for creating kubernetes cmd client
// for a given kubeconfig context
func getKubeCmd(context string) *kube.Client {
return kube.New(kube.GetConfig(context))
}

@ -95,6 +95,7 @@ func releaseMock(opts *releaseOptions) *release.Release {
FirstDeployed: &date,
LastDeployed: &date,
Status: &release.Status{Code: scode},
Description: "Release mock",
},
Chart: ch,
Config: &chart.Config{Raw: `name: "value"`},
@ -189,6 +190,10 @@ func (c *fakeReleaseClient) ReleaseHistory(rlsName string, opts ...helm.HistoryO
return &rls.GetHistoryResponse{Releases: c.rels}, c.err
}
func (c *fakeReleaseClient) RunReleaseTest(rlsName string, opts ...helm.ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) {
return nil, nil
}
func (c *fakeReleaseClient) Option(opt ...helm.Option) helm.Interface {
return c
}

@ -38,11 +38,11 @@ configures the maximum length of the revision list returned.
The historical release set is printed as a formatted table, e.g:
$ helm history angry-bird --max=4
REVISION UPDATED STATUS CHART
1 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0
2 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0
3 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0
4 Mon Oct 3 10:15:13 2016 DEPLOYED alpine-0.1.0
REVISION UPDATED STATUS CHART DESCRIPTION
1 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Initial install
2 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Upgraded successfully
3 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Rolled back to 2
4 Mon Oct 3 10:15:13 2016 DEPLOYED alpine-0.1.0 Upgraded successfully
`
type historyCmd struct {
@ -97,15 +97,16 @@ func (cmd *historyCmd) run() error {
func formatHistory(rls []*release.Release) string {
tbl := uitable.New()
tbl.MaxColWidth = 30
tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART")
tbl.MaxColWidth = 60
tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "DESCRIPTION")
for i := len(rls) - 1; i >= 0; i-- {
r := rls[i]
c := formatChartname(r.Chart)
t := timeconv.String(r.Info.LastDeployed)
s := r.Info.Status.Code.String()
v := r.Version
tbl.AddRow(v, t, s, c)
d := r.Info.Description
tbl.AddRow(v, t, s, c, d)
}
return tbl.String()
}

@ -50,7 +50,7 @@ func TestHistoryCmd(t *testing.T) {
mk("angry-bird", 2, rpb.Status_SUPERSEDED),
mk("angry-bird", 1, rpb.Status_SUPERSEDED),
},
xout: "REVISION\tUPDATED \tSTATUS \tCHART \n1 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\n2 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\n",
xout: "REVISION\tUPDATED \tSTATUS \tCHART \tDESCRIPTION \n1 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n2 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\tRelease mock\n",
},
{
cmds: "helm history --max=MAX RELEASE_NAME",
@ -60,7 +60,7 @@ func TestHistoryCmd(t *testing.T) {
mk("angry-bird", 4, rpb.Status_DEPLOYED),
mk("angry-bird", 3, rpb.Status_SUPERSEDED),
},
xout: "REVISION\tUPDATED \tSTATUS \tCHART \n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\n",
xout: "REVISION\tUPDATED \tSTATUS \tCHART \tDESCRIPTION \n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\tRelease mock\n",
},
}

@ -109,12 +109,15 @@ func (i *initCmd) run() error {
if err != nil {
return err
}
fmt.Fprintln(i.out, dm)
fm := fmt.Sprintf("apiVersion: extensions/v1beta1\nkind: Deployment\n%s", dm)
fmt.Fprintln(i.out, fm)
sm, err := installer.ServiceManifest(i.namespace)
if err != nil {
return err
}
fmt.Fprintln(i.out, sm)
fm = fmt.Sprintf("apiVersion: v1\nkind: Service\n%s", sm)
fmt.Fprintln(i.out, fm)
}
if i.dryRun {

@ -0,0 +1,83 @@
/*
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 installer // import "k8s.io/helm/cmd/helm/installer"
import (
"strings"
"github.com/ghodss/yaml"
"k8s.io/kubernetes/pkg/api"
kerrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/helm/pkg/kube"
)
// Uninstall uses kubernetes client to uninstall tiller
func Uninstall(kubeClient internalclientset.Interface, kubeCmd *kube.Client, namespace string, verbose bool) error {
if _, err := kubeClient.Core().Services(namespace).Get("tiller-deploy"); err != nil {
if !kerrors.IsNotFound(err) {
return err
}
} else if err := deleteService(kubeClient.Core(), namespace); err != nil {
return err
}
if obj, err := kubeClient.Extensions().Deployments(namespace).Get("tiller-deploy"); err != nil {
if !kerrors.IsNotFound(err) {
return err
}
} else if err := deleteDeployment(kubeCmd, namespace, obj); err != nil {
return err
}
return nil
}
// deleteService deletes the Tiller Service resource
func deleteService(client internalversion.ServicesGetter, namespace string) error {
return client.Services(namespace).Delete("tiller-deploy", &api.DeleteOptions{})
}
// deleteDeployment deletes the Tiller Deployment resource
// We need to use the kubeCmd reaper instead of the kube API because GC for deployment dependents
// is not yet supported at the k8s server level (<= 1.5)
func deleteDeployment(kubeCmd *kube.Client, namespace string, obj *extensions.Deployment) error {
obj.Kind = "Deployment"
obj.APIVersion = "extensions/v1beta1"
buf, err := yaml.Marshal(obj)
if err != nil {
return err
}
reader := strings.NewReader(string(buf))
infos, err := kubeCmd.Build(namespace, reader)
if err != nil {
return err
}
for _, info := range infos {
reaper, err := kubeCmd.Reaper(info.Mapping)
if err != nil {
return err
}
err = reaper.Stop(info.Namespace, info.Name, 0, nil)
if err != nil {
return err
}
}
return nil
}

@ -0,0 +1,160 @@
/*
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 installer // import "k8s.io/helm/cmd/helm/installer"
import (
"testing"
"time"
"k8s.io/kubernetes/pkg/api"
apierrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
testcore "k8s.io/kubernetes/pkg/client/testing/core"
"k8s.io/kubernetes/pkg/kubectl"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/helm/pkg/kube"
)
type fakeReaper struct {
namespace string
name string
}
func (r *fakeReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
r.namespace = namespace
r.name = name
return nil
}
type fakeReaperFactory struct {
cmdutil.Factory
reaper kubectl.Reaper
}
func (f *fakeReaperFactory) Reaper(mapping *meta.RESTMapping) (kubectl.Reaper, error) {
return f.reaper, nil
}
func TestUninstall(t *testing.T) {
existingService := service(api.NamespaceDefault)
existingDeployment := deployment(api.NamespaceDefault, "image", false)
fc := &fake.Clientset{}
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingService, nil
})
fc.AddReactor("delete", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, nil, nil
})
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingDeployment, nil
})
f, _, _, _ := cmdtesting.NewAPIFactory()
r := &fakeReaper{}
rf := &fakeReaperFactory{Factory: f, reaper: r}
kc := &kube.Client{Factory: rf}
if err := Uninstall(fc, kc, api.NamespaceDefault, false); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
}
if r.namespace != api.NamespaceDefault {
t.Errorf("unexpected reaper namespace: %s", r.name)
}
if r.name != "tiller-deploy" {
t.Errorf("unexpected reaper name: %s", r.name)
}
}
func TestUninstall_serviceNotFound(t *testing.T) {
existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false)
fc := &fake.Clientset{}
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewNotFound(api.Resource("services"), "1")
})
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingDeployment, nil
})
f, _, _, _ := cmdtesting.NewAPIFactory()
r := &fakeReaper{}
rf := &fakeReaperFactory{Factory: f, reaper: r}
kc := &kube.Client{Factory: rf}
if err := Uninstall(fc, kc, api.NamespaceDefault, false); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 2 {
t.Errorf("unexpected actions: %v, expected 2 actions got %d", actions, len(actions))
}
if r.namespace != api.NamespaceDefault {
t.Errorf("unexpected reaper namespace: %s", r.name)
}
if r.name != "tiller-deploy" {
t.Errorf("unexpected reaper name: %s", r.name)
}
}
func TestUninstall_deploymentNotFound(t *testing.T) {
existingService := service(api.NamespaceDefault)
fc := &fake.Clientset{}
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingService, nil
})
fc.AddReactor("delete", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, nil, nil
})
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewNotFound(api.Resource("deployments"), "1")
})
f, _, _, _ := cmdtesting.NewAPIFactory()
r := &fakeReaper{}
rf := &fakeReaperFactory{Factory: f, reaper: r}
kc := &kube.Client{Factory: rf}
if err := Uninstall(fc, kc, api.NamespaceDefault, false); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
}
if r.namespace != "" {
t.Errorf("unexpected reaper namespace: %s", r.name)
}
if r.name != "" {
t.Errorf("unexpected reaper name: %s", r.name)
}
}

@ -0,0 +1,84 @@
/*
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/spf13/cobra"
"k8s.io/helm/pkg/helm"
)
const releaseTestDesc = `
The 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() (err error) {
c, errc := t.client.RunReleaseTest(t.name, helm.ReleaseTestTimeout(t.timeout))
for {
select {
case err := <-errc:
return prettyError(err)
case res, ok := <-c:
if !ok {
break
}
fmt.Fprintf(t.out, res.Msg+"\n")
}
}
}

@ -0,0 +1,131 @@
/*
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 (
"errors"
"fmt"
"io"
"os"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/helm/cmd/helm/helmpath"
"k8s.io/helm/cmd/helm/installer"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/proto/hapi/release"
)
const resetDesc = `
This command uninstalls Tiller (the helm server side component) from your
Kubernetes Cluster and optionally deletes local configuration in
$HELM_HOME (default ~/.helm/)
`
type resetCmd struct {
force bool
removeHelmHome bool
namespace string
out io.Writer
home helmpath.Home
client helm.Interface
kubeClient internalclientset.Interface
kubeCmd *kube.Client
}
func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command {
d := &resetCmd{
out: out,
client: client,
}
cmd := &cobra.Command{
Use: "reset",
Short: "uninstalls Tiller from a cluster",
Long: resetDesc,
PersistentPreRunE: setupConnection,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return errors.New("This command does not accept arguments")
}
d.namespace = tillerNamespace
d.home = helmpath.Home(homePath())
d.client = ensureHelmClient(d.client)
return d.run()
},
}
f := cmd.Flags()
f.BoolVarP(&d.force, "force", "f", false, "forces Tiller uninstall even if there are releases installed")
f.BoolVar(&d.removeHelmHome, "remove-helm-home", false, "if set deletes $HELM_HOME")
return cmd
}
// runReset uninstalls tiller from Kubernetes Cluster and deletes local config
func (d *resetCmd) run() error {
if d.kubeClient == nil {
_, c, err := getKubeClient(kubeContext)
if err != nil {
return fmt.Errorf("could not get kubernetes client: %s", err)
}
d.kubeClient = c
}
if d.kubeCmd == nil {
d.kubeCmd = getKubeCmd(kubeContext)
}
res, err := d.client.ListReleases(
helm.ReleaseListStatuses([]release.Status_Code{release.Status_DEPLOYED}),
)
if err != nil {
return prettyError(err)
}
if len(res.Releases) > 0 && !d.force {
return fmt.Errorf("There are still %d deployed releases (Tip: use --force).", len(res.Releases))
}
if err := installer.Uninstall(d.kubeClient, d.kubeCmd, d.namespace, flagDebug); err != nil {
return fmt.Errorf("error unstalling Tiller: %s", err)
}
if d.removeHelmHome {
if err := deleteDirectories(d.home, d.out); err != nil {
return err
}
}
fmt.Fprintln(d.out, "Tiller (the helm server side component) has been uninstalled from your Kubernetes Cluster.")
return nil
}
// deleteDirectories deletes $HELM_HOME
func deleteDirectories(home helmpath.Home, out io.Writer) error {
if _, err := os.Stat(home.String()); err == nil {
fmt.Fprintf(out, "Deleting %s \n", home.String())
if err := os.RemoveAll(home.String()); err != nil {
return fmt.Errorf("Could not remove %s: %s", home.String(), err)
}
}
return nil
}

@ -0,0 +1,187 @@
/*
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 (
"bytes"
"io/ioutil"
"os"
"strings"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/helm/cmd/helm/helmpath"
"k8s.io/helm/pkg/proto/hapi/release"
)
func TestResetCmd(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
defer os.Remove(home)
var buf bytes.Buffer
c := &fakeReleaseClient{}
fc := fake.NewSimpleClientset()
cmd := &resetCmd{
out: &buf,
home: helmpath.Home(home),
client: c,
kubeClient: fc,
namespace: api.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Errorf("unexpected error: %v", err)
}
actions := fc.Actions()
if len(actions) != 2 {
t.Errorf("Expected 2 actions, got %d", len(actions))
}
if !actions[0].Matches("get", "services") {
t.Errorf("unexpected action: %v, expected get service", actions[1])
}
if !actions[1].Matches("get", "deployments") {
t.Errorf("unexpected action: %v, expected get deployment", actions[0])
}
expected := "Tiller (the helm server side component) has been uninstalled from your Kubernetes Cluster."
if !strings.Contains(buf.String(), expected) {
t.Errorf("expected %q, got %q", expected, buf.String())
}
if _, err := os.Stat(home); err != nil {
t.Errorf("Helm home directory %s does not exists", home)
}
}
func TestResetCmd_removeHelmHome(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
defer os.Remove(home)
var buf bytes.Buffer
c := &fakeReleaseClient{}
fc := fake.NewSimpleClientset()
cmd := &resetCmd{
removeHelmHome: true,
out: &buf,
home: helmpath.Home(home),
client: c,
kubeClient: fc,
namespace: api.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Errorf("unexpected error: %v", err)
}
actions := fc.Actions()
if len(actions) != 2 {
t.Errorf("Expected 2 actions, got %d", len(actions))
}
if !actions[0].Matches("get", "services") {
t.Errorf("unexpected action: %v, expected get service", actions[1])
}
if !actions[1].Matches("get", "deployments") {
t.Errorf("unexpected action: %v, expected get deployment", actions[0])
}
expected := "Tiller (the helm server side component) has been uninstalled from your Kubernetes Cluster."
if !strings.Contains(buf.String(), expected) {
t.Errorf("expected %q, got %q", expected, buf.String())
}
if _, err := os.Stat(home); err == nil {
t.Errorf("Helm home directory %s already exists", home)
}
}
func TestReset_deployedReleases(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
defer os.Remove(home)
var buf bytes.Buffer
resp := []*release.Release{
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
}
c := &fakeReleaseClient{
rels: resp,
}
fc := fake.NewSimpleClientset()
cmd := &resetCmd{
out: &buf,
home: helmpath.Home(home),
client: c,
kubeClient: fc,
namespace: api.NamespaceDefault,
}
err = cmd.run()
expected := "There are still 1 deployed releases (Tip: use --force)"
if !strings.Contains(err.Error(), expected) {
t.Errorf("unexpected error: %v", err)
}
if _, err := os.Stat(home); err != nil {
t.Errorf("Helm home directory %s does not exists", home)
}
}
func TestReset_forceFlag(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
defer os.Remove(home)
var buf bytes.Buffer
resp := []*release.Release{
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
}
c := &fakeReleaseClient{
rels: resp,
}
fc := fake.NewSimpleClientset()
cmd := &resetCmd{
force: true,
out: &buf,
home: helmpath.Home(home),
client: c,
kubeClient: fc,
namespace: api.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Errorf("unexpected error: %v", err)
}
actions := fc.Actions()
if len(actions) != 2 {
t.Errorf("Expected 2 actions, got %d", len(actions))
}
if !actions[0].Matches("get", "services") {
t.Errorf("unexpected action: %v, expected get service", actions[1])
}
if !actions[1].Matches("get", "deployments") {
t.Errorf("unexpected action: %v, expected get deployment", actions[0])
}
expected := "Tiller (the helm server side component) has been uninstalled from your Kubernetes Cluster."
if !strings.Contains(buf.String(), expected) {
t.Errorf("expected %q, got %q", expected, buf.String())
}
if _, err := os.Stat(home); err != nil {
t.Errorf("Helm home directory %s does not exists", home)
}
}

@ -62,6 +62,7 @@ const verSep = "$$"
// AddRepo adds a repository index to the search index.
func (i *Index) AddRepo(rname string, ind *repo.IndexFile, all bool) {
ind.SortEntries()
for name, ref := range ind.Entries {
if len(ref) == 0 {
// Skip chart names that have zero releases.
@ -175,7 +176,7 @@ func (i *Index) SearchRegexp(re string, threshold int) ([]*Result, error) {
return buf, nil
}
// Chart returns the ChartRef for a particular name.
// Chart returns the ChartVersion for a particular name.
func (i *Index) Chart(name string) (*repo.ChartVersion, error) {
c, ok := i.charts[name]
if !ok {
@ -220,6 +221,8 @@ func (s scoreSorter) Less(a, b int) bool {
if err != nil {
return true
}
// Sort so that the newest chart is higher than the oldest chart. This is
// the opposite of what you'd expect in a function called Less.
return v1.GreaterThan(v2)
}
return first.Name < second.Name

@ -26,14 +26,16 @@ import (
func TestSortScore(t *testing.T) {
in := []*Result{
{Name: "bbb", Score: 0},
{Name: "bbb", Score: 0, Chart: &repo.ChartVersion{Metadata: &chart.Metadata{Version: "1.2.3"}}},
{Name: "aaa", Score: 5},
{Name: "abb", Score: 5},
{Name: "aab", Score: 0},
{Name: "bab", Score: 5},
{Name: "ver", Score: 5, Chart: &repo.ChartVersion{Metadata: &chart.Metadata{Version: "1.2.4"}}},
{Name: "ver", Score: 5, Chart: &repo.ChartVersion{Metadata: &chart.Metadata{Version: "1.2.3"}}},
}
expect := []string{"aab", "bbb", "aaa", "abb", "bab"}
expectScore := []int{0, 0, 5, 5, 5}
expect := []string{"aab", "bbb", "aaa", "abb", "bab", "ver", "ver"}
expectScore := []int{0, 0, 5, 5, 5, 5, 5}
SortScore(in)
// Test Score
@ -48,6 +50,14 @@ func TestSortScore(t *testing.T) {
t.Errorf("Sort error: expected %s, got %s", expect[i], in[i].Name)
}
}
// Test version of last two items
if in[5].Chart.Version != "1.2.4" {
t.Errorf("Expected 1.2.4, got %s", in[5].Chart.Version)
}
if in[6].Chart.Version != "1.2.3" {
t.Error("Expected 1.2.3 to be last")
}
}
var indexfileEntries = map[string]repo.ChartVersions{
@ -123,6 +133,21 @@ func TestAll(t *testing.T) {
}
}
func TestAddRepo_Sort(t *testing.T) {
i := loadTestIndex(t, true)
sr, err := i.Search("testing/santa-maria", 100, false)
if err != nil {
t.Fatal(err)
}
SortScore(sr)
ch := sr[0]
expect := "1.2.3"
if ch.Chart.Version != expect {
t.Errorf("Expected %q, got %q", expect, ch.Chart.Version)
}
}
func TestSearchByName(t *testing.T) {
tests := []struct {

@ -39,7 +39,7 @@ func TestSearchCmd(t *testing.T) {
{
name: "search for 'alpine', expect two matches",
args: []string{"alpine"},
expect: "NAME \tVERSION\tDESCRIPTION \ntesting/alpine\t0.1.0 \tDeploy a basic Alpine Linux pod",
expect: "NAME \tVERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \tDeploy a basic Alpine Linux pod",
},
{
name: "search for 'alpine' with versions, expect three matches",
@ -56,7 +56,7 @@ func TestSearchCmd(t *testing.T) {
name: "search for 'alp[a-z]+', expect two matches",
args: []string{"alp[a-z]+"},
flags: []string{"--regexp"},
expect: "NAME \tVERSION\tDESCRIPTION \ntesting/alpine\t0.1.0 \tDeploy a basic Alpine Linux pod",
expect: "NAME \tVERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \tDeploy a basic Alpine Linux pod",
regexp: true,
},
{

@ -22,9 +22,12 @@ import (
"regexp"
"text/tabwriter"
"github.com/gosuri/uitable"
"github.com/gosuri/uitable/util/strutil"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/timeconv"
)
@ -36,6 +39,7 @@ The status consists of:
- k8s namespace in which the release lives
- state of the release (can be: UNKNOWN, DEPLOYED, DELETED, SUPERSEDED, FAILED or DELETING)
- list of resources that this release consists of, sorted by kind
- details on last test suite run, if applicable
- additional notes provided by the chart
`
@ -92,9 +96,6 @@ func PrintStatus(out io.Writer, res *services.GetReleaseStatusResponse) {
}
fmt.Fprintf(out, "NAMESPACE: %s\n", res.Namespace)
fmt.Fprintf(out, "STATUS: %s\n", res.Info.Status.Code)
if res.Info.Status.Details != nil {
fmt.Fprintf(out, "Details: %s\n", res.Info.Status.Details)
}
fmt.Fprintf(out, "\n")
if len(res.Info.Status.Resources) > 0 {
re := regexp.MustCompile(" +")
@ -103,7 +104,31 @@ func PrintStatus(out io.Writer, res *services.GetReleaseStatusResponse) {
fmt.Fprintf(w, "RESOURCES:\n%s\n", re.ReplaceAllString(res.Info.Status.Resources, "\t"))
w.Flush()
}
if res.Info.Status.LastTestSuiteRun != nil {
lastRun := res.Info.Status.LastTestSuiteRun
fmt.Fprintf(out, "TEST SUITE:\n%s\n%s\n\n%s\n",
fmt.Sprintf("Last Started: %s", timeconv.String(lastRun.StartedAt)),
fmt.Sprintf("Last Completed: %s", timeconv.String(lastRun.CompletedAt)),
formatTestResults(lastRun.Results))
}
if len(res.Info.Status.Notes) > 0 {
fmt.Fprintf(out, "NOTES:\n%s\n", res.Info.Status.Notes)
}
}
func formatTestResults(results []*release.TestRun) string {
tbl := uitable.New()
tbl.MaxColWidth = 50
tbl.AddRow("TEST", "STATUS", "INFO", "STARTED", "COMPLETED")
for i := 0; i < len(results); i++ {
r := results[i]
n := r.Name
s := strutil.PadRight(r.Status.String(), 10, ' ')
i := r.Info
ts := timeconv.String(r.StartedAt)
tc := timeconv.String(r.CompletedAt)
tbl.AddRow(n, s, i, ts, tc)
}
return tbl.String()
}

@ -0,0 +1,149 @@
/*
Copyright 2017 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 (
"bytes"
"fmt"
"io"
"strings"
"testing"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/timeconv"
)
var (
date = timestamp.Timestamp{Seconds: 242085845, Nanos: 0}
dateString = timeconv.String(&date)
)
// statusCase describes a test case dealing with the status of a release
type statusCase struct {
name string
args []string
flags []string
expected string
err bool
rel *release.Release
}
func TestStatusCmd(t *testing.T) {
tests := []statusCase{
{
name: "get status of a deployed release",
args: []string{"flummoxed-chickadee"},
expected: outputWithStatus("DEPLOYED\n\n"),
rel: releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
}),
},
{
name: "get status of a deployed release with notes",
args: []string{"flummoxed-chickadee"},
expected: outputWithStatus("DEPLOYED\n\nNOTES:\nrelease notes\n"),
rel: releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Notes: "release notes",
}),
},
{
name: "get status of a deployed release with resources",
args: []string{"flummoxed-chickadee"},
expected: outputWithStatus("DEPLOYED\n\nRESOURCES:\nresource A\nresource B\n\n"),
rel: releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Resources: "resource A\nresource B\n",
}),
},
{
name: "get status of a deployed release with test suite",
args: []string{"flummoxed-chickadee"},
expected: outputWithStatus(
fmt.Sprintf("DEPLOYED\n\nTEST SUITE:\nLast Started: %s\nLast Completed: %s\n\n", dateString, dateString) +
fmt.Sprint("TEST \tSTATUS \tINFO \tSTARTED \tCOMPLETED \n") +
fmt.Sprintf("test run 1\tSUCCESS \textra info\t%s\t%s\n", dateString, dateString) +
fmt.Sprintf("test run 2\tFAILURE \t \t%s\t%s\n", dateString, dateString)),
rel: releaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
LastTestSuiteRun: &release.TestSuite{
StartedAt: &date,
CompletedAt: &date,
Results: []*release.TestRun{
{
Name: "test run 1",
Status: release.TestRun_SUCCESS,
Info: "extra info",
StartedAt: &date,
CompletedAt: &date,
},
{
Name: "test run 2",
Status: release.TestRun_FAILURE,
StartedAt: &date,
CompletedAt: &date,
},
},
},
}),
},
}
scmd := func(c *fakeReleaseClient, out io.Writer) *cobra.Command {
return newStatusCmd(c, out)
}
var buf bytes.Buffer
for _, tt := range tests {
c := &fakeReleaseClient{
rels: []*release.Release{tt.rel},
}
cmd := scmd(c, &buf)
cmd.ParseFlags(tt.flags)
err := cmd.RunE(cmd, tt.args)
if (err != nil) != tt.err {
t.Errorf("%q. expected error, got '%v'", tt.name, err)
}
expected := strings.Replace(tt.expected, " ", "", -1)
got := strings.Replace(buf.String(), " ", "", -1)
if expected != got {
t.Errorf("%q. expected\n%q\ngot\n%q", tt.name, expected, got)
}
buf.Reset()
}
}
func outputWithStatus(status string) string {
return fmt.Sprintf("LAST DEPLOYED: %s\nNAMESPACE: \nSTATUS: %s",
dateString,
status)
}
func releaseMockWithStatus(status *release.Status) *release.Release {
return &release.Release{
Name: "flummoxed-chickadee",
Info: &release.Info{
FirstDeployed: &date,
LastDeployed: &date,
Status: status,
},
}
}

@ -33,6 +33,7 @@ import (
"k8s.io/helm/pkg/storage/driver"
"k8s.io/helm/pkg/tiller"
"k8s.io/helm/pkg/tiller/environment"
"k8s.io/helm/pkg/version"
)
const (
@ -104,8 +105,9 @@ func start(c *cobra.Command, args []string) {
os.Exit(1)
}
fmt.Printf("Tiller is listening on %s\n", grpcAddr)
fmt.Printf("Probes server is listening on %s\n", probeAddr)
fmt.Printf("Starting Tiller %s\n", version.GetVersion())
fmt.Printf("GRPC listening on %s\n", grpcAddr)
fmt.Printf("Probes listening on %s\n", probeAddr)
fmt.Printf("Storage driver is %s\n", env.Releases.Name())
if enableTracing {

@ -74,16 +74,38 @@ The `sha256sum` function can be used together with the `include`
function to ensure a deployments template section is updated if another
spec changes:
```
```yaml
kind: Deployment
spec:
template:
metadata:
annotations:
checksum/config: {{ include (print $.Chart.Name "/templates/secret.yaml") . | sha256sum }}
[...]
[...]
```
## Tell Tiller Not To Delete a Resource
Sometimes there are resources that should not be deleted when Helm runs a
`helm delete`. Chart developers can add an annotation to a resource to prevent
it from being deleted.
```yaml
kind: Secret
metadata:
annotations:
"helm.sh/resource-policy": keep
[...]
```
(Quotation marks are required)
The annotation `"helm.sh/resource-policy": keep` instructs Tiller to skip this
resource during a `helm delete` operation. _However_, this resource becomes
orphaned. Helm will no longer manage it in any way. This can lead to problems
if using `helm install --replace` on a release that has already been deleted, but
has kept resources.
## Using "Partials" and Template Includes
Sometimes you want to create some reusable parts in your chart, whether

@ -0,0 +1,13 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{.Release.Name}}-service-test"
annotations:
"helm.sh/hook": test-success
spec:
containers:
- name: curl
image: radial/busyboxplus:curl
command: ['curl']
args: [ '{{ template "fullname" .}}:{{default 80 .Values.httpPort}}' ]
restartPolicy: Never

@ -189,7 +189,7 @@ Setting `TILLER_TAG=canary` will get the latest snapshot of master.
Because Tiller stores its data in Kubernetes ConfigMaps, you can safely
delete and re-install Tiller without worrying about losing any data. The
recommended way of deleting Tiller is with `kubectl delete deployment
tiller-deploy --namespace kube-system`
tiller-deploy --namespace kube-system`, or more concisely `helm reset`.
Tiller can then be re-installed from the client with:

@ -51,17 +51,26 @@ with `helm repo add...`.
**Q: How do I configure Helm, but not install Tiller?**
By default, `helm init` will ensure that the local `$HELM_HOME` is configured,
A: By default, `helm init` will ensure that the local `$HELM_HOME` is configured,
and then install Tiller on your cluster. To locally configure, but not install
Tiller, use `helm init --client-only`.
**Q: How do I manually install Tiller on the cluster?**
Tiller is installed as a Kubernetes `deployment`. You can get the manifest
A: Tiller is installed as a Kubernetes `deployment`. You can get the manifest
by running `helm init --dry-run --debug`, and then manually install it with
`kubectl`. It is suggested that you do not remove or change the labels on that
deployment, as they are sometimes used by supporting scripts and tools.
**Q: Why do I get `Error response from daemon: target is unknown` during Tiller install?**
A: Users have reported being unable to install Tiller on Kubernetes instances that
are using Docker 1.13.0. The root cause of this was a bug in Docker that made
that one version incompatible with images pushed to the Docker registry by
earlier versions of Docker.
This [issue](https://github.com/docker/docker/issues/30083) was fixed shortly
after the release, and is available in Docker 1.13.1-RC1 and later.
## Getting Started

@ -324,16 +324,17 @@ cluster. And as we can see above, it shows that our new values from
`panda.yaml` were deployed to the cluster.
Now, if something does not go as planned during a release, it is easy to
roll back to a previous release.
roll back to a previous release using `helm rollback [RELEASE] [REVISION]`.
```console
$ helm rollback happy-panda --version 1
$ helm rollback happy-panda 1
```
The above rolls back our happy-panda to its very first release version.
A release version is an incremental revision. Every time an install,
upgrade, or rollback happens, the revision number is incremented by 1.
The first revision number is always 1.
The first revision number is always 1. And we can use `helm history [RELEASE]`
to see revision numbers for a certain release.
## Helpful Options for Install/Upgrade/Rollback
There are several other helpful options you can specify for customizing the

@ -21,6 +21,7 @@ import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
@ -29,6 +30,7 @@ import (
"k8s.io/helm/cmd/helm/helmpath"
"k8s.io/helm/pkg/provenance"
"k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/urlutil"
)
// VerificationStrategy describes a strategy for determining whether to verify a chart.
@ -49,6 +51,9 @@ const (
VerifyLater
)
// ErrNoOwnerRepo indicates that a given chart URL can't be found in any repos.
var ErrNoOwnerRepo = errors.New("could not find a repo containing the given URL")
// ChartDownloader handles downloading a chart.
//
// It is capable of performing verifications on charts as well.
@ -75,6 +80,7 @@ type ChartDownloader struct {
// Returns a string path to the location where the file was downloaded and a verification
// (if provenance was verified), or an error if something bad happened.
func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *provenance.Verification, error) {
var r repo.Getter
u, r, err := c.ResolveChartVersion(ref, version)
if err != nil {
return "", nil, err
@ -121,16 +127,19 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
// ResolveChartVersion resolves a chart reference to a URL.
//
// It returns the URL as well as a preconfigured repo.Getter that can fetch
// the URL.
//
// A reference may be an HTTP URL, a 'reponame/chartname' reference, or a local path.
//
// A version is a SemVer string (1.2.3-beta.1+f334a6789).
//
// - For fully qualified URLs, the version will be ignored (since URLs aren't versioned)
// - For fully qualified URLs, the version will be ignored (since URLs aren't versioned)
// - For a chart reference
// * If version is non-empty, this will return the URL for that version
// * If version is empty, this will return the URL for the latest version
// * If no version can be found, an error is returned
func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, *repo.ChartRepository, error) {
// * If no version can be found, an error is returned
func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, repo.Getter, error) {
u, err := url.Parse(ref)
if err != nil {
return nil, nil, fmt.Errorf("invalid chart URL format: %s", ref)
@ -138,64 +147,67 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, *r
rf, err := repo.LoadRepositoriesFile(c.HelmHome.RepositoryFile())
if err != nil {
return nil, nil, err
return u, nil, err
}
var (
chartName string
rc *repo.Entry
)
if u.IsAbs() && len(u.Host) > 0 && len(u.Path) > 0 {
// If it has a scheme and host and path, it's a full URL
p := strings.SplitN(strings.TrimLeft(u.Path, "/"), "-", 2)
if len(p) < 2 {
return nil, nil, fmt.Errorf("Seems that chart path is not in form of repo_url/path_to_chart, got: %s", u)
}
chartName = p[0]
u.Path = ""
rc, err = pickChartRepositoryConfigByURL(u.String(), rf.Repositories)
// In this case, we have to find the parent repo that contains this chart
// URL. And this is an unfortunate problem, as it requires actually going
// through each repo cache file and finding a matching URL. But basically
// we want to find the repo in case we have special SSL cert config
// for that repo.
rc, err := c.scanReposForURL(ref, rf)
if err != nil {
return nil, nil, err
}
} else {
// See if it's of the form: repo/path_to_chart
p := strings.SplitN(u.Path, "/", 2)
if len(p) < 2 {
return nil, nil, fmt.Errorf("Non-absolute URLs should be in form of repo_name/path_to_chart, got: %s", u)
// If there is no special config, return the default HTTP client and
// swallow the error.
if err == ErrNoOwnerRepo {
return u, http.DefaultClient, nil
}
return u, nil, err
}
r, err := repo.NewChartRepository(rc)
// If we get here, we don't need to go through the next phase of looking
// up the URL. We have it already. So we just return.
return u, r, err
}
repoName := p[0]
chartName = p[1]
rc, err = pickChartRepositoryConfigByName(repoName, rf.Repositories)
if err != nil {
return nil, nil, err
}
// See if it's of the form: repo/path_to_chart
p := strings.SplitN(u.Path, "/", 2)
if len(p) < 2 {
return u, nil, fmt.Errorf("Non-absolute URLs should be in form of repo_name/path_to_chart, got: %s", u)
}
repoName := p[0]
chartName := p[1]
rc, err := pickChartRepositoryConfigByName(repoName, rf.Repositories)
if err != nil {
return u, nil, err
}
r, err := repo.NewChartRepository(rc)
if err != nil {
return nil, nil, err
return u, nil, err
}
// Next, we need to load the index, and actually look up the chart.
i, err := repo.LoadIndexFile(c.HelmHome.CacheIndex(r.Config.Name))
if err != nil {
return nil, nil, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err)
return u, r, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err)
}
cv, err := i.Get(chartName, version)
if err != nil {
return nil, nil, fmt.Errorf("chart %q not found in %s index. (try 'helm repo update'). %s", chartName, r.Config.Name, err)
return u, r, fmt.Errorf("chart %q not found in %s index. (try 'helm repo update'). %s", chartName, r.Config.Name, err)
}
if len(cv.URLs) == 0 {
return nil, nil, fmt.Errorf("chart %q has no downloadable URLs", ref)
return u, r, fmt.Errorf("chart %q has no downloadable URLs", ref)
}
// TODO: Seems that picking first URL is not fully correct
u, err = url.Parse(cv.URLs[0])
if err != nil {
return nil, nil, fmt.Errorf("invalid chart URL format: %s", ref)
return u, r, fmt.Errorf("invalid chart URL format: %s", ref)
}
return u, r, nil
@ -264,11 +276,48 @@ func pickChartRepositoryConfigByName(name string, cfgs []*repo.Entry) (*repo.Ent
return nil, fmt.Errorf("repo %s not found", name)
}
func pickChartRepositoryConfigByURL(u string, cfgs []*repo.Entry) (*repo.Entry, error) {
for _, rc := range cfgs {
if rc.URL == u {
return rc, nil
// scanReposForURL scans all repos to find which repo contains the given URL.
//
// This will attempt to find the given URL in all of the known repositories files.
//
// If the URL is found, this will return the repo entry that contained that URL.
//
// If all of the repos are checked, but the URL is not found, an ErrNoOwnerRepo
// error is returned.
//
// Other errors may be returned when repositories cannot be loaded or searched.
//
// Technically, the fact that a URL is not found in a repo is not a failure indication.
// Charts are not required to be included in an index before they are valid. So
// be mindful of this case.
//
// The same URL can technically exist in two or more repositories. This algorithm
// will return the first one it finds. Order is determined by the order of repositories
// in the repositories.yaml file.
func (c *ChartDownloader) scanReposForURL(u string, rf *repo.RepoFile) (*repo.Entry, error) {
// FIXME: This is far from optimal. Larger installations and index files will
// incur a performance hit for this type of scanning.
for _, rc := range rf.Repositories {
r, err := repo.NewChartRepository(rc)
if err != nil {
return nil, err
}
i, err := repo.LoadIndexFile(c.HelmHome.CacheIndex(r.Config.Name))
if err != nil {
return nil, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err)
}
for _, entry := range i.Entries {
for _, ver := range entry {
for _, dl := range ver.URLs {
if urlutil.Equal(u, dl) {
return rc, nil
}
}
}
}
}
return nil, fmt.Errorf("repo with URL %s not found", u)
// This means that there is no repo file for the given URL.
return nil, ErrNoOwnerRepo
}

@ -26,6 +26,7 @@ import (
"testing"
"k8s.io/helm/cmd/helm/helmpath"
"k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/repo/repotest"
)
@ -256,3 +257,33 @@ func TestDownloadTo_VerifyLater(t *testing.T) {
return
}
}
func TestScanReposForURL(t *testing.T) {
hh := helmpath.Home("testdata/helmhome")
c := ChartDownloader{
HelmHome: hh,
Out: os.Stderr,
Verify: VerifyLater,
}
u := "http://example.com/alpine-0.2.0.tgz"
rf, err := repo.LoadRepositoriesFile(c.HelmHome.RepositoryFile())
if err != nil {
t.Fatal(err)
}
entry, err := c.scanReposForURL(u, rf)
if err != nil {
t.Fatal(err)
}
if entry.Name != "testing" {
t.Errorf("Unexpected repo %q for URL %q", entry.Name, u)
}
// A lookup failure should produce an ErrNoOwnerRepo
u = "https://no.such.repo/foo/bar-1.23.4.tgz"
if _, err = c.scanReposForURL(u, rf); err != ErrNoOwnerRepo {
t.Fatalf("expected ErrNoOwnerRepo, got %v", err)
}
}

@ -169,6 +169,9 @@ func (m *Manager) resolve(req *chartutil.Requirements, repoNames map[string]stri
}
// downloadAll takes a list of dependencies and downloads them into charts/
//
// It will delete versions of the chart that exist on disk and might cause
// a conflict.
func (m *Manager) downloadAll(deps []*chartutil.Dependency) error {
repos, err := m.loadChartRepositories()
if err != nil {
@ -195,6 +198,9 @@ func (m *Manager) downloadAll(deps []*chartutil.Dependency) error {
fmt.Fprintf(m.Out, "Saving %d charts\n", len(deps))
for _, dep := range deps {
if err := m.safeDeleteDep(dep.Name, destPath); err != nil {
return err
}
fmt.Fprintf(m.Out, "Downloading %s from repo %s\n", dep.Name, dep.Repository)
// Any failure to resolve/download a chart should fail:
@ -211,6 +217,39 @@ func (m *Manager) downloadAll(deps []*chartutil.Dependency) error {
return nil
}
// safeDeleteDep deletes any versions of the given dependency in the given directory.
//
// It does this by first matching the file name to an expected pattern, then loading
// the file to verify that it is a chart with the same name as the given name.
//
// Because it requires tar file introspection, it is more intensive than a basic delete.
//
// This will only return errors that should stop processing entirely. Other errors
// will emit log messages or be ignored.
func (m *Manager) safeDeleteDep(name, dir string) error {
files, err := filepath.Glob(filepath.Join(dir, name+"-*.tgz"))
if err != nil {
// Only for ErrBadPattern
return err
}
for _, fname := range files {
ch, err := chartutil.LoadFile(fname)
if err != nil {
fmt.Fprintf(m.Out, "Could not verify %s for deletion: %s (Skipping)", fname, err)
continue
}
if ch.Metadata.Name != name {
// This is not the file you are looking for.
continue
}
if err := os.Remove(fname); err != nil {
fmt.Fprintf(m.Out, "Could not delete %s: %s (Skipping)", fname, err)
continue
}
}
return nil
}
// hasAllRepos ensures that all of the referenced deps are in the local repo cache.
func (m *Manager) hasAllRepos(deps []*chartutil.Dependency) error {
rf, err := repo.LoadRepositoriesFile(m.HelmHome.RepositoryFile())

@ -17,6 +17,8 @@ limitations under the License.
package helm // import "k8s.io/helm/pkg/helm"
import (
"io"
"golang.org/x/net/context"
"google.golang.org/grpc"
@ -244,6 +246,19 @@ func (h *Client) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.Get
return h.history(ctx, req)
}
// RunReleaseTest executes a pre-defined test on a release
func (h *Client) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan 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.
func (h *Client) list(ctx context.Context, req *rls.ListReleasesRequest) (*rls.ListReleasesResponse, error) {
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
@ -356,3 +371,41 @@ func (h *Client) history(ctx context.Context, req *rls.GetHistoryRequest) (*rls.
rlc := rls.NewReleaseServiceClient(c)
return rlc.GetHistory(ctx, req)
}
// Executes tiller.TestRelease RPC.
func (h *Client) test(ctx context.Context, req *rls.TestReleaseRequest) (<-chan *rls.TestReleaseResponse, <-chan error) {
errc := make(chan error, 1)
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil {
errc <- err
return nil, errc
}
ch := make(chan *rls.TestReleaseResponse, 1)
go func() {
defer close(errc)
defer close(ch)
defer c.Close()
rlc := rls.NewReleaseServiceClient(c)
s, err := rlc.RunReleaseTest(ctx, req)
if err != nil {
errc <- err
return
}
for {
msg, err := s.Recv()
if err == io.EOF {
return
}
if err != nil {
errc <- err
return
}
ch <- msg
}
}()
return ch, errc
}

@ -34,4 +34,5 @@ type Interface interface {
ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error)
ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error)
GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error)
RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error)
}

@ -66,6 +66,8 @@ type options struct {
histReq rls.GetHistoryRequest
// resetValues instructs Tiller to reset values to their defaults.
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").
@ -174,6 +176,13 @@ func DeleteTimeout(timeout int64) DeleteOption {
}
}
// ReleaseTestTimeout 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
func RollbackTimeout(timeout int64) RollbackOption {
return func(opts *options) {
@ -364,3 +373,7 @@ func NewContext() context.Context {
md := metadata.Pairs("x-helm-api-client", version.Version)
return metadata.NewContext(context.TODO(), md)
}
// ReleaseTestOption allows configuring optional request data for
// issuing a TestRelease rpc.
type ReleaseTestOption func(*options)

@ -14,27 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package portforwarder
import (
"fmt"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/helm/pkg/kube"
)
// TODO refactor out this global var
var tillerTunnel *kube.Tunnel
func newTillerPortForwarder(namespace, context string) (*kube.Tunnel, error) {
config, client, err := getKubeClient(context)
if err != nil {
return nil, err
}
func New(namespace string, client *internalclientset.Clientset, config *restclient.Config) (*kube.Tunnel, error) {
podName, err := getTillerPodName(client.Core(), namespace)
if err != nil {
return nil, err

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package portforwarder
import (
"testing"

@ -33,6 +33,7 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"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/fields"
"k8s.io/kubernetes/pkg/kubectl"
@ -305,6 +306,7 @@ func perform(c *Client, namespace string, infos Result, fn ResourceActorFunc) er
if len(infos) == 0 {
return ErrNoObjectsVisited
}
for _, info := range infos {
if err := fn(info); err != nil {
return err
@ -610,3 +612,43 @@ func scrubValidationError(err error) error {
}
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(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]
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
}

@ -18,6 +18,7 @@ package kube
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
@ -37,6 +38,8 @@ import (
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/watch"
watchjson "k8s.io/kubernetes/pkg/watch/json"
)
func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
@ -44,10 +47,18 @@ func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
}
func newPod(name string) api.Pod {
return newPodWithStatus(name, api.PodStatus{}, "")
}
func newPodWithStatus(name string, status api.PodStatus, namespace string) api.Pod {
ns := api.NamespaceDefault
if namespace != "" {
ns = namespace
}
return api.Pod{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: api.NamespaceDefault,
Namespace: ns,
},
Spec: api.PodSpec{
Containers: []api.Container{{
@ -56,6 +67,7 @@ func newPod(name string) api.Pod {
Ports: []api.ContainerPort{{Name: "http", ContainerPort: 80}},
}},
},
Status: status,
}
}
@ -102,6 +114,32 @@ func (f *fakeReaperFactory) Reaper(mapping *meta.RESTMapping) (kubectl.Reaper, e
return f.reaper, nil
}
func newEventResponse(code int, e *watch.Event) (*http.Response, error) {
dispatchedEvent, err := encodeAndMarshalEvent(e)
if err != nil {
return nil, err
}
header := http.Header{}
header.Set("Content-Type", runtime.ContentTypeJSON)
body := ioutil.NopCloser(bytes.NewReader(dispatchedEvent))
return &http.Response{StatusCode: 200, Header: header, Body: body}, nil
}
func encodeAndMarshalEvent(e *watch.Event) ([]byte, error) {
encodedEvent, err := watchjson.Object(testapi.Default.Codec(), e)
if err != nil {
return nil, err
}
marshaledEvent, err := json.Marshal(encodedEvent)
if err != nil {
return nil, err
}
return marshaledEvent, nil
}
func TestUpdate(t *testing.T) {
listA := newPodList("starfish", "otter", "squid")
listB := newPodList("starfish", "otter", "dolphin")
@ -305,6 +343,72 @@ func TestPerform(t *testing.T) {
}
}
func TestWaitAndGetCompletedPodPhase(t *testing.T) {
tests := []struct {
podPhase api.PodPhase
expectedPhase api.PodPhase
err bool
errMessage string
}{
{
podPhase: api.PodPending,
expectedPhase: api.PodUnknown,
err: true,
errMessage: "timed out waiting for the condition",
}, {
podPhase: api.PodRunning,
expectedPhase: api.PodUnknown,
err: true,
errMessage: "timed out waiting for the condition",
}, {
podPhase: api.PodSucceeded,
expectedPhase: api.PodSucceeded,
}, {
podPhase: api.PodFailed,
expectedPhase: api.PodFailed,
},
}
for _, tt := range tests {
f, tf, codec, ns := cmdtesting.NewAPIFactory()
actions := make(map[string]string)
var testPodList api.PodList
testPodList.Items = append(testPodList.Items, newPodWithStatus("bestpod", api.PodStatus{Phase: tt.podPhase}, "test"))
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
switch {
case p == "/namespaces/test/pods/bestpod" && m == "GET":
return newResponse(200, &testPodList.Items[0])
case p == "/watch/namespaces/test/pods/bestpod" && m == "GET":
event := watch.Event{Type: watch.Added, Object: &testPodList.Items[0]}
return newEventResponse(200, &event)
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
c := &Client{Factory: f}
phase, err := c.WaitAndGetCompletedPodPhase("test", objBody(codec, &testPodList), 1*time.Second)
if (err != nil) != tt.err {
t.Fatalf("Expected error but there was none.")
}
if err != nil && err.Error() != tt.errMessage {
t.Fatalf("Expected error %s, got %s", tt.errMessage, err.Error())
}
if phase != tt.expectedPhase {
t.Fatalf("Expected pod phase %s, got %s", tt.expectedPhase, phase)
}
}
}
func TestReal(t *testing.T) {
t.Skip("This is a live test, comment this line to run")
c := New(nil)

@ -10,12 +10,16 @@ It is generated from these files:
hapi/release/info.proto
hapi/release/release.proto
hapi/release/status.proto
hapi/release/test_run.proto
hapi/release/test_suite.proto
It has these top-level messages:
Hook
Info
Release
Status
TestRun
TestSuite
*/
package release
@ -38,15 +42,16 @@ const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Hook_Event int32
const (
Hook_UNKNOWN Hook_Event = 0
Hook_PRE_INSTALL Hook_Event = 1
Hook_POST_INSTALL Hook_Event = 2
Hook_PRE_DELETE Hook_Event = 3
Hook_POST_DELETE Hook_Event = 4
Hook_PRE_UPGRADE Hook_Event = 5
Hook_POST_UPGRADE Hook_Event = 6
Hook_PRE_ROLLBACK Hook_Event = 7
Hook_POST_ROLLBACK Hook_Event = 8
Hook_UNKNOWN Hook_Event = 0
Hook_PRE_INSTALL Hook_Event = 1
Hook_POST_INSTALL Hook_Event = 2
Hook_PRE_DELETE Hook_Event = 3
Hook_POST_DELETE Hook_Event = 4
Hook_PRE_UPGRADE Hook_Event = 5
Hook_POST_UPGRADE Hook_Event = 6
Hook_PRE_ROLLBACK Hook_Event = 7
Hook_POST_ROLLBACK Hook_Event = 8
Hook_RELEASE_TEST_SUCCESS Hook_Event = 9
)
var Hook_Event_name = map[int32]string{
@ -59,17 +64,19 @@ var Hook_Event_name = map[int32]string{
6: "POST_UPGRADE",
7: "PRE_ROLLBACK",
8: "POST_ROLLBACK",
9: "RELEASE_TEST_SUCCESS",
}
var Hook_Event_value = map[string]int32{
"UNKNOWN": 0,
"PRE_INSTALL": 1,
"POST_INSTALL": 2,
"PRE_DELETE": 3,
"POST_DELETE": 4,
"PRE_UPGRADE": 5,
"POST_UPGRADE": 6,
"PRE_ROLLBACK": 7,
"POST_ROLLBACK": 8,
"UNKNOWN": 0,
"PRE_INSTALL": 1,
"POST_INSTALL": 2,
"PRE_DELETE": 3,
"POST_DELETE": 4,
"PRE_UPGRADE": 5,
"POST_UPGRADE": 6,
"PRE_ROLLBACK": 7,
"POST_ROLLBACK": 8,
"RELEASE_TEST_SUCCESS": 9,
}
func (x Hook_Event) String() string {
@ -112,26 +119,27 @@ func init() {
func init() { proto.RegisterFile("hapi/release/hook.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 321 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8f, 0x4d, 0x6e, 0xea, 0x30,
0x14, 0x85, 0x5f, 0x20, 0x24, 0x70, 0xe1, 0xd1, 0xd4, 0x93, 0x5a, 0x4c, 0x8a, 0x18, 0x31, 0x72,
0x2a, 0xaa, 0x2e, 0x00, 0x8a, 0xd5, 0x56, 0x44, 0x01, 0x99, 0xa0, 0x4a, 0x9d, 0x20, 0xa3, 0x1a,
0x88, 0x20, 0x71, 0x44, 0x4c, 0xd7, 0xd3, 0xf5, 0x75, 0x15, 0x95, 0x9d, 0x1f, 0x75, 0x76, 0xfd,
0xdd, 0xcf, 0xc7, 0x3e, 0x70, 0x77, 0xe4, 0x59, 0xec, 0x5f, 0xc4, 0x59, 0xf0, 0x5c, 0xf8, 0x47,
0x29, 0x4f, 0x24, 0xbb, 0x48, 0x25, 0x51, 0x4f, 0x2f, 0x48, 0xb9, 0x18, 0xdc, 0x1f, 0xa4, 0x3c,
0x9c, 0x85, 0x6f, 0x76, 0xbb, 0xeb, 0xde, 0x57, 0x71, 0x22, 0x72, 0xc5, 0x93, 0xac, 0xd0, 0x47,
0x3f, 0x0d, 0xb0, 0x5f, 0xa5, 0x3c, 0x21, 0x04, 0x76, 0xca, 0x13, 0x81, 0xad, 0xa1, 0x35, 0xee,
0x30, 0x33, 0x6b, 0x76, 0x8a, 0xd3, 0x4f, 0xdc, 0x28, 0x98, 0x9e, 0x35, 0xcb, 0xb8, 0x3a, 0xe2,
0x66, 0xc1, 0xf4, 0x8c, 0x06, 0xd0, 0x4e, 0x78, 0x1a, 0xef, 0x45, 0xae, 0xb0, 0x6d, 0x78, 0x7d,
0x46, 0x0f, 0xe0, 0x88, 0x2f, 0x91, 0xaa, 0x1c, 0xb7, 0x86, 0xcd, 0x71, 0x7f, 0x82, 0xc9, 0xdf,
0x0f, 0x12, 0xfd, 0x36, 0xa1, 0x5a, 0x60, 0xa5, 0x87, 0x9e, 0xa0, 0x7d, 0xe6, 0xb9, 0xda, 0x5e,
0xae, 0x29, 0x76, 0x86, 0xd6, 0xb8, 0x3b, 0x19, 0x90, 0xa2, 0x06, 0xa9, 0x6a, 0x90, 0xa8, 0xaa,
0xc1, 0x5c, 0xed, 0xb2, 0x6b, 0x3a, 0xfa, 0xb6, 0xa0, 0x65, 0x82, 0x50, 0x17, 0xdc, 0x4d, 0xb8,
0x08, 0x97, 0xef, 0xa1, 0xf7, 0x0f, 0xdd, 0x40, 0x77, 0xc5, 0xe8, 0xf6, 0x2d, 0x5c, 0x47, 0xd3,
0x20, 0xf0, 0x2c, 0xe4, 0x41, 0x6f, 0xb5, 0x5c, 0x47, 0x35, 0x69, 0xa0, 0x3e, 0x80, 0x56, 0xe6,
0x34, 0xa0, 0x11, 0xf5, 0x9a, 0xe6, 0x8a, 0x36, 0x4a, 0x60, 0x57, 0x19, 0x9b, 0xd5, 0x0b, 0x9b,
0xce, 0xa9, 0xd7, 0xaa, 0x33, 0x2a, 0xe2, 0x18, 0xc2, 0xe8, 0x96, 0x2d, 0x83, 0x60, 0x36, 0x7d,
0x5e, 0x78, 0x2e, 0xba, 0x85, 0xff, 0xc6, 0xa9, 0x51, 0x7b, 0xd6, 0xf9, 0x70, 0xcb, 0xde, 0x3b,
0xc7, 0x54, 0x79, 0xfc, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xa4, 0x2e, 0x6f, 0xbd, 0xc8, 0x01, 0x00,
0x00,
// 343 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x90, 0xdf, 0x6e, 0xa2, 0x40,
0x14, 0xc6, 0x17, 0x41, 0xd0, 0xa3, 0xeb, 0xb2, 0x93, 0x4d, 0x76, 0xe2, 0x4d, 0x8d, 0x57, 0x5e,
0x0d, 0x8d, 0x4d, 0x1f, 0x00, 0x75, 0xd2, 0x36, 0x12, 0x34, 0x03, 0xa6, 0x49, 0x6f, 0x08, 0xa6,
0xa3, 0x12, 0x85, 0x21, 0x82, 0x7d, 0x82, 0x3e, 0x55, 0x9f, 0xae, 0x99, 0xe1, 0x4f, 0x7a, 0x77,
0xf8, 0x9d, 0x1f, 0xdf, 0xcc, 0x37, 0xf0, 0xff, 0x14, 0xe7, 0x89, 0x73, 0xe5, 0x17, 0x1e, 0x17,
0xdc, 0x39, 0x09, 0x71, 0x26, 0xf9, 0x55, 0x94, 0x02, 0x0d, 0xe5, 0x82, 0xd4, 0x8b, 0xf1, 0xdd,
0x51, 0x88, 0xe3, 0x85, 0x3b, 0x6a, 0xb7, 0xbf, 0x1d, 0x9c, 0x32, 0x49, 0x79, 0x51, 0xc6, 0x69,
0x5e, 0xe9, 0xd3, 0x4f, 0x1d, 0x8c, 0x67, 0x21, 0xce, 0x08, 0x81, 0x91, 0xc5, 0x29, 0xc7, 0xda,
0x44, 0x9b, 0xf5, 0x99, 0x9a, 0x25, 0x3b, 0x27, 0xd9, 0x3b, 0xee, 0x54, 0x4c, 0xce, 0x92, 0xe5,
0x71, 0x79, 0xc2, 0x7a, 0xc5, 0xe4, 0x8c, 0xc6, 0xd0, 0x4b, 0xe3, 0x2c, 0x39, 0xf0, 0xa2, 0xc4,
0x86, 0xe2, 0xed, 0x37, 0xba, 0x07, 0x93, 0x7f, 0xf0, 0xac, 0x2c, 0x70, 0x77, 0xa2, 0xcf, 0x46,
0x73, 0x4c, 0x7e, 0x5e, 0x90, 0xc8, 0xb3, 0x09, 0x95, 0x02, 0xab, 0x3d, 0xf4, 0x08, 0xbd, 0x4b,
0x5c, 0x94, 0xd1, 0xf5, 0x96, 0x61, 0x73, 0xa2, 0xcd, 0x06, 0xf3, 0x31, 0xa9, 0x6a, 0x90, 0xa6,
0x06, 0x09, 0x9b, 0x1a, 0xcc, 0x92, 0x2e, 0xbb, 0x65, 0xd3, 0x2f, 0x0d, 0xba, 0x2a, 0x08, 0x0d,
0xc0, 0xda, 0xf9, 0x6b, 0x7f, 0xf3, 0xea, 0xdb, 0xbf, 0xd0, 0x1f, 0x18, 0x6c, 0x19, 0x8d, 0x5e,
0xfc, 0x20, 0x74, 0x3d, 0xcf, 0xd6, 0x90, 0x0d, 0xc3, 0xed, 0x26, 0x08, 0x5b, 0xd2, 0x41, 0x23,
0x00, 0xa9, 0xac, 0xa8, 0x47, 0x43, 0x6a, 0xeb, 0xea, 0x17, 0x69, 0xd4, 0xc0, 0x68, 0x32, 0x76,
0xdb, 0x27, 0xe6, 0xae, 0xa8, 0xdd, 0x6d, 0x33, 0x1a, 0x62, 0x2a, 0xc2, 0x68, 0xc4, 0x36, 0x9e,
0xb7, 0x70, 0x97, 0x6b, 0xdb, 0x42, 0x7f, 0xe1, 0xb7, 0x72, 0x5a, 0xd4, 0x43, 0x18, 0xfe, 0x31,
0xea, 0x51, 0x37, 0xa0, 0x51, 0x48, 0x83, 0x30, 0x0a, 0x76, 0xcb, 0x25, 0x0d, 0x02, 0xbb, 0xbf,
0xe8, 0xbf, 0x59, 0xf5, 0x8b, 0xec, 0x4d, 0x55, 0xf2, 0xe1, 0x3b, 0x00, 0x00, 0xff, 0xff, 0xdf,
0xef, 0x1c, 0xfd, 0xe2, 0x01, 0x00, 0x00,
}

@ -21,6 +21,8 @@ type Info struct {
LastDeployed *google_protobuf.Timestamp `protobuf:"bytes,3,opt,name=last_deployed,json=lastDeployed" json:"last_deployed,omitempty"`
// Deleted tracks when this object was deleted.
Deleted *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=deleted" json:"deleted,omitempty"`
// Description is human-friendly "log entry" about this release.
Description string `protobuf:"bytes,5,opt,name=Description" json:"Description,omitempty"`
}
func (m *Info) Reset() { *m = Info{} }
@ -63,19 +65,20 @@ func init() {
func init() { proto.RegisterFile("hapi/release/info.proto", fileDescriptor1) }
var fileDescriptor1 = []byte{
// 212 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcf, 0x48, 0x2c, 0xc8,
0xd4, 0x2f, 0x4a, 0xcd, 0x49, 0x4d, 0x2c, 0x4e, 0xd5, 0xcf, 0xcc, 0x4b, 0xcb, 0xd7, 0x2b, 0x28,
0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x01, 0x49, 0xe8, 0x41, 0x25, 0xa4, 0xe4, 0xd3, 0xf3, 0xf3, 0xd3,
0x73, 0x52, 0xf5, 0xc1, 0x72, 0x49, 0xa5, 0x69, 0xfa, 0x25, 0x99, 0xb9, 0xa9, 0xc5, 0x25, 0x89,
0xb9, 0x05, 0x10, 0xe5, 0x52, 0x92, 0x28, 0xe6, 0x14, 0x97, 0x24, 0x96, 0x94, 0x16, 0x43, 0xa4,
0x94, 0xde, 0x31, 0x72, 0xb1, 0x78, 0xe6, 0xa5, 0xe5, 0x0b, 0xe9, 0x70, 0xb1, 0x41, 0x24, 0x24,
0x18, 0x15, 0x18, 0x35, 0xb8, 0x8d, 0x44, 0xf4, 0x90, 0xed, 0xd0, 0x0b, 0x06, 0xcb, 0x05, 0x41,
0xd5, 0x08, 0x39, 0x72, 0xf1, 0xa5, 0x65, 0x16, 0x15, 0x97, 0xc4, 0xa7, 0xa4, 0x16, 0xe4, 0xe4,
0x57, 0xa6, 0xa6, 0x48, 0x30, 0x81, 0x75, 0x49, 0xe9, 0x41, 0xdc, 0xa2, 0x07, 0x73, 0x8b, 0x5e,
0x08, 0xcc, 0x2d, 0x41, 0xbc, 0x60, 0x1d, 0x2e, 0x50, 0x0d, 0x42, 0xf6, 0x5c, 0xbc, 0x39, 0x89,
0xc8, 0x26, 0x30, 0x13, 0x34, 0x81, 0x07, 0xa4, 0x01, 0x6e, 0x80, 0x09, 0x17, 0x7b, 0x4a, 0x6a,
0x4e, 0x6a, 0x49, 0x6a, 0x8a, 0x04, 0x0b, 0x41, 0xad, 0x30, 0xa5, 0x4e, 0x9c, 0x51, 0xec, 0x50,
0x3f, 0x25, 0xb1, 0x81, 0xd5, 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xeb, 0x9d, 0xa1, 0xf8,
0x67, 0x01, 0x00, 0x00,
// 235 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x8f, 0x31, 0x4f, 0xc3, 0x30,
0x10, 0x85, 0x95, 0x52, 0x5a, 0xd5, 0x6d, 0x19, 0x2c, 0x24, 0x42, 0x16, 0x22, 0xa6, 0x0e, 0xc8,
0x91, 0x80, 0x1d, 0x81, 0xba, 0xb0, 0x06, 0x26, 0x16, 0xe4, 0xe2, 0x73, 0xb1, 0xe4, 0xe6, 0x2c,
0xfb, 0x3a, 0xf0, 0x2f, 0xf8, 0xc9, 0xa8, 0xb6, 0x83, 0xd2, 0xa9, 0xab, 0xbf, 0xf7, 0x3e, 0xbf,
0x63, 0x57, 0xdf, 0xd2, 0x99, 0xc6, 0x83, 0x05, 0x19, 0xa0, 0x31, 0x9d, 0x46, 0xe1, 0x3c, 0x12,
0xf2, 0xc5, 0x01, 0x88, 0x0c, 0xaa, 0x9b, 0x2d, 0xe2, 0xd6, 0x42, 0x13, 0xd9, 0x66, 0xaf, 0x1b,
0x32, 0x3b, 0x08, 0x24, 0x77, 0x2e, 0xc5, 0xab, 0xeb, 0x23, 0x4f, 0x20, 0x49, 0xfb, 0x90, 0xd0,
0xed, 0xef, 0x88, 0x8d, 0x5f, 0x3b, 0x8d, 0xfc, 0x8e, 0x4d, 0x12, 0x28, 0x8b, 0xba, 0x58, 0xcd,
0xef, 0x2f, 0xc5, 0xf0, 0x0f, 0xf1, 0x16, 0x59, 0x9b, 0x33, 0xfc, 0x99, 0x5d, 0x68, 0xe3, 0x03,
0x7d, 0x2a, 0x70, 0x16, 0x7f, 0x40, 0x95, 0xa3, 0xd8, 0xaa, 0x44, 0xda, 0x22, 0xfa, 0x2d, 0xe2,
0xbd, 0xdf, 0xd2, 0x2e, 0x63, 0x63, 0x9d, 0x0b, 0xfc, 0x89, 0x2d, 0xad, 0x1c, 0x1a, 0xce, 0x4e,
0x1a, 0x16, 0x87, 0xc2, 0xbf, 0xe0, 0x91, 0x4d, 0x15, 0x58, 0x20, 0x50, 0xe5, 0xf8, 0x64, 0xb5,
0x8f, 0xf2, 0x9a, 0xcd, 0xd7, 0x10, 0xbe, 0xbc, 0x71, 0x64, 0xb0, 0x2b, 0xcf, 0xeb, 0x62, 0x35,
0x6b, 0x87, 0x4f, 0x2f, 0xb3, 0x8f, 0x69, 0xbe, 0x7a, 0x33, 0x89, 0xa6, 0x87, 0xbf, 0x00, 0x00,
0x00, 0xff, 0xff, 0x1a, 0x52, 0x8f, 0x9c, 0x89, 0x01, 0x00, 0x00,
}

@ -7,7 +7,7 @@ package release
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_protobuf1 "github.com/golang/protobuf/ptypes/any"
import _ "github.com/golang/protobuf/ptypes/any"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@ -55,12 +55,13 @@ func (Status_Code) EnumDescriptor() ([]byte, []int) { return fileDescriptor3, []
// Status defines the status of a release.
type Status struct {
Code Status_Code `protobuf:"varint,1,opt,name=code,enum=hapi.release.Status_Code" json:"code,omitempty"`
Details *google_protobuf1.Any `protobuf:"bytes,2,opt,name=details" json:"details,omitempty"`
Code Status_Code `protobuf:"varint,1,opt,name=code,enum=hapi.release.Status_Code" json:"code,omitempty"`
// Cluster resources as kubectl would print them.
Resources string `protobuf:"bytes,3,opt,name=resources" json:"resources,omitempty"`
// Contains the rendered templates/NOTES.txt if available
Notes string `protobuf:"bytes,4,opt,name=notes" json:"notes,omitempty"`
// LastTestSuiteRun provides results on the last test run on a release
LastTestSuiteRun *TestSuite `protobuf:"bytes,5,opt,name=last_test_suite_run,json=lastTestSuiteRun" json:"last_test_suite_run,omitempty"`
}
func (m *Status) Reset() { *m = Status{} }
@ -68,9 +69,9 @@ func (m *Status) String() string { return proto.CompactTextString(m)
func (*Status) ProtoMessage() {}
func (*Status) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{0} }
func (m *Status) GetDetails() *google_protobuf1.Any {
func (m *Status) GetLastTestSuiteRun() *TestSuite {
if m != nil {
return m.Details
return m.LastTestSuiteRun
}
return nil
}
@ -83,22 +84,24 @@ func init() {
func init() { proto.RegisterFile("hapi/release/status.proto", fileDescriptor3) }
var fileDescriptor3 = []byte{
// 269 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8f, 0x4d, 0x6f, 0x82, 0x40,
0x10, 0x86, 0xbb, 0x8a, 0x50, 0x46, 0x63, 0x36, 0x1b, 0x0f, 0xd0, 0xf4, 0x40, 0x3c, 0x71, 0xe9,
0x92, 0xd8, 0x5f, 0x60, 0xbb, 0xdb, 0xc6, 0x94, 0xa0, 0x01, 0x4d, 0x3f, 0x6e, 0x28, 0x53, 0x6b,
0x42, 0x58, 0xc3, 0xc2, 0xc1, 0x1f, 0xde, 0x7b, 0x03, 0x68, 0xea, 0x71, 0xf7, 0x79, 0xde, 0x79,
0x67, 0xc0, 0xfd, 0x49, 0x8f, 0x87, 0xa0, 0xc4, 0x1c, 0x53, 0x8d, 0x81, 0xae, 0xd2, 0xaa, 0xd6,
0xfc, 0x58, 0xaa, 0x4a, 0xb1, 0x51, 0x83, 0xf8, 0x19, 0xdd, 0xb9, 0x7b, 0xa5, 0xf6, 0x39, 0x06,
0x2d, 0xdb, 0xd6, 0xdf, 0x41, 0x5a, 0x9c, 0x3a, 0x71, 0xfa, 0x4b, 0xc0, 0x4c, 0xda, 0x24, 0x7b,
0x00, 0x63, 0xa7, 0x32, 0x74, 0x88, 0x47, 0xfc, 0xf1, 0xcc, 0xe5, 0xd7, 0x23, 0x78, 0xe7, 0xf0,
0x67, 0x95, 0x61, 0xdc, 0x6a, 0x8c, 0x83, 0x95, 0x61, 0x95, 0x1e, 0x72, 0xed, 0xf4, 0x3c, 0xe2,
0x0f, 0x67, 0x13, 0xde, 0xd5, 0xf0, 0x4b, 0x0d, 0x9f, 0x17, 0xa7, 0xf8, 0x22, 0xb1, 0x7b, 0xb0,
0x4b, 0xd4, 0xaa, 0x2e, 0x77, 0xa8, 0x9d, 0xbe, 0x47, 0x7c, 0x3b, 0xfe, 0xff, 0x60, 0x13, 0x18,
0x14, 0xaa, 0x42, 0xed, 0x18, 0x2d, 0xe9, 0x1e, 0xd3, 0x0f, 0x30, 0x9a, 0x46, 0x36, 0x04, 0x6b,
0x13, 0xbd, 0x45, 0xcb, 0xf7, 0x88, 0xde, 0xb0, 0x11, 0xdc, 0x0a, 0xb9, 0x0a, 0x97, 0x9f, 0x52,
0x50, 0xd2, 0x20, 0x21, 0x43, 0xb9, 0x96, 0x82, 0xf6, 0xd8, 0x18, 0x20, 0xd9, 0xac, 0x64, 0x9c,
0x48, 0x21, 0x05, 0xed, 0x33, 0x00, 0xf3, 0x65, 0xbe, 0x08, 0xa5, 0xa0, 0x46, 0x17, 0x0b, 0xe5,
0x7a, 0x11, 0xbd, 0xd2, 0xc1, 0x93, 0xfd, 0x65, 0x9d, 0x4f, 0xdb, 0x9a, 0xed, 0xbe, 0x8f, 0x7f,
0x01, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x7b, 0x5f, 0x3b, 0x4f, 0x01, 0x00, 0x00,
// 291 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x54, 0x90, 0xdf, 0x6a, 0xc2, 0x30,
0x14, 0xc6, 0x57, 0xad, 0x3a, 0x8f, 0x22, 0x21, 0x1b, 0xac, 0xca, 0x06, 0xc5, 0xab, 0xde, 0xac,
0x05, 0xf7, 0x04, 0xdb, 0x12, 0x87, 0xac, 0x54, 0x69, 0x2b, 0xfb, 0x73, 0x53, 0xaa, 0x9e, 0x39,
0xa1, 0x34, 0xd2, 0x24, 0x17, 0x7b, 0x88, 0xbd, 0xf3, 0x68, 0x2b, 0x74, 0x5e, 0x7e, 0xf9, 0xfd,
0x4e, 0xce, 0xc7, 0x81, 0xf1, 0x77, 0x7a, 0x3c, 0x78, 0x05, 0x66, 0x98, 0x4a, 0xf4, 0xa4, 0x4a,
0x95, 0x96, 0xee, 0xb1, 0x10, 0x4a, 0xd0, 0x61, 0x89, 0xdc, 0x13, 0x9a, 0xdc, 0x9d, 0x89, 0x0a,
0xa5, 0x4a, 0xa4, 0x3e, 0x28, 0xac, 0xe5, 0xc9, 0x78, 0x2f, 0xc4, 0x3e, 0x43, 0xaf, 0x4a, 0x1b,
0xfd, 0xe5, 0xa5, 0xf9, 0x4f, 0x8d, 0xa6, 0xbf, 0x2d, 0xe8, 0x46, 0xd5, 0xc7, 0xf4, 0x1e, 0xcc,
0xad, 0xd8, 0xa1, 0x65, 0xd8, 0x86, 0x33, 0x9a, 0x8d, 0xdd, 0xff, 0x1b, 0xdc, 0xda, 0x71, 0x9f,
0xc5, 0x0e, 0xc3, 0x4a, 0xa3, 0xb7, 0xd0, 0x2f, 0x50, 0x0a, 0x5d, 0x6c, 0x51, 0x5a, 0x6d, 0xdb,
0x70, 0xfa, 0x61, 0xf3, 0x40, 0xaf, 0xa1, 0x93, 0x0b, 0x85, 0xd2, 0x32, 0x2b, 0x52, 0x07, 0x3a,
0x87, 0xab, 0x2c, 0x95, 0x2a, 0x69, 0x1a, 0x26, 0x85, 0xce, 0xad, 0x8e, 0x6d, 0x38, 0x83, 0xd9,
0xcd, 0xf9, 0xc6, 0x18, 0xa5, 0x8a, 0x4a, 0x25, 0x24, 0xe5, 0x4c, 0x13, 0x75, 0x3e, 0x7d, 0x07,
0xb3, 0x6c, 0x42, 0x07, 0xd0, 0x5b, 0x07, 0xaf, 0xc1, 0xf2, 0x2d, 0x20, 0x17, 0x74, 0x08, 0x97,
0x8c, 0xaf, 0xfc, 0xe5, 0x07, 0x67, 0xc4, 0x28, 0x11, 0xe3, 0x3e, 0x8f, 0x39, 0x23, 0x2d, 0x3a,
0x02, 0x88, 0xd6, 0x2b, 0x1e, 0x46, 0x9c, 0x71, 0x46, 0xda, 0x14, 0xa0, 0x3b, 0x7f, 0x5c, 0xf8,
0x9c, 0x11, 0xb3, 0x1e, 0xf3, 0x79, 0xbc, 0x08, 0x5e, 0x48, 0xe7, 0xa9, 0xff, 0xd9, 0x3b, 0x15,
0xd8, 0x74, 0xab, 0x0b, 0x3d, 0xfc, 0x05, 0x00, 0x00, 0xff, 0xff, 0xd4, 0x11, 0x21, 0x30, 0x86,
0x01, 0x00, 0x00,
}

@ -0,0 +1,94 @@
// Code generated by protoc-gen-go.
// source: hapi/release/test_run.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 TestRun_Status int32
const (
TestRun_UNKNOWN TestRun_Status = 0
TestRun_SUCCESS TestRun_Status = 1
TestRun_FAILURE TestRun_Status = 2
)
var TestRun_Status_name = map[int32]string{
0: "UNKNOWN",
1: "SUCCESS",
2: "FAILURE",
}
var TestRun_Status_value = map[string]int32{
"UNKNOWN": 0,
"SUCCESS": 1,
"FAILURE": 2,
}
func (x TestRun_Status) String() string {
return proto.EnumName(TestRun_Status_name, int32(x))
}
func (TestRun_Status) EnumDescriptor() ([]byte, []int) { return fileDescriptor4, []int{0, 0} }
type TestRun struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Status TestRun_Status `protobuf:"varint,2,opt,name=status,enum=hapi.release.TestRun_Status" json:"status,omitempty"`
Info string `protobuf:"bytes,3,opt,name=info" json:"info,omitempty"`
StartedAt *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=started_at,json=startedAt" json:"started_at,omitempty"`
CompletedAt *google_protobuf.Timestamp `protobuf:"bytes,5,opt,name=completed_at,json=completedAt" json:"completed_at,omitempty"`
}
func (m *TestRun) Reset() { *m = TestRun{} }
func (m *TestRun) String() string { return proto.CompactTextString(m) }
func (*TestRun) ProtoMessage() {}
func (*TestRun) Descriptor() ([]byte, []int) { return fileDescriptor4, []int{0} }
func (m *TestRun) GetStartedAt() *google_protobuf.Timestamp {
if m != nil {
return m.StartedAt
}
return nil
}
func (m *TestRun) GetCompletedAt() *google_protobuf.Timestamp {
if m != nil {
return m.CompletedAt
}
return nil
}
func init() {
proto.RegisterType((*TestRun)(nil), "hapi.release.TestRun")
proto.RegisterEnum("hapi.release.TestRun_Status", TestRun_Status_name, TestRun_Status_value)
}
func init() { proto.RegisterFile("hapi/release/test_run.proto", fileDescriptor4) }
var fileDescriptor4 = []byte{
// 265 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x8f, 0x41, 0x4b, 0xfb, 0x40,
0x14, 0xc4, 0xff, 0xc9, 0xbf, 0x26, 0x64, 0x53, 0x24, 0xec, 0x29, 0x54, 0xc1, 0xd0, 0x53, 0x4e,
0xbb, 0x50, 0xbd, 0x78, 0xf0, 0x10, 0x4b, 0x05, 0x51, 0x22, 0x6c, 0x1a, 0x04, 0x2f, 0x65, 0xab,
0xaf, 0x35, 0x90, 0x64, 0x43, 0xf6, 0xe5, 0x8b, 0xf8, 0x89, 0x65, 0x93, 0xad, 0x78, 0xf3, 0xf6,
0x86, 0xf9, 0xcd, 0x30, 0x8f, 0x5c, 0x7c, 0xca, 0xae, 0xe2, 0x3d, 0xd4, 0x20, 0x35, 0x70, 0x04,
0x8d, 0xbb, 0x7e, 0x68, 0x59, 0xd7, 0x2b, 0x54, 0x74, 0x6e, 0x4c, 0x66, 0xcd, 0xc5, 0xd5, 0x51,
0xa9, 0x63, 0x0d, 0x7c, 0xf4, 0xf6, 0xc3, 0x81, 0x63, 0xd5, 0x80, 0x46, 0xd9, 0x74, 0x13, 0xbe,
0xfc, 0x72, 0x89, 0xbf, 0x05, 0x8d, 0x62, 0x68, 0x29, 0x25, 0xb3, 0x56, 0x36, 0x10, 0x3b, 0x89,
0x93, 0x06, 0x62, 0xbc, 0xe9, 0x0d, 0xf1, 0x34, 0x4a, 0x1c, 0x74, 0xec, 0x26, 0x4e, 0x7a, 0xbe,
0xba, 0x64, 0xbf, 0xfb, 0x99, 0x8d, 0xb2, 0x62, 0x64, 0x84, 0x65, 0x4d, 0x53, 0xd5, 0x1e, 0x54,
0xfc, 0x7f, 0x6a, 0x32, 0x37, 0xbd, 0x25, 0x44, 0xa3, 0xec, 0x11, 0x3e, 0x76, 0x12, 0xe3, 0x59,
0xe2, 0xa4, 0xe1, 0x6a, 0xc1, 0xa6, 0x7d, 0xec, 0xb4, 0x8f, 0x6d, 0x4f, 0xfb, 0x44, 0x60, 0xe9,
0x0c, 0xe9, 0x1d, 0x99, 0xbf, 0xab, 0xa6, 0xab, 0xc1, 0x86, 0xcf, 0xfe, 0x0c, 0x87, 0x3f, 0x7c,
0x86, 0x4b, 0x4e, 0xbc, 0x69, 0x1f, 0x0d, 0x89, 0x5f, 0xe6, 0x4f, 0xf9, 0xcb, 0x6b, 0x1e, 0xfd,
0x33, 0xa2, 0x28, 0xd7, 0xeb, 0x4d, 0x51, 0x44, 0x8e, 0x11, 0x0f, 0xd9, 0xe3, 0x73, 0x29, 0x36,
0x91, 0x7b, 0x1f, 0xbc, 0xf9, 0xf6, 0xc1, 0xbd, 0x37, 0x96, 0x5f, 0x7f, 0x07, 0x00, 0x00, 0xff,
0xff, 0x8d, 0xb9, 0xce, 0x57, 0x74, 0x01, 0x00, 0x00,
}

@ -0,0 +1,74 @@
// 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 {
// StartedAt indicates the date/time this test suite was kicked off
StartedAt *google_protobuf.Timestamp `protobuf:"bytes,1,opt,name=started_at,json=startedAt" json:"started_at,omitempty"`
// CompletedAt indicates the date/time this test suite was completed
CompletedAt *google_protobuf.Timestamp `protobuf:"bytes,2,opt,name=completed_at,json=completedAt" json:"completed_at,omitempty"`
// Results are the results of each segment of the test
Results []*TestRun `protobuf:"bytes,3,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) GetStartedAt() *google_protobuf.Timestamp {
if m != nil {
return m.StartedAt
}
return nil
}
func (m *TestSuite) GetCompletedAt() *google_protobuf.Timestamp {
if m != nil {
return m.CompletedAt
}
return nil
}
func (m *TestSuite) GetResults() []*TestRun {
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{
// 207 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x8f, 0xc1, 0x4a, 0x86, 0x40,
0x14, 0x85, 0x31, 0x21, 0x71, 0x74, 0x35, 0x10, 0x88, 0x11, 0x49, 0x2b, 0x57, 0x33, 0x60, 0xab,
0x16, 0x2d, 0xec, 0x11, 0xcc, 0x55, 0x1b, 0x19, 0xeb, 0x66, 0xc2, 0xe8, 0x0c, 0x73, 0xef, 0xbc,
0x5a, 0xcf, 0x17, 0xea, 0x18, 0x41, 0x8b, 0x7f, 0xfd, 0x7d, 0xe7, 0x9c, 0x7b, 0xd9, 0xdd, 0x97,
0xb2, 0xb3, 0x74, 0xa0, 0x41, 0x21, 0x48, 0x02, 0xa4, 0x01, 0xfd, 0x4c, 0x20, 0xac, 0x33, 0x64,
0x78, 0xbe, 0x61, 0x11, 0x70, 0x79, 0x3f, 0x19, 0x33, 0x69, 0x90, 0x3b, 0x1b, 0xfd, 0xa7, 0xa4,
0x79, 0x01, 0x24, 0xb5, 0xd8, 0x43, 0x2f, 0x6f, 0xff, 0xb7, 0x39, 0xbf, 0x1e, 0xf0, 0xe1, 0x3b,
0x62, 0x69, 0x0f, 0x48, 0xaf, 0x5b, 0x3f, 0x7f, 0x62, 0x0c, 0x49, 0x39, 0x82, 0x8f, 0x41, 0x51,
0x11, 0x55, 0x51, 0x9d, 0x35, 0xa5, 0x38, 0x06, 0xc4, 0x39, 0x20, 0xfa, 0x73, 0xa0, 0x4b, 0x83,
0xdd, 0x12, 0x7f, 0x66, 0xf9, 0xbb, 0x59, 0xac, 0x86, 0x10, 0xbe, 0xba, 0x18, 0xce, 0x7e, 0xfd,
0x96, 0xb8, 0x64, 0x89, 0x03, 0xf4, 0x9a, 0xb0, 0x88, 0xab, 0xb8, 0xce, 0x9a, 0x1b, 0xf1, 0xf7,
0x4b, 0xb1, 0xdd, 0xd8, 0xf9, 0xb5, 0x3b, 0xad, 0x97, 0xf4, 0x2d, 0x09, 0x6c, 0xbc, 0xde, 0xcb,
0x1f, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x8c, 0x59, 0x65, 0x4f, 0x37, 0x01, 0x00, 0x00,
}

@ -28,6 +28,8 @@ It has these top-level messages:
GetVersionResponse
GetHistoryRequest
GetHistoryResponse
TestReleaseRequest
TestReleaseResponse
*/
package services
@ -36,9 +38,9 @@ import fmt "fmt"
import math "math"
import hapi_chart3 "k8s.io/helm/pkg/proto/hapi/chart"
import hapi_chart "k8s.io/helm/pkg/proto/hapi/chart"
import hapi_release5 "k8s.io/helm/pkg/proto/hapi/release"
import hapi_release4 "k8s.io/helm/pkg/proto/hapi/release"
import hapi_release3 "k8s.io/helm/pkg/proto/hapi/release"
import hapi_release2 "k8s.io/helm/pkg/proto/hapi/release"
import hapi_release1 "k8s.io/helm/pkg/proto/hapi/release"
import hapi_version "k8s.io/helm/pkg/proto/hapi/version"
import (
@ -126,7 +128,7 @@ type ListReleasesRequest struct {
Filter string `protobuf:"bytes,4,opt,name=filter" json:"filter,omitempty"`
// SortOrder is the ordering directive used for sorting.
SortOrder ListSort_SortOrder `protobuf:"varint,5,opt,name=sort_order,json=sortOrder,enum=hapi.services.tiller.ListSort_SortOrder" json:"sort_order,omitempty"`
StatusCodes []hapi_release1.Status_Code `protobuf:"varint,6,rep,packed,name=status_codes,json=statusCodes,enum=hapi.release.Status_Code" json:"status_codes,omitempty"`
StatusCodes []hapi_release3.Status_Code `protobuf:"varint,6,rep,packed,name=status_codes,json=statusCodes,enum=hapi.release.Status_Code" json:"status_codes,omitempty"`
}
func (m *ListReleasesRequest) Reset() { *m = ListReleasesRequest{} }
@ -153,7 +155,7 @@ type ListReleasesResponse struct {
// Total is the total number of queryable releases.
Total int64 `protobuf:"varint,3,opt,name=total" json:"total,omitempty"`
// Releases is the list of found release objects.
Releases []*hapi_release3.Release `protobuf:"bytes,4,rep,name=releases" json:"releases,omitempty"`
Releases []*hapi_release5.Release `protobuf:"bytes,4,rep,name=releases" json:"releases,omitempty"`
}
func (m *ListReleasesResponse) Reset() { *m = ListReleasesResponse{} }
@ -161,7 +163,7 @@ func (m *ListReleasesResponse) String() string { return proto.Compact
func (*ListReleasesResponse) ProtoMessage() {}
func (*ListReleasesResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
func (m *ListReleasesResponse) GetReleases() []*hapi_release3.Release {
func (m *ListReleasesResponse) GetReleases() []*hapi_release5.Release {
if m != nil {
return m.Releases
}
@ -186,7 +188,7 @@ type GetReleaseStatusResponse struct {
// Name is the name of the release.
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// Info contains information about the release.
Info *hapi_release2.Info `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"`
Info *hapi_release4.Info `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"`
// Namesapce the release was released into
Namespace string `protobuf:"bytes,3,opt,name=namespace" json:"namespace,omitempty"`
}
@ -196,7 +198,7 @@ func (m *GetReleaseStatusResponse) String() string { return proto.Com
func (*GetReleaseStatusResponse) ProtoMessage() {}
func (*GetReleaseStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
func (m *GetReleaseStatusResponse) GetInfo() *hapi_release2.Info {
func (m *GetReleaseStatusResponse) GetInfo() *hapi_release4.Info {
if m != nil {
return m.Info
}
@ -219,7 +221,7 @@ func (*GetReleaseContentRequest) Descriptor() ([]byte, []int) { return fileDescr
// GetReleaseContentResponse is a response containing the contents of a release.
type GetReleaseContentResponse struct {
// The release content
Release *hapi_release3.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"`
Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"`
}
func (m *GetReleaseContentResponse) Reset() { *m = GetReleaseContentResponse{} }
@ -227,7 +229,7 @@ func (m *GetReleaseContentResponse) String() string { return proto.Co
func (*GetReleaseContentResponse) ProtoMessage() {}
func (*GetReleaseContentResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
func (m *GetReleaseContentResponse) GetRelease() *hapi_release3.Release {
func (m *GetReleaseContentResponse) GetRelease() *hapi_release5.Release {
if m != nil {
return m.Release
}
@ -278,7 +280,7 @@ func (m *UpdateReleaseRequest) GetValues() *hapi_chart.Config {
// UpdateReleaseResponse is the response to an update request.
type UpdateReleaseResponse struct {
Release *hapi_release3.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"`
Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"`
}
func (m *UpdateReleaseResponse) Reset() { *m = UpdateReleaseResponse{} }
@ -286,7 +288,7 @@ func (m *UpdateReleaseResponse) String() string { return proto.Compac
func (*UpdateReleaseResponse) ProtoMessage() {}
func (*UpdateReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} }
func (m *UpdateReleaseResponse) GetRelease() *hapi_release3.Release {
func (m *UpdateReleaseResponse) GetRelease() *hapi_release5.Release {
if m != nil {
return m.Release
}
@ -318,7 +320,7 @@ func (*RollbackReleaseRequest) Descriptor() ([]byte, []int) { return fileDescrip
// RollbackReleaseResponse is the response to an update request.
type RollbackReleaseResponse struct {
Release *hapi_release3.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"`
Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"`
}
func (m *RollbackReleaseResponse) Reset() { *m = RollbackReleaseResponse{} }
@ -326,7 +328,7 @@ func (m *RollbackReleaseResponse) String() string { return proto.Comp
func (*RollbackReleaseResponse) ProtoMessage() {}
func (*RollbackReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} }
func (m *RollbackReleaseResponse) GetRelease() *hapi_release3.Release {
func (m *RollbackReleaseResponse) GetRelease() *hapi_release5.Release {
if m != nil {
return m.Release
}
@ -381,7 +383,7 @@ func (m *InstallReleaseRequest) GetValues() *hapi_chart.Config {
// InstallReleaseResponse is the response from a release installation.
type InstallReleaseResponse struct {
Release *hapi_release3.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"`
Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"`
}
func (m *InstallReleaseResponse) Reset() { *m = InstallReleaseResponse{} }
@ -389,7 +391,7 @@ func (m *InstallReleaseResponse) String() string { return proto.Compa
func (*InstallReleaseResponse) ProtoMessage() {}
func (*InstallReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} }
func (m *InstallReleaseResponse) GetRelease() *hapi_release3.Release {
func (m *InstallReleaseResponse) GetRelease() *hapi_release5.Release {
if m != nil {
return m.Release
}
@ -416,7 +418,7 @@ func (*UninstallReleaseRequest) Descriptor() ([]byte, []int) { return fileDescri
// UninstallReleaseResponse represents a successful response to an uninstall request.
type UninstallReleaseResponse struct {
// Release is the release that was marked deleted.
Release *hapi_release3.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"`
Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"`
// Info is an uninstall message
Info string `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"`
}
@ -426,7 +428,7 @@ func (m *UninstallReleaseResponse) String() string { return proto.Com
func (*UninstallReleaseResponse) ProtoMessage() {}
func (*UninstallReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} }
func (m *UninstallReleaseResponse) GetRelease() *hapi_release3.Release {
func (m *UninstallReleaseResponse) GetRelease() *hapi_release5.Release {
if m != nil {
return m.Release
}
@ -473,7 +475,7 @@ func (*GetHistoryRequest) Descriptor() ([]byte, []int) { return fileDescriptor0,
// GetHistoryResponse is received in response to a GetHistory rpc.
type GetHistoryResponse struct {
Releases []*hapi_release3.Release `protobuf:"bytes,1,rep,name=releases" json:"releases,omitempty"`
Releases []*hapi_release5.Release `protobuf:"bytes,1,rep,name=releases" json:"releases,omitempty"`
}
func (m *GetHistoryResponse) Reset() { *m = GetHistoryResponse{} }
@ -481,13 +483,36 @@ func (m *GetHistoryResponse) String() string { return proto.CompactTe
func (*GetHistoryResponse) ProtoMessage() {}
func (*GetHistoryResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} }
func (m *GetHistoryResponse) GetReleases() []*hapi_release3.Release {
func (m *GetHistoryResponse) GetReleases() []*hapi_release5.Release {
if m != nil {
return m.Releases
}
return nil
}
// TestReleaseRequest is a request to get the status of a release.
type TestReleaseRequest struct {
// Name is the name of the release
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// timeout specifies the max amount of time any kubernetes client command can run.
Timeout int64 `protobuf:"varint,2,opt,name=timeout" json:"timeout,omitempty"`
}
func (m *TestReleaseRequest) Reset() { *m = TestReleaseRequest{} }
func (m *TestReleaseRequest) String() string { return proto.CompactTextString(m) }
func (*TestReleaseRequest) ProtoMessage() {}
func (*TestReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19} }
// TestReleaseResponse represents a message from executing a test
type TestReleaseResponse struct {
Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"`
}
func (m *TestReleaseResponse) Reset() { *m = TestReleaseResponse{} }
func (m *TestReleaseResponse) String() string { return proto.CompactTextString(m) }
func (*TestReleaseResponse) ProtoMessage() {}
func (*TestReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} }
func init() {
proto.RegisterType((*ListReleasesRequest)(nil), "hapi.services.tiller.ListReleasesRequest")
proto.RegisterType((*ListSort)(nil), "hapi.services.tiller.ListSort")
@ -508,6 +533,8 @@ func init() {
proto.RegisterType((*GetVersionResponse)(nil), "hapi.services.tiller.GetVersionResponse")
proto.RegisterType((*GetHistoryRequest)(nil), "hapi.services.tiller.GetHistoryRequest")
proto.RegisterType((*GetHistoryResponse)(nil), "hapi.services.tiller.GetHistoryResponse")
proto.RegisterType((*TestReleaseRequest)(nil), "hapi.services.tiller.TestReleaseRequest")
proto.RegisterType((*TestReleaseResponse)(nil), "hapi.services.tiller.TestReleaseResponse")
proto.RegisterEnum("hapi.services.tiller.ListSort_SortBy", ListSort_SortBy_name, ListSort_SortBy_value)
proto.RegisterEnum("hapi.services.tiller.ListSort_SortOrder", ListSort_SortOrder_name, ListSort_SortOrder_value)
}
@ -544,6 +571,8 @@ type ReleaseServiceClient interface {
RollbackRelease(ctx context.Context, in *RollbackReleaseRequest, opts ...grpc.CallOption) (*RollbackReleaseResponse, error)
// ReleaseHistory retrieves a releasse's history.
GetHistory(ctx context.Context, in *GetHistoryRequest, opts ...grpc.CallOption) (*GetHistoryResponse, error)
// RunReleaseTest executes the tests defined of a named release
RunReleaseTest(ctx context.Context, in *TestReleaseRequest, opts ...grpc.CallOption) (ReleaseService_RunReleaseTestClient, error)
}
type releaseServiceClient struct {
@ -658,6 +687,38 @@ func (c *releaseServiceClient) GetHistory(ctx context.Context, in *GetHistoryReq
return out, nil
}
func (c *releaseServiceClient) RunReleaseTest(ctx context.Context, in *TestReleaseRequest, opts ...grpc.CallOption) (ReleaseService_RunReleaseTestClient, error) {
stream, err := grpc.NewClientStream(ctx, &_ReleaseService_serviceDesc.Streams[1], c.cc, "/hapi.services.tiller.ReleaseService/RunReleaseTest", opts...)
if err != nil {
return nil, err
}
x := &releaseServiceRunReleaseTestClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type ReleaseService_RunReleaseTestClient interface {
Recv() (*TestReleaseResponse, error)
grpc.ClientStream
}
type releaseServiceRunReleaseTestClient struct {
grpc.ClientStream
}
func (x *releaseServiceRunReleaseTestClient) Recv() (*TestReleaseResponse, error) {
m := new(TestReleaseResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// Server API for ReleaseService service
type ReleaseServiceServer interface {
@ -682,6 +743,8 @@ type ReleaseServiceServer interface {
RollbackRelease(context.Context, *RollbackReleaseRequest) (*RollbackReleaseResponse, error)
// ReleaseHistory retrieves a releasse's history.
GetHistory(context.Context, *GetHistoryRequest) (*GetHistoryResponse, error)
// RunReleaseTest executes the tests defined of a named release
RunReleaseTest(*TestReleaseRequest, ReleaseService_RunReleaseTestServer) error
}
func RegisterReleaseServiceServer(s *grpc.Server, srv ReleaseServiceServer) {
@ -853,6 +916,27 @@ func _ReleaseService_GetHistory_Handler(srv interface{}, ctx context.Context, de
return interceptor(ctx, in, info, handler)
}
func _ReleaseService_RunReleaseTest_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(TestReleaseRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(ReleaseServiceServer).RunReleaseTest(m, &releaseServiceRunReleaseTestServer{stream})
}
type ReleaseService_RunReleaseTestServer interface {
Send(*TestReleaseResponse) error
grpc.ServerStream
}
type releaseServiceRunReleaseTestServer struct {
grpc.ServerStream
}
func (x *releaseServiceRunReleaseTestServer) Send(m *TestReleaseResponse) error {
return x.ServerStream.SendMsg(m)
}
var _ReleaseService_serviceDesc = grpc.ServiceDesc{
ServiceName: "hapi.services.tiller.ReleaseService",
HandlerType: (*ReleaseServiceServer)(nil),
@ -896,6 +980,11 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{
Handler: _ReleaseService_ListReleases_Handler,
ServerStreams: true,
},
{
StreamName: "RunReleaseTest",
Handler: _ReleaseService_RunReleaseTest_Handler,
ServerStreams: true,
},
},
Metadata: fileDescriptor0,
}
@ -903,74 +992,77 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("hapi/services/tiller.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 1092 bytes of a gzipped FileDescriptorProto
// 1141 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x57, 0xdd, 0x6e, 0xe3, 0x44,
0x14, 0xae, 0xf3, 0xe3, 0x24, 0xa7, 0x3f, 0xa4, 0xb3, 0x6d, 0xe3, 0x5a, 0x80, 0x82, 0x11, 0x6c,
0x58, 0xd8, 0x14, 0xc2, 0x15, 0x12, 0x42, 0xea, 0x66, 0xa3, 0xb4, 0x50, 0xb2, 0x92, 0x43, 0x17,
0x89, 0x0b, 0x22, 0x37, 0x99, 0x6c, 0xcd, 0x3a, 0x9e, 0xe0, 0x99, 0x94, 0xcd, 0x2d, 0x77, 0xbc,
0x06, 0x77, 0xf0, 0x30, 0x3c, 0x0b, 0x8f, 0x80, 0x3c, 0x3f, 0xae, 0xed, 0xda, 0x59, 0x93, 0x9b,
0xd8, 0x33, 0xe7, 0xcc, 0x77, 0xce, 0xf9, 0xe6, 0xfc, 0x38, 0x60, 0xde, 0x3a, 0x4b, 0xf7, 0x8c,
0xe2, 0xe0, 0xce, 0x9d, 0x62, 0x7a, 0xc6, 0x5c, 0xcf, 0xc3, 0x41, 0x77, 0x19, 0x10, 0x46, 0xd0,
0x51, 0x28, 0xeb, 0x2a, 0x59, 0x57, 0xc8, 0xcc, 0x13, 0x7e, 0x62, 0x7a, 0xeb, 0x04, 0x4c, 0xfc,
0x0a, 0x6d, 0xb3, 0x15, 0xdf, 0x27, 0xfe, 0xdc, 0x7d, 0x25, 0x05, 0xc2, 0x44, 0x80, 0x3d, 0xec,
0x50, 0xac, 0x9e, 0x89, 0x43, 0x4a, 0xe6, 0xfa, 0x73, 0x22, 0x05, 0xa7, 0x09, 0x01, 0x65, 0x0e,
0x5b, 0xd1, 0x04, 0xde, 0x1d, 0x0e, 0xa8, 0x4b, 0x7c, 0xf5, 0x14, 0x32, 0xeb, 0xcf, 0x12, 0x3c,
0xba, 0x72, 0x29, 0xb3, 0xc5, 0x41, 0x6a, 0xe3, 0x5f, 0x57, 0x98, 0x32, 0x74, 0x04, 0x55, 0xcf,
0x5d, 0xb8, 0xcc, 0xd0, 0xda, 0x5a, 0xa7, 0x6c, 0x8b, 0x05, 0x3a, 0x01, 0x9d, 0xcc, 0xe7, 0x14,
0x33, 0xa3, 0xd4, 0xd6, 0x3a, 0x0d, 0x5b, 0xae, 0xd0, 0x37, 0x50, 0xa3, 0x24, 0x60, 0x93, 0x9b,
0xb5, 0x51, 0x6e, 0x6b, 0x9d, 0x83, 0xde, 0x47, 0xdd, 0x2c, 0x2a, 0xba, 0xa1, 0xa5, 0x31, 0x09,
0x58, 0x37, 0xfc, 0x79, 0xb6, 0xb6, 0x75, 0xca, 0x9f, 0x21, 0xee, 0xdc, 0xf5, 0x18, 0x0e, 0x8c,
0x8a, 0xc0, 0x15, 0x2b, 0x34, 0x04, 0xe0, 0xb8, 0x24, 0x98, 0xe1, 0xc0, 0xa8, 0x72, 0xe8, 0x4e,
0x01, 0xe8, 0x17, 0xa1, 0xbe, 0xdd, 0xa0, 0xea, 0x15, 0x7d, 0x0d, 0x7b, 0x82, 0x92, 0xc9, 0x94,
0xcc, 0x30, 0x35, 0xf4, 0x76, 0xb9, 0x73, 0xd0, 0x3b, 0x15, 0x50, 0x8a, 0xe1, 0xb1, 0x20, 0xad,
0x4f, 0x66, 0xd8, 0xde, 0x15, 0xea, 0xe1, 0x3b, 0xb5, 0x7e, 0x86, 0xba, 0x82, 0xb7, 0x7a, 0xa0,
0x0b, 0xe7, 0xd1, 0x2e, 0xd4, 0xae, 0x47, 0xdf, 0x8d, 0x5e, 0xfc, 0x38, 0x6a, 0xee, 0xa0, 0x3a,
0x54, 0x46, 0xe7, 0xdf, 0x0f, 0x9a, 0x1a, 0x3a, 0x84, 0xfd, 0xab, 0xf3, 0xf1, 0x0f, 0x13, 0x7b,
0x70, 0x35, 0x38, 0x1f, 0x0f, 0x9e, 0x37, 0x4b, 0xd6, 0xfb, 0xd0, 0x88, 0xbc, 0x42, 0x35, 0x28,
0x9f, 0x8f, 0xfb, 0xe2, 0xc8, 0xf3, 0xc1, 0xb8, 0xdf, 0xd4, 0xac, 0x3f, 0x34, 0x38, 0x4a, 0x5e,
0x02, 0x5d, 0x12, 0x9f, 0xe2, 0xf0, 0x16, 0xa6, 0x64, 0xe5, 0x47, 0xb7, 0xc0, 0x17, 0x08, 0x41,
0xc5, 0xc7, 0x6f, 0xd4, 0x1d, 0xf0, 0xf7, 0x50, 0x93, 0x11, 0xe6, 0x78, 0x9c, 0xff, 0xb2, 0x2d,
0x16, 0xe8, 0x0b, 0xa8, 0xcb, 0xe0, 0xa8, 0x51, 0x69, 0x97, 0x3b, 0xbb, 0xbd, 0xe3, 0x64, 0xc8,
0xd2, 0xa2, 0x1d, 0xa9, 0x59, 0x43, 0x68, 0x0d, 0xb1, 0xf2, 0x44, 0x30, 0xa2, 0x72, 0x22, 0xb4,
0xeb, 0x2c, 0x30, 0x77, 0x26, 0xb4, 0xeb, 0x2c, 0x30, 0x32, 0xa0, 0x26, 0x13, 0x8a, 0xbb, 0x53,
0xb5, 0xd5, 0xd2, 0x62, 0x60, 0x3c, 0x04, 0x92, 0x71, 0x65, 0x21, 0x7d, 0x0c, 0x95, 0x30, 0x9d,
0x39, 0xcc, 0x6e, 0x0f, 0x25, 0xfd, 0xbc, 0xf4, 0xe7, 0xc4, 0xe6, 0x72, 0xf4, 0x2e, 0x34, 0x42,
0x7d, 0xba, 0x74, 0xa6, 0x98, 0x47, 0xdb, 0xb0, 0xef, 0x37, 0xac, 0x8b, 0xb8, 0xd5, 0x3e, 0xf1,
0x19, 0xf6, 0xd9, 0x76, 0xfe, 0x5f, 0xc1, 0x69, 0x06, 0x92, 0x0c, 0xe0, 0x0c, 0x6a, 0xd2, 0x35,
0x8e, 0x96, 0xcb, 0xab, 0xd2, 0xb2, 0xfe, 0x2e, 0xc1, 0xd1, 0xf5, 0x72, 0xe6, 0x30, 0xac, 0x44,
0x1b, 0x9c, 0x7a, 0x0c, 0x55, 0xde, 0x16, 0x24, 0x17, 0x87, 0x02, 0x5b, 0xf4, 0x8e, 0x7e, 0xf8,
0x6b, 0x0b, 0x39, 0x7a, 0x02, 0xfa, 0x9d, 0xe3, 0xad, 0x30, 0xe5, 0x44, 0x44, 0xac, 0x49, 0x4d,
0xde, 0x53, 0x6c, 0xa9, 0x81, 0x5a, 0x50, 0x9b, 0x05, 0xeb, 0x49, 0xb0, 0xf2, 0x79, 0x91, 0xd5,
0x6d, 0x7d, 0x16, 0xac, 0xed, 0x95, 0x8f, 0x3e, 0x84, 0xfd, 0x99, 0x4b, 0x9d, 0x1b, 0x0f, 0x4f,
0x6e, 0x09, 0x79, 0x4d, 0x79, 0x9d, 0xd5, 0xed, 0x3d, 0xb9, 0x79, 0x11, 0xee, 0x21, 0x33, 0xcc,
0xa4, 0x69, 0x80, 0x1d, 0x86, 0x0d, 0x9d, 0xcb, 0xa3, 0x75, 0xc8, 0x21, 0x73, 0x17, 0x98, 0xac,
0x98, 0x51, 0xe3, 0xd9, 0xa7, 0x96, 0xe8, 0x03, 0xd8, 0x0b, 0x30, 0xc5, 0x6c, 0x22, 0xbd, 0xac,
0xf3, 0x93, 0xbb, 0x7c, 0xef, 0xa5, 0x70, 0x0b, 0x41, 0xe5, 0x37, 0xc7, 0x65, 0x46, 0x83, 0x8b,
0xf8, 0xbb, 0x75, 0x01, 0xc7, 0x29, 0xae, 0xb6, 0xa5, 0xfd, 0x1f, 0x0d, 0x4e, 0x6c, 0xe2, 0x79,
0x37, 0xce, 0xf4, 0x75, 0x01, 0xe2, 0x63, 0x1c, 0x95, 0x36, 0x73, 0x54, 0xce, 0xe0, 0x28, 0x96,
0x4b, 0x95, 0x44, 0x2e, 0x25, 0xd8, 0xab, 0xe6, 0xb3, 0xa7, 0x27, 0xd9, 0x53, 0xd4, 0xd4, 0x62,
0xd4, 0x7c, 0x0b, 0xad, 0x07, 0xf1, 0x6c, 0x4b, 0xce, 0x5f, 0x25, 0x38, 0xbe, 0xf4, 0x29, 0x73,
0x3c, 0x2f, 0xc5, 0x4d, 0x94, 0x80, 0x5a, 0xe1, 0x04, 0x2c, 0xfd, 0x9f, 0x04, 0x2c, 0x27, 0xc8,
0x55, 0x37, 0x51, 0x89, 0xdd, 0x44, 0xa1, 0xa4, 0x4c, 0xb4, 0x02, 0x3d, 0xd5, 0x0a, 0xd0, 0x7b,
0x00, 0x01, 0x5e, 0x51, 0x3c, 0xe1, 0xe0, 0x82, 0xc4, 0x06, 0xdf, 0x19, 0xc9, 0xca, 0x57, 0xbc,
0xd7, 0xb3, 0x79, 0x8f, 0xa7, 0xe4, 0x25, 0x9c, 0xa4, 0xa9, 0xda, 0x96, 0xf6, 0xdf, 0x35, 0x68,
0x5d, 0xfb, 0x6e, 0x26, 0xf1, 0x59, 0x49, 0xf9, 0x80, 0x8a, 0x52, 0x06, 0x15, 0x47, 0x50, 0x5d,
0xae, 0x82, 0x57, 0x58, 0x52, 0x2b, 0x16, 0xf1, 0x18, 0x2b, 0x89, 0x18, 0xad, 0x09, 0x18, 0x0f,
0x7d, 0xd8, 0x32, 0xa2, 0xd0, 0xeb, 0xa8, 0x75, 0x37, 0x44, 0x9b, 0xb6, 0x1e, 0xc1, 0xe1, 0x10,
0xb3, 0x97, 0xa2, 0x00, 0x64, 0x78, 0xd6, 0x00, 0x50, 0x7c, 0xf3, 0xde, 0x9e, 0xdc, 0x4a, 0xda,
0x53, 0x5f, 0x2a, 0x4a, 0x5f, 0x69, 0x59, 0x5f, 0x71, 0xec, 0x0b, 0x97, 0x32, 0x12, 0xac, 0x37,
0x51, 0xd7, 0x84, 0xf2, 0xc2, 0x79, 0x23, 0x3b, 0x7b, 0xf8, 0x6a, 0x0d, 0xb9, 0x07, 0xd1, 0x51,
0xe9, 0x41, 0x7c, 0x4e, 0x6a, 0x85, 0xe6, 0x64, 0xef, 0xdf, 0x1a, 0x1c, 0xa8, 0xe1, 0x26, 0x3e,
0x45, 0x90, 0x0b, 0x7b, 0xf1, 0x29, 0x8e, 0x3e, 0xc9, 0xff, 0x52, 0x49, 0x7d, 0x6e, 0x99, 0x4f,
0x8a, 0xa8, 0x0a, 0x67, 0xad, 0x9d, 0xcf, 0x35, 0x44, 0xa1, 0x99, 0x1e, 0xae, 0xe8, 0x69, 0x36,
0x46, 0xce, 0x34, 0x37, 0xbb, 0x45, 0xd5, 0x95, 0x59, 0x74, 0xc7, 0x69, 0x4f, 0x4e, 0x44, 0xf4,
0x56, 0x98, 0xe4, 0x10, 0x36, 0xcf, 0x0a, 0xeb, 0x47, 0x76, 0x7f, 0x81, 0xfd, 0xc4, 0x38, 0x40,
0x39, 0x6c, 0x65, 0xcd, 0x57, 0xf3, 0xd3, 0x42, 0xba, 0x91, 0xad, 0x05, 0x1c, 0x24, 0xeb, 0x1c,
0xe5, 0x00, 0x64, 0x36, 0x4e, 0xf3, 0xb3, 0x62, 0xca, 0x91, 0x39, 0x0a, 0xcd, 0x74, 0x19, 0xe6,
0xdd, 0x63, 0x4e, 0xcb, 0xc8, 0xbb, 0xc7, 0xbc, 0xea, 0xb6, 0x76, 0x90, 0x03, 0x70, 0x5f, 0x85,
0xe8, 0x71, 0xee, 0x85, 0x24, 0x8b, 0xd7, 0xec, 0xbc, 0x5d, 0x31, 0x32, 0xb1, 0x84, 0x77, 0x52,
0x63, 0x0a, 0xe5, 0x50, 0x93, 0x3d, 0x9d, 0xcd, 0xa7, 0x05, 0xb5, 0x53, 0x41, 0xc9, 0xc2, 0xde,
0x10, 0x54, 0xb2, 0x6b, 0x6c, 0x08, 0x2a, 0xd5, 0x23, 0xac, 0x9d, 0x67, 0xf0, 0x53, 0x5d, 0xe9,
0xdd, 0xe8, 0xfc, 0xef, 0xd3, 0x97, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0xbd, 0x3d, 0xae, 0x84,
0x0f, 0x0e, 0x00, 0x00,
0x14, 0xae, 0xf3, 0x9f, 0xd3, 0x1f, 0xd2, 0xe9, 0x9f, 0x6b, 0x01, 0x2a, 0x46, 0xd0, 0xec, 0xc2,
0xa6, 0x10, 0xae, 0x90, 0x10, 0x52, 0xdb, 0x8d, 0xda, 0x42, 0xe9, 0x4a, 0xce, 0x76, 0x91, 0xb8,
0x20, 0x72, 0x93, 0x49, 0x6b, 0xd6, 0xf1, 0x04, 0xcf, 0xa4, 0x6c, 0x6f, 0xb9, 0xe3, 0x35, 0xb8,
0x83, 0x87, 0xe1, 0x05, 0x78, 0x19, 0x34, 0x7f, 0xae, 0x27, 0xb5, 0x5b, 0x93, 0x9b, 0x78, 0x66,
0xce, 0x99, 0xef, 0x9c, 0xf3, 0x9d, 0x33, 0x67, 0x26, 0xe0, 0xdc, 0xf8, 0xd3, 0xe0, 0x80, 0xe2,
0xf8, 0x36, 0x18, 0x62, 0x7a, 0xc0, 0x82, 0x30, 0xc4, 0x71, 0x67, 0x1a, 0x13, 0x46, 0xd0, 0x26,
0x97, 0x75, 0xb4, 0xac, 0x23, 0x65, 0xce, 0xb6, 0xd8, 0x31, 0xbc, 0xf1, 0x63, 0x26, 0x7f, 0xa5,
0xb6, 0xb3, 0x93, 0x5e, 0x27, 0xd1, 0x38, 0xb8, 0x56, 0x02, 0x69, 0x22, 0xc6, 0x21, 0xf6, 0x29,
0xd6, 0x5f, 0x63, 0x93, 0x96, 0x05, 0xd1, 0x98, 0x28, 0xc1, 0xae, 0x21, 0xa0, 0xcc, 0x67, 0x33,
0x6a, 0xe0, 0xdd, 0xe2, 0x98, 0x06, 0x24, 0xd2, 0x5f, 0x29, 0x73, 0xff, 0x2c, 0xc1, 0xc6, 0x79,
0x40, 0x99, 0x27, 0x37, 0x52, 0x0f, 0xff, 0x3a, 0xc3, 0x94, 0xa1, 0x4d, 0xa8, 0x86, 0xc1, 0x24,
0x60, 0xb6, 0xb5, 0x67, 0xb5, 0xcb, 0x9e, 0x9c, 0xa0, 0x6d, 0xa8, 0x91, 0xf1, 0x98, 0x62, 0x66,
0x97, 0xf6, 0xac, 0x76, 0xd3, 0x53, 0x33, 0xf4, 0x2d, 0xd4, 0x29, 0x89, 0xd9, 0xe0, 0xea, 0xce,
0x2e, 0xef, 0x59, 0xed, 0xb5, 0xee, 0x27, 0x9d, 0x2c, 0x2a, 0x3a, 0xdc, 0x52, 0x9f, 0xc4, 0xac,
0xc3, 0x7f, 0x8e, 0xee, 0xbc, 0x1a, 0x15, 0x5f, 0x8e, 0x3b, 0x0e, 0x42, 0x86, 0x63, 0xbb, 0x22,
0x71, 0xe5, 0x0c, 0x9d, 0x00, 0x08, 0x5c, 0x12, 0x8f, 0x70, 0x6c, 0x57, 0x05, 0x74, 0xbb, 0x00,
0xf4, 0x2b, 0xae, 0xef, 0x35, 0xa9, 0x1e, 0xa2, 0x6f, 0x60, 0x45, 0x52, 0x32, 0x18, 0x92, 0x11,
0xa6, 0x76, 0x6d, 0xaf, 0xdc, 0x5e, 0xeb, 0xee, 0x4a, 0x28, 0xcd, 0x70, 0x5f, 0x92, 0x76, 0x4c,
0x46, 0xd8, 0x5b, 0x96, 0xea, 0x7c, 0x4c, 0xdd, 0x9f, 0xa1, 0xa1, 0xe1, 0xdd, 0x2e, 0xd4, 0xa4,
0xf3, 0x68, 0x19, 0xea, 0x97, 0x17, 0xdf, 0x5f, 0xbc, 0xfa, 0xf1, 0xa2, 0xb5, 0x84, 0x1a, 0x50,
0xb9, 0x38, 0xfc, 0xa1, 0xd7, 0xb2, 0xd0, 0x3a, 0xac, 0x9e, 0x1f, 0xf6, 0x5f, 0x0f, 0xbc, 0xde,
0x79, 0xef, 0xb0, 0xdf, 0x7b, 0xd9, 0x2a, 0xb9, 0x1f, 0x42, 0x33, 0xf1, 0x0a, 0xd5, 0xa1, 0x7c,
0xd8, 0x3f, 0x96, 0x5b, 0x5e, 0xf6, 0xfa, 0xc7, 0x2d, 0xcb, 0xfd, 0xc3, 0x82, 0x4d, 0x33, 0x09,
0x74, 0x4a, 0x22, 0x8a, 0x79, 0x16, 0x86, 0x64, 0x16, 0x25, 0x59, 0x10, 0x13, 0x84, 0xa0, 0x12,
0xe1, 0x77, 0x3a, 0x07, 0x62, 0xcc, 0x35, 0x19, 0x61, 0x7e, 0x28, 0xf8, 0x2f, 0x7b, 0x72, 0x82,
0xbe, 0x84, 0x86, 0x0a, 0x8e, 0xda, 0x95, 0xbd, 0x72, 0x7b, 0xb9, 0xbb, 0x65, 0x86, 0xac, 0x2c,
0x7a, 0x89, 0x9a, 0x7b, 0x02, 0x3b, 0x27, 0x58, 0x7b, 0x22, 0x19, 0xd1, 0x35, 0xc1, 0xed, 0xfa,
0x13, 0x2c, 0x9c, 0xe1, 0x76, 0xfd, 0x09, 0x46, 0x36, 0xd4, 0x55, 0x41, 0x09, 0x77, 0xaa, 0x9e,
0x9e, 0xba, 0x0c, 0xec, 0x87, 0x40, 0x2a, 0xae, 0x2c, 0xa4, 0x4f, 0xa1, 0xc2, 0xcb, 0x59, 0xc0,
0x2c, 0x77, 0x91, 0xe9, 0xe7, 0x59, 0x34, 0x26, 0x9e, 0x90, 0xa3, 0xf7, 0xa1, 0xc9, 0xf5, 0xe9,
0xd4, 0x1f, 0x62, 0x11, 0x6d, 0xd3, 0xbb, 0x5f, 0x70, 0x4f, 0xd3, 0x56, 0x8f, 0x49, 0xc4, 0x70,
0xc4, 0x16, 0xf3, 0xff, 0x1c, 0x76, 0x33, 0x90, 0x54, 0x00, 0x07, 0x50, 0x57, 0xae, 0x09, 0xb4,
0x5c, 0x5e, 0xb5, 0x96, 0xfb, 0x77, 0x09, 0x36, 0x2f, 0xa7, 0x23, 0x9f, 0x61, 0x2d, 0x7a, 0xc4,
0xa9, 0x7d, 0xa8, 0x8a, 0xb6, 0xa0, 0xb8, 0x58, 0x97, 0xd8, 0xb2, 0x77, 0x1c, 0xf3, 0x5f, 0x4f,
0xca, 0xd1, 0x73, 0xa8, 0xdd, 0xfa, 0xe1, 0x0c, 0x53, 0x41, 0x44, 0xc2, 0x9a, 0xd2, 0x14, 0x3d,
0xc5, 0x53, 0x1a, 0x68, 0x07, 0xea, 0xa3, 0xf8, 0x6e, 0x10, 0xcf, 0x22, 0x71, 0xc8, 0x1a, 0x5e,
0x6d, 0x14, 0xdf, 0x79, 0xb3, 0x08, 0x7d, 0x0c, 0xab, 0xa3, 0x80, 0xfa, 0x57, 0x21, 0x1e, 0xdc,
0x10, 0xf2, 0x96, 0x8a, 0x73, 0xd6, 0xf0, 0x56, 0xd4, 0xe2, 0x29, 0x5f, 0x43, 0x0e, 0xaf, 0xa4,
0x61, 0x8c, 0x7d, 0x86, 0xed, 0x9a, 0x90, 0x27, 0x73, 0xce, 0x21, 0x0b, 0x26, 0x98, 0xcc, 0x98,
0x5d, 0x17, 0xd5, 0xa7, 0xa7, 0xe8, 0x23, 0x58, 0x89, 0x31, 0xc5, 0x6c, 0xa0, 0xbc, 0x6c, 0x88,
0x9d, 0xcb, 0x62, 0xed, 0x8d, 0x74, 0x0b, 0x41, 0xe5, 0x37, 0x3f, 0x60, 0x76, 0x53, 0x88, 0xc4,
0xd8, 0x3d, 0x85, 0xad, 0x39, 0xae, 0x16, 0xa5, 0xfd, 0x1f, 0x0b, 0xb6, 0x3d, 0x12, 0x86, 0x57,
0xfe, 0xf0, 0x6d, 0x01, 0xe2, 0x53, 0x1c, 0x95, 0x1e, 0xe7, 0xa8, 0x9c, 0xc1, 0x51, 0xaa, 0x96,
0x2a, 0x46, 0x2d, 0x19, 0xec, 0x55, 0xf3, 0xd9, 0xab, 0x99, 0xec, 0x69, 0x6a, 0xea, 0x29, 0x6a,
0xbe, 0x83, 0x9d, 0x07, 0xf1, 0x2c, 0x4a, 0xce, 0x5f, 0x25, 0xd8, 0x3a, 0x8b, 0x28, 0xf3, 0xc3,
0x70, 0x8e, 0x9b, 0xa4, 0x00, 0xad, 0xc2, 0x05, 0x58, 0xfa, 0x3f, 0x05, 0x58, 0x36, 0xc8, 0xd5,
0x99, 0xa8, 0xa4, 0x32, 0x51, 0xa8, 0x28, 0x8d, 0x56, 0x50, 0x9b, 0x6b, 0x05, 0xe8, 0x03, 0x80,
0x18, 0xcf, 0x28, 0x1e, 0x08, 0x70, 0x49, 0x62, 0x53, 0xac, 0x5c, 0xa8, 0x93, 0xaf, 0x79, 0x6f,
0x64, 0xf3, 0x9e, 0x2e, 0xc9, 0x33, 0xd8, 0x9e, 0xa7, 0x6a, 0x51, 0xda, 0x7f, 0xb7, 0x60, 0xe7,
0x32, 0x0a, 0x32, 0x89, 0xcf, 0x2a, 0xca, 0x07, 0x54, 0x94, 0x32, 0xa8, 0xd8, 0x84, 0xea, 0x74,
0x16, 0x5f, 0x63, 0x45, 0xad, 0x9c, 0xa4, 0x63, 0xac, 0x18, 0x31, 0xba, 0x03, 0xb0, 0x1f, 0xfa,
0xb0, 0x60, 0x44, 0xdc, 0xeb, 0xa4, 0x75, 0x37, 0x65, 0x9b, 0x76, 0x37, 0x60, 0xfd, 0x04, 0xb3,
0x37, 0xf2, 0x00, 0xa8, 0xf0, 0xdc, 0x1e, 0xa0, 0xf4, 0xe2, 0xbd, 0x3d, 0xb5, 0x64, 0xda, 0xd3,
0x2f, 0x15, 0xad, 0xaf, 0xb5, 0xdc, 0xaf, 0x05, 0xf6, 0x69, 0x40, 0x19, 0x89, 0xef, 0x1e, 0xa3,
0xae, 0x05, 0xe5, 0x89, 0xff, 0x4e, 0x75, 0x76, 0x3e, 0x74, 0x4f, 0x84, 0x07, 0xc9, 0x56, 0xe5,
0x41, 0xfa, 0x9e, 0xb4, 0x8a, 0xdd, 0x93, 0x47, 0x80, 0x5e, 0xe3, 0xe4, 0xca, 0x7e, 0xe2, 0x8a,
0xd1, 0x49, 0x28, 0x99, 0x49, 0xd8, 0x87, 0x0d, 0x03, 0x43, 0x79, 0xc3, 0xbd, 0xa6, 0xd7, 0x0a,
0x83, 0x0f, 0xbb, 0xff, 0x36, 0x60, 0x4d, 0xdf, 0xa4, 0xf2, 0xdd, 0x83, 0x02, 0x58, 0x49, 0x3f,
0x19, 0xd0, 0xb3, 0xfc, 0x67, 0xd1, 0xdc, 0xdb, 0xce, 0x79, 0x5e, 0x44, 0x55, 0xfa, 0xe2, 0x2e,
0x7d, 0x61, 0x21, 0x0a, 0xad, 0xf9, 0x9b, 0x1c, 0xbd, 0xc8, 0xc6, 0xc8, 0x79, 0x3a, 0x38, 0x9d,
0xa2, 0xea, 0xda, 0x2c, 0xba, 0x15, 0x39, 0x36, 0xaf, 0x5f, 0xf4, 0x24, 0x8c, 0x79, 0xe3, 0x3b,
0x07, 0x85, 0xf5, 0x13, 0xbb, 0xbf, 0xc0, 0xaa, 0x71, 0xf7, 0xa0, 0x1c, 0xb6, 0xb2, 0x2e, 0x73,
0xe7, 0xb3, 0x42, 0xba, 0x89, 0xad, 0x09, 0xac, 0x99, 0x4d, 0x05, 0xe5, 0x00, 0x64, 0x76, 0x69,
0xe7, 0xf3, 0x62, 0xca, 0x89, 0x39, 0x0a, 0xad, 0xf9, 0x33, 0x9f, 0x97, 0xc7, 0x9c, 0xfe, 0x94,
0x97, 0xc7, 0xbc, 0x56, 0xe2, 0x2e, 0x21, 0x1f, 0xe0, 0xfe, 0xc8, 0xa3, 0xfd, 0xdc, 0x84, 0x98,
0x9d, 0xc2, 0x69, 0x3f, 0xad, 0x98, 0x98, 0x98, 0xc2, 0x7b, 0x73, 0x77, 0x22, 0xca, 0xa1, 0x26,
0xfb, 0x29, 0xe0, 0xbc, 0x28, 0xa8, 0x3d, 0x17, 0x94, 0xea, 0x22, 0x8f, 0x04, 0x65, 0xb6, 0xa8,
0x47, 0x82, 0x9a, 0x6b, 0x48, 0xee, 0x12, 0x0a, 0x60, 0xcd, 0x9b, 0x45, 0xca, 0x34, 0xef, 0x12,
0x28, 0x67, 0xf7, 0xc3, 0x2e, 0xe4, 0x3c, 0x2b, 0xa0, 0x79, 0x7f, 0xbe, 0x8f, 0xe0, 0xa7, 0x86,
0x56, 0xbd, 0xaa, 0x89, 0xbf, 0x85, 0x5f, 0xfd, 0x17, 0x00, 0x00, 0xff, 0xff, 0x70, 0xcd, 0xbe,
0x36, 0xe7, 0x0e, 0x00, 0x00,
}

@ -0,0 +1,68 @@
/*
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 releasetesting
import (
"fmt"
"k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/tiller/environment"
)
// Environment encapsulates information about where test suite executes and returns results
type Environment struct {
Namespace string
KubeClient environment.KubeClient
Stream services.ReleaseService_RunReleaseTestServer
Timeout int64
}
func streamRunning(name string, stream services.ReleaseService_RunReleaseTestServer) error {
msg := "RUNNING: " + name
err := streamMessage(msg, stream)
return err
}
func streamError(info string, stream services.ReleaseService_RunReleaseTestServer) error {
msg := "ERROR: " + info
err := streamMessage(msg, stream)
return err
}
func streamFailed(name, namespace string, stream services.ReleaseService_RunReleaseTestServer) error {
msg := fmt.Sprintf("FAILED: %s, run `kubectl logs %s --namespace %s` for more info", name, name, namespace)
err := streamMessage(msg, stream)
return err
}
func streamSuccess(name string, stream services.ReleaseService_RunReleaseTestServer) error {
msg := fmt.Sprintf("PASSED: %s", name)
err := streamMessage(msg, stream)
return err
}
func streamUnknown(name, info string, stream services.ReleaseService_RunReleaseTestServer) error {
msg := fmt.Sprintf("UNKNOWN: %s: %s", name, info)
err := streamMessage(msg, stream)
return err
}
func streamMessage(msg string, stream services.ReleaseService_RunReleaseTestServer) error {
resp := &services.TestReleaseResponse{Msg: msg}
err := stream.Send(resp)
return err
}

@ -0,0 +1,204 @@
/*
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 releasetesting
import (
"bytes"
"fmt"
"log"
"strings"
"time"
"github.com/ghodss/yaml"
"github.com/golang/protobuf/ptypes/timestamp"
"k8s.io/kubernetes/pkg/api"
"k8s.io/helm/pkg/proto/hapi/release"
util "k8s.io/helm/pkg/releaseutil"
"k8s.io/helm/pkg/timeconv"
)
// TestSuite what tests are run, results, and metadata
type TestSuite struct {
StartedAt *timestamp.Timestamp
CompletedAt *timestamp.Timestamp
TestManifests []string
Results []*release.TestRun
}
type test struct {
manifest string
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, error) {
testManifests, err := extractTestManifestsFromHooks(rel.Hooks, rel.Name)
if err != nil {
return nil, err
}
results := []*release.TestRun{}
return &TestSuite{
TestManifests: testManifests,
Results: results,
}, nil
}
// Run executes tests in a test suite and stores a result within the context of a given environment
func (t *TestSuite) Run(env *Environment) error {
t.StartedAt = timeconv.Now()
for _, testManifest := range t.TestManifests {
test, err := newTest(testManifest)
if err != nil {
return err
}
test.result.StartedAt = timeconv.Now()
if err := streamRunning(test.result.Name, env.Stream); err != nil {
return err
}
resourceCreated := true
if err := t.createTestPod(test, env); err != nil {
resourceCreated = false
if streamErr := streamError(test.result.Info, env.Stream); streamErr != nil {
return err
}
}
resourceCleanExit := true
status := api.PodUnknown
if resourceCreated {
status, err = t.getTestPodStatus(test, env)
if err != nil {
resourceCleanExit = false
if streamErr := streamUnknown(test.result.Name, test.result.Info, env.Stream); streamErr != nil {
return streamErr
}
}
}
if resourceCreated && resourceCleanExit && status == api.PodSucceeded {
test.result.Status = release.TestRun_SUCCESS
if streamErr := streamSuccess(test.result.Name, env.Stream); streamErr != nil {
return streamErr
}
} else if resourceCreated && resourceCleanExit && status == api.PodFailed {
test.result.Status = release.TestRun_FAILURE
if streamErr := streamFailed(test.result.Name, env.Namespace, env.Stream); streamErr != nil {
return err
}
}
test.result.CompletedAt = timeconv.Now()
t.Results = append(t.Results, test.result)
}
t.CompletedAt = timeconv.Now()
return nil
}
// NOTE: may want to move this function to pkg/tiller in the future
func filterHooksForTestHooks(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
}
for _, h := range hooks {
for _, e := range h.Events {
if e == release.Hook_RELEASE_TEST_SUCCESS {
testHooks = append(testHooks, h)
continue
}
}
}
if len(testHooks) == 0 {
return nil, notFoundErr
}
return testHooks, nil
}
// NOTE: may want to move this function to pkg/tiller in the future
func extractTestManifestsFromHooks(hooks []*release.Hook, releaseName string) ([]string, error) {
testHooks, err := filterHooksForTestHooks(hooks, releaseName)
if err != nil {
return nil, err
}
tests := []string{}
for _, h := range testHooks {
individualTests := util.SplitManifests(h.Manifest)
for _, t := range individualTests {
tests = append(tests, t)
}
}
return tests, nil
}
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, fmt.Errorf("%s is not a pod", sh.Metadata.Name)
}
name := strings.TrimSuffix(sh.Metadata.Name, ",")
return &test{
manifest: testManifest,
result: &release.TestRun{
Name: name,
},
}, nil
}
func (t *TestSuite) createTestPod(test *test, env *Environment) error {
b := bytes.NewBufferString(test.manifest)
if err := env.KubeClient.Create(env.Namespace, b, env.Timeout, false); err != nil {
log.Printf(err.Error())
test.result.Info = err.Error()
test.result.Status = release.TestRun_FAILURE
return err
}
return nil
}
func (t *TestSuite) getTestPodStatus(test *test, env *Environment) (api.PodPhase, error) {
b := bytes.NewBufferString(test.manifest)
status, err := env.KubeClient.WaitAndGetCompletedPodPhase(env.Namespace, b, time.Duration(env.Timeout)*time.Second)
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.TestRun_UNKNOWN
return status, err
}
return status, err
}

@ -0,0 +1,247 @@
/*
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 releasetesting
import (
"io"
"os"
"testing"
"time"
"github.com/golang/protobuf/ptypes/timestamp"
"golang.org/x/net/context"
grpc "google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"k8s.io/kubernetes/pkg/api"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver"
tillerEnv "k8s.io/helm/pkg/tiller/environment"
)
func TestNewTestSuite(t *testing.T) {
rel := releaseStub()
_, err := NewTestSuite(rel)
if err != nil {
t.Errorf("%s", err)
}
}
func TestRun(t *testing.T) {
ts := testSuiteFixture()
if err := ts.Run(testEnvFixture()); err != nil {
t.Errorf("%s", err)
}
if ts.StartedAt == nil {
t.Errorf("Expected StartedAt to not be nil. Got: %v", ts.StartedAt)
}
if ts.CompletedAt == nil {
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 == nil {
t.Errorf("Expected test StartedAt to not be nil. Got: %v", result.StartedAt)
}
if result.CompletedAt == nil {
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.TestRun_SUCCESS {
t.Errorf("Expected test result to be successful, got: %v", result.Status)
}
}
func TestGetTestPodStatus(t *testing.T) {
ts := testSuiteFixture()
status, err := ts.getTestPodStatus(testFixture(), testEnvFixture())
if err != nil {
t.Errorf("Expected getTestPodStatus not to return err, Got: %s", err)
}
if status != api.PodSucceeded {
t.Errorf("Expected pod status to be succeeded, Got: %s ", status)
}
}
func TestExtractTestManifestsFromHooks(t *testing.T) {
rel := releaseStub()
testManifests, err := extractTestManifestsFromHooks(rel.Hooks, rel.Name)
if err != nil {
t.Errorf("Expected no error, Got: %s", err)
}
if len(testManifests) != 1 {
t.Errorf("Expected 1 test manifest, Got: %v", len(testManifests))
}
}
func chartStub() *chart.Chart {
return &chart.Chart{
Metadata: &chart.Metadata{
Name: "nemo",
},
Templates: []*chart.Template{
{Name: "templates/hello", Data: []byte("hello: world")},
{Name: "templates/hooks", Data: []byte(manifestWithTestHook)},
},
}
}
var manifestWithTestHook = `
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
`
var manifestWithInstallHooks = `apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
annotations:
"helm.sh/hook": post-install,pre-delete
data:
name: value
`
func releaseStub() *release.Release {
date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0}
return &release.Release{
Name: "lost-fish",
Info: &release.Info{
FirstDeployed: &date,
LastDeployed: &date,
Status: &release.Status{Code: release.Status_DEPLOYED},
Description: "a release stub",
},
Chart: chartStub(),
Config: &chart.Config{Raw: `name: value`},
Version: 1,
Hooks: []*release.Hook{
{
Name: "finding-nemo",
Kind: "Pod",
Path: "finding-nemo",
Manifest: manifestWithTestHook,
Events: []release.Hook_Event{
release.Hook_RELEASE_TEST_SUCCESS,
},
},
{
Name: "test-cm",
Kind: "ConfigMap",
Path: "test-cm",
Manifest: manifestWithInstallHooks,
Events: []release.Hook_Event{
release.Hook_POST_INSTALL,
release.Hook_PRE_DELETE,
},
},
},
}
}
func testFixture() *test {
return &test{
manifest: manifestWithTestHook,
result: &release.TestRun{},
}
}
func testSuiteFixture() *TestSuite {
testManifests := []string{manifestWithTestHook}
testResults := []*release.TestRun{}
ts := &TestSuite{
TestManifests: testManifests,
Results: testResults,
}
return ts
}
func testEnvFixture() *Environment {
tillerEnv := mockTillerEnvironment()
return &Environment{
Namespace: "default",
KubeClient: tillerEnv.KubeClient,
Timeout: 5,
Stream: mockStream{},
}
}
func mockTillerEnvironment() *tillerEnv.Environment {
e := tillerEnv.New()
e.Releases = storage.Init(driver.NewMemory())
e.KubeClient = newPodSucceededKubeClient()
return e
}
type mockStream struct {
stream grpc.ServerStream
}
func (rs mockStream) Send(m *services.TestReleaseResponse) error {
return nil
}
func (rs mockStream) SetHeader(m metadata.MD) error { return nil }
func (rs mockStream) SendHeader(m metadata.MD) error { return nil }
func (rs mockStream) SetTrailer(m metadata.MD) {}
func (rs mockStream) SendMsg(v interface{}) error { return nil }
func (rs mockStream) RecvMsg(v interface{}) error { return nil }
func (rs mockStream) Context() context.Context { return helm.NewContext() }
func newPodSucceededKubeClient() *podSucceededKubeClient {
return &podSucceededKubeClient{
PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: os.Stdout},
}
}
type podSucceededKubeClient struct {
tillerEnv.PrintingKubeClient
}
func (p *podSucceededKubeClient) WaitAndGetCompletedPodPhase(ns string, r io.Reader, timeout time.Duration) (api.PodPhase, error) {
return api.PodSucceeded, nil
}

@ -0,0 +1,48 @@
/*
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 releaseutil
import (
"fmt"
"strings"
)
// SimpleHead defines what the structure of the head of a manifest file
type SimpleHead struct {
Version string `json:"apiVersion"`
Kind string `json:"kind,omitempty"`
Metadata *struct {
Name string `json:"name"`
Annotations map[string]string `json:"annotations"`
} `json:"metadata,omitempty"`
}
// SplitManifests takes a string of manifest and returns a map contains individual manifests
func SplitManifests(bigfile string) map[string]string {
// This is not the best way of doing things, but it's how k8s itself does it.
// Basically, we're quickly splitting a stream of YAML documents into an
// array of YAML docs. In the current implementation, the file name is just
// a place holder, and doesn't have any further meaning.
sep := "\n---\n"
tpl := "manifest-%d"
res := map[string]string{}
tmp := strings.Split(bigfile, sep)
for i, d := range tmp {
res[fmt.Sprintf(tpl, i)] = d
}
return res
}

@ -24,6 +24,7 @@ package environment
import (
"io"
"time"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/engine"
@ -31,6 +32,7 @@ import (
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubectl/resource"
)
@ -135,6 +137,10 @@ type KubeClient interface {
Update(namespace string, originalReader, modifiedReader io.Reader, recreate bool, timeout int64, shouldWait bool) error
Build(namespace string, reader io.Reader) (kube.Result, error)
// WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase
// and returns said phase (PodSucceeded or PodFailed qualify)
WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error)
}
// PrintingKubeClient implements KubeClient, but simply prints the reader to
@ -180,6 +186,12 @@ func (p *PrintingKubeClient) Build(ns string, reader io.Reader) (kube.Result, er
return []*resource.Info{}, nil
}
// WaitAndGetCompletedPodPhase implements KubeClient WaitAndGetCompletedPodPhase
func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) {
_, err := io.Copy(p.Out, reader)
return api.PodUnknown, err
}
// Environment provides the context for executing a client request.
//
// All services in a context are concurrency safe.

@ -20,10 +20,12 @@ import (
"bytes"
"io"
"testing"
"time"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubectl/resource"
)
@ -55,6 +57,13 @@ func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64,
func (k *mockKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) {
return []*resource.Info{}, nil
}
func (k *mockKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) {
return api.PodUnknown, nil
}
func (k *mockKubeClient) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) {
return "", nil
}
var _ Engine = &mockEngine{}
var _ KubeClient = &mockKubeClient{}

@ -26,47 +26,41 @@ import (
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/proto/hapi/release"
util "k8s.io/helm/pkg/releaseutil"
)
// hookAnno is the label name for a hook
const hookAnno = "helm.sh/hook"
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"
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"
)
var events = map[string]release.Hook_Event{
preInstall: release.Hook_PRE_INSTALL,
postInstall: release.Hook_POST_INSTALL,
preDelete: release.Hook_PRE_DELETE,
postDelete: release.Hook_POST_DELETE,
preUpgrade: release.Hook_PRE_UPGRADE,
postUpgrade: release.Hook_POST_UPGRADE,
preRollback: release.Hook_PRE_ROLLBACK,
postRollback: release.Hook_POST_ROLLBACK,
}
type simpleHead struct {
Version string `json:"apiVersion"`
Kind string `json:"kind,omitempty"`
Metadata *struct {
Name string `json:"name"`
Annotations map[string]string `json:"annotations"`
} `json:"metadata,omitempty"`
preInstall: release.Hook_PRE_INSTALL,
postInstall: release.Hook_POST_INSTALL,
preDelete: release.Hook_PRE_DELETE,
postDelete: release.Hook_POST_DELETE,
preUpgrade: release.Hook_PRE_UPGRADE,
postUpgrade: release.Hook_POST_UPGRADE,
preRollback: release.Hook_PRE_ROLLBACK,
postRollback: release.Hook_POST_ROLLBACK,
releaseTestSuccess: release.Hook_RELEASE_TEST_SUCCESS,
}
// manifest represents a manifest file, which has a name and some content.
type manifest struct {
name string
content string
head *simpleHead
head *util.SimpleHead
}
// sortManifests takes a map of filename/YAML contents and sorts them into hook types.
@ -106,7 +100,7 @@ func sortManifests(files map[string]string, apis chartutil.VersionSet, sort Sort
continue
}
var sh simpleHead
var sh util.SimpleHead
err := yaml.Unmarshal([]byte(c), &sh)
if err != nil {

@ -23,6 +23,7 @@ import (
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/proto/hapi/release"
util "k8s.io/helm/pkg/releaseutil"
)
func TestSortManifests(t *testing.T) {
@ -162,7 +163,7 @@ metadata:
// Verify the sort order
sorted := make([]manifest, len(data))
for i, s := range data {
var sh simpleHead
var sh util.SimpleHead
err := yaml.Unmarshal([]byte(s.manifest), &sh)
if err != nil {
// This is expected for manifests that are corrupt or empty.

@ -24,10 +24,10 @@ import (
type SortOrder []string
// InstallOrder is the order in which manifests should be installed (by Kind)
var InstallOrder SortOrder = []string{"Namespace", "Secret", "ConfigMap", "PersistentVolume", "ServiceAccount", "Service", "Pod", "ReplicationController", "Deployment", "DaemonSet", "Ingress", "Job"}
var InstallOrder SortOrder = []string{"Namespace", "Secret", "ConfigMap", "PersistentVolume", "PersistentVolumeClaim", "ServiceAccount", "Service", "Pod", "ReplicationController", "Deployment", "DaemonSet", "Ingress", "Job"}
// UninstallOrder is the order in which manifests should be uninstalled (by Kind)
var UninstallOrder SortOrder = []string{"Service", "Pod", "ReplicationController", "Deployment", "DaemonSet", "ConfigMap", "Secret", "PersistentVolume", "ServiceAccount", "Ingress", "Job", "Namespace"}
var UninstallOrder SortOrder = []string{"Service", "Pod", "ReplicationController", "Deployment", "DaemonSet", "ConfigMap", "Secret", "PersistentVolumeClaim", "PersistentVolume", "ServiceAccount", "Ingress", "Job", "Namespace"}
// sortByKind does an in-place sort of manifests by Kind.
//

@ -18,6 +18,8 @@ package tiller
import (
"testing"
util "k8s.io/helm/pkg/releaseutil"
)
func TestKindSorter(t *testing.T) {
@ -25,27 +27,27 @@ func TestKindSorter(t *testing.T) {
{
name: "m",
content: "",
head: &simpleHead{Kind: "Deployment"},
head: &util.SimpleHead{Kind: "Deployment"},
},
{
name: "l",
content: "",
head: &simpleHead{Kind: "Service"},
head: &util.SimpleHead{Kind: "Service"},
},
{
name: "!",
content: "",
head: &simpleHead{Kind: "HonkyTonkSet"},
head: &util.SimpleHead{Kind: "HonkyTonkSet"},
},
{
name: "h",
content: "",
head: &simpleHead{Kind: "Namespace"},
head: &util.SimpleHead{Kind: "Namespace"},
},
{
name: "e",
content: "",
head: &simpleHead{Kind: "ConfigMap"},
head: &util.SimpleHead{Kind: "ConfigMap"},
},
}

@ -36,6 +36,7 @@ import (
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services"
reltesting "k8s.io/helm/pkg/releasetesting"
relutil "k8s.io/helm/pkg/releaseutil"
"k8s.io/helm/pkg/storage/driver"
"k8s.io/helm/pkg/tiller/environment"
@ -289,6 +290,7 @@ func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.R
if req.DryRun {
log.Printf("Dry run for %s", updatedRelease.Name)
res.Release.Info.Description = "Dry run complete"
return res, nil
}
@ -300,9 +302,11 @@ func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.R
}
if err := s.performKubeUpdate(originalRelease, updatedRelease, req.Recreate, req.Timeout, req.Wait); err != nil {
log.Printf("warning: Release Upgrade %q failed: %s", updatedRelease.Name, err)
msg := fmt.Sprintf("Upgrade %q failed: %s", updatedRelease.Name, err)
log.Printf("warning: %s", msg)
originalRelease.Info.Status.Code = release.Status_SUPERSEDED
updatedRelease.Info.Status.Code = release.Status_FAILED
updatedRelease.Info.Description = msg
s.recordRelease(originalRelease, true)
s.recordRelease(updatedRelease, false)
return res, err
@ -319,6 +323,7 @@ func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.R
s.recordRelease(originalRelease, true)
updatedRelease.Info.Status.Code = release.Status_DEPLOYED
updatedRelease.Info.Description = "Upgrade complete"
return res, nil
}
@ -404,6 +409,7 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele
FirstDeployed: currentRelease.Info.FirstDeployed,
LastDeployed: ts,
Status: &release.Status{Code: release.Status_UNKNOWN},
Description: "Preparing upgrade", // This should be overwritten later.
},
Version: revision,
Manifest: manifestDoc.String(),
@ -454,9 +460,11 @@ func (s *ReleaseServer) performRollback(currentRelease, targetRelease *release.R
}
if err := s.performKubeUpdate(currentRelease, targetRelease, req.Recreate, req.Timeout, req.Wait); err != nil {
log.Printf("warning: Release Rollback %q failed: %s", targetRelease.Name, err)
msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err)
log.Printf("warning: %s", msg)
currentRelease.Info.Status.Code = release.Status_SUPERSEDED
targetRelease.Info.Status.Code = release.Status_FAILED
targetRelease.Info.Description = msg
s.recordRelease(currentRelease, true)
s.recordRelease(targetRelease, false)
return res, err
@ -524,6 +532,9 @@ func (s *ReleaseServer) prepareRollback(req *services.RollbackReleaseRequest) (*
Code: release.Status_UNKNOWN,
Notes: prls.Info.Status.Notes,
},
// Because we lose the reference to rbv elsewhere, we set the
// message here, and only override it later if we experience failure.
Description: fmt.Sprintf("Rollback to %d", rbv),
},
Version: crls.Version + 1,
Manifest: prls.Manifest,
@ -672,6 +683,7 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
FirstDeployed: ts,
LastDeployed: ts,
Status: &release.Status{Code: release.Status_UNKNOWN},
Description: fmt.Sprintf("Install failed: %s", err),
},
Version: 0,
}
@ -691,6 +703,7 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
FirstDeployed: ts,
LastDeployed: ts,
Status: &release.Status{Code: release.Status_UNKNOWN},
Description: "Initial install underway", // Will be overwritten.
},
Manifest: manifestDoc.String(),
Hooks: hooks,
@ -793,6 +806,7 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install
if req.DryRun {
log.Printf("Dry run for %s", r.Name)
res.Release.Info.Description = "Dry run complete"
return res, nil
}
@ -821,9 +835,11 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install
r.Version = old.Version + 1
if err := s.performKubeUpdate(old, r, false, req.Timeout, req.Wait); err != nil {
log.Printf("warning: Release replace %q failed: %s", r.Name, err)
msg := fmt.Sprintf("Release replace %q failed: %s", r.Name, err)
log.Printf("warning: %s", msg)
old.Info.Status.Code = release.Status_SUPERSEDED
r.Info.Status.Code = release.Status_FAILED
r.Info.Description = msg
s.recordRelease(old, true)
s.recordRelease(r, false)
return res, err
@ -834,8 +850,10 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install
// regular manifests
b := bytes.NewBufferString(r.Manifest)
if err := s.env.KubeClient.Create(r.Namespace, b, req.Timeout, req.Wait); err != nil {
log.Printf("warning: Release %q failed: %s", r.Name, err)
msg := fmt.Sprintf("Release %q failed: %s", r.Name, err)
log.Printf("warning: %s", msg)
r.Info.Status.Code = release.Status_FAILED
r.Info.Description = msg
s.recordRelease(r, false)
return res, fmt.Errorf("release %s failed: %s", r.Name, err)
}
@ -844,13 +862,17 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install
// post-install hooks
if !req.DisableHooks {
if err := s.execHook(r.Hooks, r.Name, r.Namespace, postInstall, req.Timeout); err != nil {
log.Printf("warning: Release %q failed post-install: %s", r.Name, err)
msg := fmt.Sprintf("Release %q failed post-install: %s", r.Name, err)
log.Printf("warning: %s", msg)
r.Info.Status.Code = release.Status_FAILED
r.Info.Description = msg
s.recordRelease(r, false)
return res, err
}
}
r.Info.Status.Code = release.Status_DEPLOYED
r.Info.Description = "Install complete"
// This is a tricky case. The release has been created, but the result
// cannot be recorded. The truest thing to tell the user is that the
// release was created. However, the user will not be able to do anything
@ -858,7 +880,6 @@ func (s *ReleaseServer) performRelease(r *release.Release, req *services.Install
//
// One possible strategy would be to do a timed retry to see if we can get
// this stored in the future.
r.Info.Status.Code = release.Status_DEPLOYED
s.recordRelease(r, false)
return res, nil
@ -946,6 +967,7 @@ func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
log.Printf("uninstall: Deleting %s", req.Name)
rel.Info.Status.Code = release.Status_DELETING
rel.Info.Deleted = timeconv.Now()
rel.Info.Description = "Deletion in progress (or silently failed)"
res := &services.UninstallReleaseResponse{Release: rel}
if !req.DisableHooks {
@ -965,7 +987,7 @@ func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
log.Printf("uninstall: Failed to store updated release: %s", err)
}
manifests := splitManifests(rel.Manifest)
manifests := relutil.SplitManifests(rel.Manifest)
_, files, err := sortManifests(manifests, vs, UninstallOrder)
if err != nil {
// We could instead just delete everything in no particular order.
@ -1001,6 +1023,7 @@ func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
}
rel.Info.Status.Code = release.Status_DELETED
rel.Info.Description = "Deletion complete"
if req.Purge {
err := s.purgeReleases(rels...)
@ -1022,23 +1045,48 @@ func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
return res, errs
}
func splitManifests(bigfile string) map[string]string {
// This is not the best way of doing things, but it's how k8s itself does it.
// Basically, we're quickly splitting a stream of YAML documents into an
// array of YAML docs. In the current implementation, the file name is just
// a place holder, and doesn't have any further meaning.
sep := "\n---\n"
tpl := "manifest-%d"
res := map[string]string{}
tmp := strings.Split(bigfile, sep)
for i, d := range tmp {
res[fmt.Sprintf(tpl, i)] = d
}
return res
}
func validateManifest(c environment.KubeClient, ns string, manifest []byte) error {
r := bytes.NewReader(manifest)
_, err := c.Build(ns, r)
return err
}
// RunReleaseTest runs pre-defined tests stored as hooks on a given release
func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream services.ReleaseService_RunReleaseTestServer) error {
if !ValidName.MatchString(req.Name) {
return errMissingRelease
}
// finds the non-deleted release with the given name
rel, err := s.env.Releases.Last(req.Name)
if err != nil {
return err
}
testEnv := &reltesting.Environment{
Namespace: rel.Namespace,
KubeClient: s.env.KubeClient,
Timeout: req.Timeout,
Stream: stream,
}
tSuite, err := reltesting.NewTestSuite(rel)
if err != nil {
log.Printf("Error creating test suite for %s", rel.Name)
return err
}
if err := tSuite.Run(testEnv); err != nil {
log.Printf("Error running test suite for %s", rel.Name)
return err
}
rel.Info.Status.LastTestSuiteRun = &release.TestSuite{
StartedAt: tSuite.StartedAt,
CompletedAt: tSuite.CompletedAt,
Results: tSuite.Results,
}
return s.env.Releases.Update(rel)
}

@ -27,6 +27,7 @@ import (
"github.com/golang/protobuf/ptypes/timestamp"
"golang.org/x/net/context"
grpc "google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
@ -51,6 +52,20 @@ data:
name: value
`
var manifestWithTestHook = `
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
`
var manifestWithKeep = `apiVersion: v1
kind: ConfigMap
metadata:
@ -83,7 +98,7 @@ data:
func rsFixture() *ReleaseServer {
return &ReleaseServer{
env: mockEnvironment(),
env: MockEnvironment(),
clientset: fake.NewSimpleClientset(),
}
}
@ -120,6 +135,7 @@ func namedReleaseStub(name string, status release.Status_Code) *release.Release
FirstDeployed: &date,
LastDeployed: &date,
Status: &release.Status{Code: status},
Description: "Named Release Stub",
},
Chart: chartStub(),
Config: &chart.Config{Raw: `name: value`},
@ -135,6 +151,15 @@ func namedReleaseStub(name string, status release.Status_Code) *release.Release
release.Hook_PRE_DELETE,
},
},
{
Name: "finding-nemo",
Kind: "Pod",
Path: "finding-nemo",
Manifest: manifestWithTestHook,
Events: []release.Hook_Event{
release.Hook_RELEASE_TEST_SUCCESS,
},
},
},
}
}
@ -294,6 +319,10 @@ func TestInstallRelease(t *testing.T) {
if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest)
}
if rel.Info.Description != "Install complete" {
t.Errorf("unexpected description: %s", rel.Info.Description)
}
}
func TestInstallReleaseWithNotes(t *testing.T) {
@ -359,6 +388,10 @@ func TestInstallReleaseWithNotes(t *testing.T) {
if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest)
}
if rel.Info.Description != "Install complete" {
t.Errorf("unexpected description: %s", rel.Info.Description)
}
}
func TestInstallReleaseWithNotesRendered(t *testing.T) {
@ -425,6 +458,10 @@ func TestInstallReleaseWithNotesRendered(t *testing.T) {
if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest)
}
if rel.Info.Description != "Install complete" {
t.Errorf("unexpected description: %s", rel.Info.Description)
}
}
func TestInstallReleaseWithChartAndDependencyNotes(t *testing.T) {
@ -472,6 +509,10 @@ func TestInstallReleaseWithChartAndDependencyNotes(t *testing.T) {
if rel.Info.Status.Notes != notesText {
t.Fatalf("Expected '%s', got '%s'", notesText, rel.Info.Status.Notes)
}
if rel.Info.Description != "Install complete" {
t.Errorf("unexpected description: %s", rel.Info.Description)
}
}
func TestInstallReleaseDryRun(t *testing.T) {
@ -521,6 +562,10 @@ func TestInstallReleaseDryRun(t *testing.T) {
if res.Release.Hooks[0].LastRun != nil {
t.Error("Expected hook to not be marked as run.")
}
if res.Release.Info.Description != "Dry run complete" {
t.Errorf("unexpected description: %s", res.Release.Info.Description)
}
}
func TestInstallReleaseNoHooks(t *testing.T) {
@ -666,6 +711,11 @@ func TestUpdateRelease(t *testing.T) {
if res.Release.Version != 2 {
t.Errorf("Expected release version to be %v, got %v", 2, res.Release.Version)
}
edesc := "Upgrade complete"
if got := res.Release.Info.Description; got != edesc {
t.Errorf("Expected description %q, got %q", edesc, got)
}
}
func TestUpdateReleaseResetValues(t *testing.T) {
c := helm.NewContext()
@ -721,6 +771,11 @@ func TestUpdateReleaseFailure(t *testing.T) {
t.Errorf("Expected FAILED release. Got %d", updatedStatus)
}
edesc := "Upgrade \"angry-panda\" failed: Failed update in kube client"
if got := res.Release.Info.Description; got != edesc {
t.Errorf("Expected description %q, got %q", edesc, got)
}
oldRelease, err := rs.env.Releases.Get(rel.Name, rel.Version)
if err != nil {
t.Errorf("Expected to be able to get previous release")
@ -919,8 +974,8 @@ func TestRollbackRelease(t *testing.T) {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
if len(updated.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks))
if len(updated.Hooks) != 2 {
t.Fatalf("Expected 2 hooks, got %d", len(updated.Hooks))
}
if updated.Hooks[0].Manifest != manifestWithHook {
@ -973,6 +1028,10 @@ func TestRollbackRelease(t *testing.T) {
t.Errorf("unexpected output: %s", rel.Manifest)
}
if res.Release.Info.Description != "Rollback to 2" {
t.Errorf("Expected rollback to 2, got %q", res.Release.Info.Description)
}
}
func TestUninstallRelease(t *testing.T) {
@ -1004,6 +1063,10 @@ func TestUninstallRelease(t *testing.T) {
if res.Release.Info.Deleted.Seconds <= 0 {
t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds)
}
if res.Release.Info.Description != "Deletion complete" {
t.Errorf("Expected Deletion complete, got %q", res.Release.Info.Description)
}
}
func TestUninstallPurgeRelease(t *testing.T) {
@ -1372,7 +1435,19 @@ func TestListReleasesFilter(t *testing.T) {
}
}
func mockEnvironment() *environment.Environment {
func TestRunReleaseTest(t *testing.T) {
rs := rsFixture()
rel := namedReleaseStub("nemo", release.Status_DEPLOYED)
rs.env.Releases.Create(rel)
req := &services.TestReleaseRequest{Name: "nemo", Timeout: 2}
err := rs.RunReleaseTest(req, mockRunReleaseTestServer{})
if err != nil {
t.Fatalf("failed to run release tests on %s: %s", rel.Name, err)
}
}
func MockEnvironment() *environment.Environment {
e := environment.New()
e.Releases = storage.Init(driver.NewMemory())
e.KubeClient = &environment.PrintingKubeClient{Out: os.Stdout}
@ -1423,3 +1498,17 @@ func (l *mockListServer) RecvMsg(v interface{}) error { return nil }
func (l *mockListServer) SendHeader(m metadata.MD) error { return nil }
func (l *mockListServer) SetTrailer(m metadata.MD) {}
func (l *mockListServer) SetHeader(m metadata.MD) error { return nil }
type mockRunReleaseTestServer struct {
stream grpc.ServerStream
}
func (rs mockRunReleaseTestServer) Send(m *services.TestReleaseResponse) error {
return nil
}
func (rs mockRunReleaseTestServer) SetHeader(m metadata.MD) error { return nil }
func (rs mockRunReleaseTestServer) SendHeader(m metadata.MD) error { return nil }
func (rs mockRunReleaseTestServer) SetTrailer(m metadata.MD) {}
func (rs mockRunReleaseTestServer) SendMsg(v interface{}) error { return nil }
func (rs mockRunReleaseTestServer) RecvMsg(v interface{}) error { return nil }
func (rs mockRunReleaseTestServer) Context() context.Context { return helm.NewContext() }

Loading…
Cancel
Save