Unverified Commit 0699ec42 authored by Matt Butcher's avatar Matt Butcher Committed by GitHub

feat(tiller): support CRD installation (#3982)

This adds support for installing CRDs well before any other resource
kinds are installed.

This PR introduces a new hook, `crd-install`, that fires before
manifests are even validated. It is used to install a CRD before any
other part of a chart is installed.

Currently, this hook is _only implemented for install_. That means we
currently cannot add new CRDs during `helm upgrade`, nor can they
be rolled back. This is the safest configuration, as the update/rollback
cycle gets very challenging when CRDs are added and removed.
parent 5f4b260a
...@@ -34,6 +34,7 @@ message Hook { ...@@ -34,6 +34,7 @@ message Hook {
POST_ROLLBACK = 8; POST_ROLLBACK = 8;
RELEASE_TEST_SUCCESS = 9; RELEASE_TEST_SUCCESS = 9;
RELEASE_TEST_FAILURE = 10; RELEASE_TEST_FAILURE = 10;
CRD_INSTALL = 11;
} }
enum DeletePolicy { enum DeletePolicy {
SUCCEEDED = 0; SUCCEEDED = 0;
......
...@@ -271,6 +271,8 @@ message InstallReleaseRequest { ...@@ -271,6 +271,8 @@ message InstallReleaseRequest {
// wait, if true, will wait until all Pods, PVCs, and Services are in a ready state // wait, if true, will wait until all Pods, PVCs, and Services are in a ready state
// before marking the release as successful. It will wait for as long as timeout // before marking the release as successful. It will wait for as long as timeout
bool wait = 9; bool wait = 9;
bool disable_crd_hook = 10;
} }
// InstallReleaseResponse is the response from a release installation. // InstallReleaseResponse is the response from a release installation.
......
...@@ -106,28 +106,29 @@ charts in a repository, use 'helm search'. ...@@ -106,28 +106,29 @@ charts in a repository, use 'helm search'.
` `
type installCmd struct { type installCmd struct {
name string name string
namespace string namespace string
valueFiles valueFiles valueFiles valueFiles
chartPath string chartPath string
dryRun bool dryRun bool
disableHooks bool disableHooks bool
replace bool disableCRDHook bool
verify bool replace bool
keyring string verify bool
out io.Writer keyring string
client helm.Interface out io.Writer
values []string client helm.Interface
stringValues []string values []string
nameTemplate string stringValues []string
version string nameTemplate string
timeout int64 version string
wait bool timeout int64
repoURL string wait bool
username string repoURL string
password string username string
devel bool password string
depUp bool devel bool
depUp bool
certFile string certFile string
keyFile string keyFile string
...@@ -190,6 +191,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { ...@@ -190,6 +191,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into. Defaults to the current kube config namespace.") f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into. Defaults to the current kube config namespace.")
f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install") f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install")
f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install") f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install")
f.BoolVar(&inst.disableCRDHook, "no-crd-hook", false, "prevent CRD hooks from running, but run other hooks")
f.BoolVar(&inst.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production") f.BoolVar(&inst.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production")
f.StringArrayVar(&inst.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&inst.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&inst.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&inst.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
...@@ -273,6 +275,7 @@ func (i *installCmd) run() error { ...@@ -273,6 +275,7 @@ func (i *installCmd) run() error {
helm.InstallDryRun(i.dryRun), helm.InstallDryRun(i.dryRun),
helm.InstallReuseName(i.replace), helm.InstallReuseName(i.replace),
helm.InstallDisableHooks(i.disableHooks), helm.InstallDisableHooks(i.disableHooks),
helm.InstallDisableCRDHook(i.disableCRDHook),
helm.InstallTimeout(i.timeout), helm.InstallTimeout(i.timeout),
helm.InstallWait(i.wait)) helm.InstallWait(i.wait))
if err != nil { if err != nil {
...@@ -287,6 +290,10 @@ func (i *installCmd) run() error { ...@@ -287,6 +290,10 @@ func (i *installCmd) run() error {
// If this is a dry run, we can't display status. // If this is a dry run, we can't display status.
if i.dryRun { if i.dryRun {
// This is special casing to avoid breaking backward compatibility:
if res.Release.Info.Description != "Dry run complete" {
fmt.Fprintf(os.Stdout, "WARNING: %s\n", res.Release.Info.Description)
}
return nil return nil
} }
......
...@@ -16,6 +16,17 @@ Hooks work like regular templates, but they have special annotations ...@@ -16,6 +16,17 @@ Hooks work like regular templates, but they have special annotations
that cause Helm to utilize them differently. In this section, we cover that cause Helm to utilize them differently. In this section, we cover
the basic usage pattern for hooks. the basic usage pattern for hooks.
Hooks are declared as an annotation in the metadata section of a manifest:
```yaml
apiVersion: ...
kind: ....
metadata:
annotations:
"helm.sh/hook": "pre-install"
# ...
```
## The Available Hooks ## The Available Hooks
The following hooks are defined: The following hooks are defined:
...@@ -36,6 +47,8 @@ The following hooks are defined: ...@@ -36,6 +47,8 @@ The following hooks are defined:
rendered, but before any resources have been rolled back. rendered, but before any resources have been rolled back.
- post-rollback: Executes on a rollback request after all resources - post-rollback: Executes on a rollback request after all resources
have been modified. have been modified.
- crd-install: Adds CRD resources before any other checks are run. This is used
only on CRD definitions that are used by other manifests in the chart.
## Hooks and the Release Lifecycle ## Hooks and the Release Lifecycle
...@@ -62,7 +75,7 @@ hooks, the lifecycle is altered like this: ...@@ -62,7 +75,7 @@ hooks, the lifecycle is altered like this:
Kubernetes) Kubernetes)
5. Tiller sorts hooks by weight (assigning a weight of 0 by default) and by name for those hooks with the same weight in ascending order. 5. Tiller sorts hooks by weight (assigning a weight of 0 by default) and by name for those hooks with the same weight in ascending order.
6. Tiller then loads the hook with the lowest weight first (negative to positive) 6. Tiller then loads the hook with the lowest weight first (negative to positive)
7. Tiller waits until the hook is "Ready" 7. Tiller waits until the hook is "Ready" (except for CRDs)
8. Tiller loads the resulting resources into Kubernetes. Note that if the `--wait` 8. Tiller loads the resulting resources into Kubernetes. Note that if the `--wait`
flag is set, Tiller will wait until all resources are in a ready state flag is set, Tiller will wait until all resources are in a ready state
and will not run the `post-install` hook until they are ready. and will not run the `post-install` hook until they are ready.
...@@ -185,6 +198,52 @@ You can choose one or more defined annotation values: ...@@ -185,6 +198,52 @@ You can choose one or more defined annotation values:
* `"hook-failed"` specifies Tiller should delete the hook if the hook failed during execution. * `"hook-failed"` specifies Tiller should delete the hook if the hook failed during execution.
* `"before-hook-creation"` specifies Tiller should delete the previous hook before the new hook is launched. * `"before-hook-creation"` specifies Tiller should delete the previous hook before the new hook is launched.
### Defining a CRD with the `crd-install` Hook
Custom Resource Definitions (CRDs) are a special kind in Kubernetes. They provide
a way to define other kinds.
On occasion, a chart needs to both define a kind and then use it. This is done
with the `crd-install` hook.
The `crd-install` hook is executed very early during an installation, before
the rest of the manifests are verified. CRDs can be annotated with this hook so
that they are installed before any instances of that CRD are referenced. In this
way, when verification happens later, the CRDs will be available.
Here is an example of defining a CRD with a hook, and an instance of the CRD:
```yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: crontabs.stable.example.com
annotations:
"helm.sh/hook": crd-install
spec:
group: stable.example.com
version: v1
scope: Namespaced
names:
plural: crontabs
singular: crontab
kind: CronTab
shortNames:
- ct
```
And:
```yaml
apiVersion: stable.example.com/v1
kind: CronTab
metadata:
name: {{ .Release.Name }}-inst
```
Both of these can now be in the same chart, provided that the CRD is correctly
annotated.
### Automatically delete hook from previous release ### Automatically delete hook from previous release
When helm release being updated it is possible, that hook resource already exists in cluster. By default helm will try to create resource and fail with `"... already exists"` error. When helm release being updated it is possible, that hook resource already exists in cluster. By default helm will try to create resource and fail with `"... already exists"` error.
......
...@@ -84,6 +84,7 @@ helm install [CHART] ...@@ -84,6 +84,7 @@ helm install [CHART]
-n, --name string release name. If unspecified, it will autogenerate one for you -n, --name string release name. If unspecified, it will autogenerate one for you
--name-template string specify template used to name the release --name-template string specify template used to name the release
--namespace string namespace to install the release into. Defaults to the current kube config namespace. --namespace string namespace to install the release into. Defaults to the current kube config namespace.
--no-crd-hook prevent CRD hooks from running, but run other hooks
--no-hooks prevent hooks from running during install --no-hooks prevent hooks from running during install
--password string chart repository password where to locate the requested chart --password string chart repository password where to locate the requested chart
--replace re-use the given name, even if that name is already used. This is unsafe in production --replace re-use the given name, even if that name is already used. This is unsafe in production
...@@ -117,4 +118,4 @@ helm install [CHART] ...@@ -117,4 +118,4 @@ helm install [CHART]
### SEE ALSO ### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes. * [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 20-Mar-2018 ###### Auto generated by spf13/cobra on 27-Apr-2018
...@@ -97,6 +97,7 @@ func (h *Client) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ... ...@@ -97,6 +97,7 @@ func (h *Client) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...
req.Namespace = ns req.Namespace = ns
req.DryRun = reqOpts.dryRun req.DryRun = reqOpts.dryRun
req.DisableHooks = reqOpts.disableHooks req.DisableHooks = reqOpts.disableHooks
req.DisableCrdHook = reqOpts.disableCRDHook
req.ReuseName = reqOpts.reuseName req.ReuseName = reqOpts.reuseName
ctx := NewContext() ctx := NewContext()
......
...@@ -51,6 +51,8 @@ type options struct { ...@@ -51,6 +51,8 @@ type options struct {
force bool force bool
// if set, skip running hooks // if set, skip running hooks
disableHooks bool disableHooks bool
// if set, skip CRD hook only
disableCRDHook bool
// name of release // name of release
releaseName string releaseName string
// tls.Config to use for rpc if tls enabled // tls.Config to use for rpc if tls enabled
...@@ -295,6 +297,13 @@ func InstallDisableHooks(disable bool) InstallOption { ...@@ -295,6 +297,13 @@ func InstallDisableHooks(disable bool) InstallOption {
} }
} }
// InstallDisableCRDHook disables CRD hook during installation.
func InstallDisableCRDHook(disable bool) InstallOption {
return func(opts *options) {
opts.disableCRDHook = disable
}
}
// InstallReuseName will (if true) instruct Tiller to re-use an existing name. // InstallReuseName will (if true) instruct Tiller to re-use an existing name.
func InstallReuseName(reuse bool) InstallOption { func InstallReuseName(reuse bool) InstallOption {
return func(opts *options) { return func(opts *options) {
......
...@@ -41,6 +41,7 @@ const ( ...@@ -41,6 +41,7 @@ const (
PostRollback = "post-rollback" PostRollback = "post-rollback"
ReleaseTestSuccess = "test-success" ReleaseTestSuccess = "test-success"
ReleaseTestFailure = "test-failure" ReleaseTestFailure = "test-failure"
CRDInstall = "crd-install"
) )
// Type of policy for deleting the hook // Type of policy for deleting the hook
......
...@@ -52,6 +52,7 @@ const ( ...@@ -52,6 +52,7 @@ const (
Hook_POST_ROLLBACK Hook_Event = 8 Hook_POST_ROLLBACK Hook_Event = 8
Hook_RELEASE_TEST_SUCCESS Hook_Event = 9 Hook_RELEASE_TEST_SUCCESS Hook_Event = 9
Hook_RELEASE_TEST_FAILURE Hook_Event = 10 Hook_RELEASE_TEST_FAILURE Hook_Event = 10
Hook_CRD_INSTALL Hook_Event = 11
) )
var Hook_Event_name = map[int32]string{ var Hook_Event_name = map[int32]string{
...@@ -66,6 +67,7 @@ var Hook_Event_name = map[int32]string{ ...@@ -66,6 +67,7 @@ var Hook_Event_name = map[int32]string{
8: "POST_ROLLBACK", 8: "POST_ROLLBACK",
9: "RELEASE_TEST_SUCCESS", 9: "RELEASE_TEST_SUCCESS",
10: "RELEASE_TEST_FAILURE", 10: "RELEASE_TEST_FAILURE",
11: "CRD_INSTALL",
} }
var Hook_Event_value = map[string]int32{ var Hook_Event_value = map[string]int32{
"UNKNOWN": 0, "UNKNOWN": 0,
...@@ -79,6 +81,7 @@ var Hook_Event_value = map[string]int32{ ...@@ -79,6 +81,7 @@ var Hook_Event_value = map[string]int32{
"POST_ROLLBACK": 8, "POST_ROLLBACK": 8,
"RELEASE_TEST_SUCCESS": 9, "RELEASE_TEST_SUCCESS": 9,
"RELEASE_TEST_FAILURE": 10, "RELEASE_TEST_FAILURE": 10,
"CRD_INSTALL": 11,
} }
func (x Hook_Event) String() string { func (x Hook_Event) String() string {
...@@ -199,33 +202,34 @@ func init() { ...@@ -199,33 +202,34 @@ func init() {
func init() { proto.RegisterFile("hapi/release/hook.proto", fileDescriptor0) } func init() { proto.RegisterFile("hapi/release/hook.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 445 bytes of a gzipped FileDescriptorProto // 453 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0x51, 0x8f, 0x9a, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0x51, 0x8f, 0x9a, 0x40,
0x10, 0x80, 0x8f, 0x13, 0x41, 0x47, 0xcf, 0xdb, 0x6e, 0x9a, 0x76, 0xe3, 0xcb, 0x19, 0x9f, 0x7c, 0x10, 0x80, 0x8f, 0x53, 0x41, 0x47, 0xcf, 0xdb, 0x6e, 0x9a, 0x76, 0xe3, 0xcb, 0x19, 0x9f, 0x7c,
0xc2, 0xe6, 0x9a, 0xfe, 0x00, 0x84, 0xb9, 0x6a, 0x24, 0x60, 0x16, 0x4c, 0x93, 0xbe, 0x10, 0xae, 0xc2, 0xe6, 0x9a, 0xfe, 0x00, 0x84, 0xb9, 0x6a, 0x24, 0x60, 0x16, 0x4c, 0x93, 0xbe, 0x10, 0xae,
0xee, 0x29, 0x11, 0x81, 0x08, 0xb6, 0xe9, 0x0f, 0xec, 0x3f, 0xe8, 0x0f, 0x6a, 0x76, 0x45, 0x7b, 0xee, 0x29, 0x11, 0x81, 0x08, 0xb6, 0xe9, 0x1f, 0xed, 0x3f, 0xe8, 0xff, 0x68, 0x76, 0x45, 0x7a,
0x49, 0xfb, 0x36, 0xf3, 0xcd, 0x37, 0xc3, 0x0c, 0x0b, 0xef, 0x77, 0x49, 0x99, 0x4e, 0x8f, 0x22, 0x49, 0xfb, 0x36, 0xf3, 0xcd, 0xb7, 0xb3, 0x33, 0xbb, 0xf0, 0x7e, 0x1f, 0x17, 0xc9, 0xec, 0x24,
0x13, 0x49, 0x25, 0xa6, 0xbb, 0xa2, 0xd8, 0x5b, 0xe5, 0xb1, 0xa8, 0x0b, 0xda, 0x97, 0x05, 0xab, 0x52, 0x11, 0x97, 0x62, 0xb6, 0xcf, 0xf3, 0x83, 0x59, 0x9c, 0xf2, 0x2a, 0xa7, 0x03, 0x59, 0x30,
0x29, 0x0c, 0x1f, 0xb6, 0x45, 0xb1, 0xcd, 0xc4, 0x54, 0xd5, 0x9e, 0x4f, 0x2f, 0xd3, 0x3a, 0x3d, 0xeb, 0xc2, 0xe8, 0x61, 0x97, 0xe7, 0xbb, 0x54, 0xcc, 0x54, 0xed, 0xf9, 0xfc, 0x32, 0xab, 0x92,
0x88, 0xaa, 0x4e, 0x0e, 0xe5, 0x59, 0x1f, 0xff, 0xd2, 0x41, 0x9f, 0x17, 0xc5, 0x9e, 0x52, 0xd0, 0xa3, 0x28, 0xab, 0xf8, 0x58, 0x5c, 0xf4, 0xc9, 0xaf, 0x36, 0xb4, 0x17, 0x79, 0x7e, 0xa0, 0x14,
0xf3, 0xe4, 0x20, 0x98, 0x36, 0xd2, 0x26, 0x5d, 0xae, 0x62, 0xc9, 0xf6, 0x69, 0xbe, 0x61, 0xb7, 0xda, 0x59, 0x7c, 0x14, 0x4c, 0x1b, 0x6b, 0xd3, 0x1e, 0x57, 0xb1, 0x64, 0x87, 0x24, 0xdb, 0xb2,
0x67, 0x26, 0x63, 0xc9, 0xca, 0xa4, 0xde, 0xb1, 0xd6, 0x99, 0xc9, 0x98, 0x0e, 0xa1, 0x73, 0x48, 0xdb, 0x0b, 0x93, 0xb1, 0x64, 0x45, 0x5c, 0xed, 0x59, 0xeb, 0xc2, 0x64, 0x4c, 0x47, 0xd0, 0x3d,
0xf2, 0xf4, 0x45, 0x54, 0x35, 0xd3, 0x15, 0xbf, 0xe6, 0xf4, 0x03, 0x18, 0xe2, 0xbb, 0xc8, 0xeb, 0xc6, 0x59, 0xf2, 0x22, 0xca, 0x8a, 0xb5, 0x15, 0x6f, 0x72, 0xfa, 0x01, 0x74, 0xf1, 0x5d, 0x64,
0x8a, 0xb5, 0x47, 0xad, 0xc9, 0xe0, 0x91, 0x59, 0xaf, 0x17, 0xb4, 0xe4, 0xb7, 0x2d, 0x94, 0x02, 0x55, 0xc9, 0x3a, 0xe3, 0xd6, 0x74, 0xf8, 0xc8, 0xcc, 0xd7, 0x03, 0x9a, 0xf2, 0x6e, 0x13, 0xa5,
0x6f, 0x3c, 0xfa, 0x09, 0x3a, 0x59, 0x52, 0xd5, 0xf1, 0xf1, 0x94, 0x33, 0x63, 0xa4, 0x4d, 0x7a, 0xc0, 0x6b, 0x8f, 0x7e, 0x82, 0x6e, 0x1a, 0x97, 0x55, 0x74, 0x3a, 0x67, 0x4c, 0x1f, 0x6b, 0xd3,
0x8f, 0x43, 0xeb, 0x7c, 0x86, 0x75, 0x39, 0xc3, 0x8a, 0x2e, 0x67, 0x70, 0x53, 0xba, 0xfc, 0x94, 0xfe, 0xe3, 0xc8, 0xbc, 0xac, 0x61, 0x5e, 0xd7, 0x30, 0xc3, 0xeb, 0x1a, 0xdc, 0x90, 0x2e, 0x3f,
0xd3, 0x77, 0x60, 0xfc, 0x10, 0xe9, 0x76, 0x57, 0x33, 0x73, 0xa4, 0x4d, 0xda, 0xbc, 0xc9, 0xe8, 0x67, 0xf4, 0x1d, 0xe8, 0x3f, 0x44, 0xb2, 0xdb, 0x57, 0xcc, 0x18, 0x6b, 0xd3, 0x0e, 0xaf, 0x33,
0x1c, 0xee, 0x37, 0x22, 0x13, 0xb5, 0x88, 0xcb, 0x22, 0x4b, 0xbf, 0xa5, 0xa2, 0x62, 0x1d, 0xb5, 0xba, 0x80, 0xfb, 0xad, 0x48, 0x45, 0x25, 0xa2, 0x22, 0x4f, 0x93, 0x6f, 0x89, 0x28, 0x59, 0x57,
0xc9, 0xc3, 0x7f, 0x36, 0x71, 0x95, 0xb9, 0x92, 0xe2, 0x4f, 0x3e, 0xd8, 0xfc, 0xcd, 0x52, 0x51, 0x4d, 0xf2, 0xf0, 0x9f, 0x49, 0x1c, 0x65, 0xae, 0xa5, 0xf8, 0x93, 0x0f, 0xb7, 0x7f, 0xb3, 0x44,
0x8d, 0x7f, 0x6b, 0xd0, 0x56, 0xab, 0xd2, 0x1e, 0x98, 0x6b, 0x7f, 0xe9, 0x07, 0x5f, 0x7c, 0x72, 0x94, 0x93, 0xdf, 0x1a, 0x74, 0xd4, 0xa8, 0xb4, 0x0f, 0xc6, 0xc6, 0x5b, 0x79, 0xfe, 0x17, 0x8f,
0x43, 0xef, 0xa1, 0xb7, 0xe2, 0x18, 0x2f, 0xfc, 0x30, 0xb2, 0x3d, 0x8f, 0x68, 0x94, 0x40, 0x7f, 0xdc, 0xd0, 0x7b, 0xe8, 0xaf, 0x39, 0x46, 0x4b, 0x2f, 0x08, 0x2d, 0xd7, 0x25, 0x1a, 0x25, 0x30,
0x15, 0x84, 0xd1, 0x95, 0xdc, 0xd2, 0x01, 0x80, 0x54, 0x5c, 0xf4, 0x30, 0x42, 0xd2, 0x52, 0x2d, 0x58, 0xfb, 0x41, 0xd8, 0x90, 0x5b, 0x3a, 0x04, 0x90, 0x8a, 0x83, 0x2e, 0x86, 0x48, 0x5a, 0xea,
0xd2, 0x68, 0x80, 0x7e, 0x99, 0xb1, 0x5e, 0x7d, 0xe6, 0xb6, 0x8b, 0xa4, 0x7d, 0x9d, 0x71, 0x21, 0x88, 0x34, 0x6a, 0xd0, 0xbe, 0xf6, 0xd8, 0xac, 0x3f, 0x73, 0xcb, 0x41, 0xd2, 0x69, 0x7a, 0x5c,
0x86, 0x22, 0x1c, 0x63, 0x1e, 0x78, 0xde, 0xcc, 0x76, 0x96, 0xc4, 0xa4, 0x6f, 0xe0, 0x4e, 0x39, 0x89, 0xae, 0x08, 0xc7, 0x88, 0xfb, 0xae, 0x3b, 0xb7, 0xec, 0x15, 0x31, 0xe8, 0x1b, 0xb8, 0x53,
0x57, 0xd4, 0xa1, 0x0c, 0xde, 0x72, 0xf4, 0xd0, 0x0e, 0x31, 0x8e, 0x30, 0x8c, 0xe2, 0x70, 0xed, 0x4e, 0x83, 0xba, 0x94, 0xc1, 0x5b, 0x8e, 0x2e, 0x5a, 0x01, 0x46, 0x21, 0x06, 0x61, 0x14, 0x6c,
0x38, 0x18, 0x86, 0xa4, 0xfb, 0x4f, 0xe5, 0xc9, 0x5e, 0x78, 0x6b, 0x8e, 0x04, 0xc6, 0x0e, 0xf4, 0x6c, 0x1b, 0x83, 0x80, 0xf4, 0xfe, 0xa9, 0x3c, 0x59, 0x4b, 0x77, 0xc3, 0x91, 0x80, 0xbc, 0xdb,
0x5f, 0x9f, 0x4d, 0xef, 0xa0, 0xab, 0xda, 0xd0, 0x45, 0x97, 0xdc, 0x50, 0x00, 0x43, 0xba, 0xe8, 0xe6, 0x4e, 0x33, 0x6d, 0x7f, 0x62, 0xc3, 0xe0, 0xf5, 0x3b, 0xd0, 0x3b, 0xe8, 0xa9, 0x3e, 0xe8,
0x12, 0x4d, 0x0e, 0x99, 0xe1, 0x53, 0xc0, 0x31, 0x9e, 0x07, 0xc1, 0x32, 0x76, 0x38, 0xda, 0xd1, 0xa0, 0x43, 0x6e, 0x28, 0x80, 0x2e, 0x0f, 0xa3, 0x43, 0x34, 0xd9, 0x75, 0x8e, 0x4f, 0x3e, 0xc7,
0x22, 0xf0, 0xc9, 0xed, 0xac, 0xfb, 0xd5, 0x6c, 0x7e, 0xe4, 0xb3, 0xa1, 0x5e, 0xe9, 0xe3, 0x9f, 0x68, 0xe1, 0xfb, 0xab, 0xc8, 0xe6, 0x68, 0x85, 0x4b, 0xdf, 0x23, 0xb7, 0xf3, 0xde, 0x57, 0xa3,
0x00, 0x00, 0x00, 0xff, 0xff, 0x13, 0x64, 0x75, 0x6c, 0xa3, 0x02, 0x00, 0x00, 0x7e, 0xd9, 0x67, 0x5d, 0x7d, 0xdb, 0xc7, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x3b, 0xcf, 0xed,
0xd9, 0xb4, 0x02, 0x00, 0x00,
} }
This diff is collapsed.
...@@ -42,6 +42,7 @@ var events = map[string]release.Hook_Event{ ...@@ -42,6 +42,7 @@ var events = map[string]release.Hook_Event{
hooks.PostRollback: release.Hook_POST_ROLLBACK, hooks.PostRollback: release.Hook_POST_ROLLBACK,
hooks.ReleaseTestSuccess: release.Hook_RELEASE_TEST_SUCCESS, hooks.ReleaseTestSuccess: release.Hook_RELEASE_TEST_SUCCESS,
hooks.ReleaseTestFailure: release.Hook_RELEASE_TEST_FAILURE, hooks.ReleaseTestFailure: release.Hook_RELEASE_TEST_FAILURE,
hooks.CRDInstall: release.Hook_CRD_INSTALL,
} }
// deletePolices represents a mapping between the key in the annotation for label deleting policy and its real meaning // deletePolices represents a mapping between the key in the annotation for label deleting policy and its real meaning
...@@ -137,10 +138,6 @@ func (file *manifestFile) sort(result *result) error { ...@@ -137,10 +138,6 @@ func (file *manifestFile) sort(result *result) error {
return e return e
} }
if entry.Version != "" && !file.apis.Has(entry.Version) {
return fmt.Errorf("apiVersion %q in %s is not available", entry.Version, file.path)
}
if !hasAnyAnnotation(entry) { if !hasAnyAnnotation(entry) {
result.generic = append(result.generic, Manifest{ result.generic = append(result.generic, Manifest{
Name: file.path, Name: file.path,
...@@ -199,7 +196,6 @@ func (file *manifestFile) sort(result *result) error { ...@@ -199,7 +196,6 @@ func (file *manifestFile) sort(result *result) error {
} }
}) })
} }
return nil return nil
} }
......
...@@ -127,20 +127,59 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re ...@@ -127,20 +127,59 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
rel.Info.Status.Notes = notesTxt rel.Info.Status.Notes = notesTxt
} }
err = validateManifest(s.env.KubeClient, req.Namespace, manifestDoc.Bytes()) return rel, nil
return rel, err }
func hasCRDHook(hs []*release.Hook) bool {
for _, h := range hs {
for _, e := range h.Events {
if e == events[hooks.CRDInstall] {
return true
}
}
}
return false
} }
// performRelease runs a release. // performRelease runs a release.
func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) {
res := &services.InstallReleaseResponse{Release: r} res := &services.InstallReleaseResponse{Release: r}
manifestDoc := []byte(r.Manifest)
if req.DryRun { if req.DryRun {
s.Log("dry run for %s", r.Name) s.Log("dry run for %s", r.Name)
if !req.DisableCrdHook && hasCRDHook(r.Hooks) {
s.Log("validation skipped because CRD hook is present")
res.Release.Info.Description = "Validation skipped because CRDs are not installed"
return res, nil
}
// Here's the problem with dry runs and CRDs: We can't install a CRD
// during a dry run, which means it cannot be validated.
if err := validateManifest(s.env.KubeClient, req.Namespace, manifestDoc); err != nil {
return res, err
}
res.Release.Info.Description = "Dry run complete" res.Release.Info.Description = "Dry run complete"
return res, nil return res, nil
} }
// crd-install hooks
if !req.DisableHooks && !req.DisableCrdHook {
if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.CRDInstall, req.Timeout); err != nil {
fmt.Printf("Finished installing CRD: %s", err)
return res, err
}
} else {
s.Log("CRD install hooks disabled for %s", req.Name)
}
// Because the CRDs are installed, they are used for validation during this step.
if err := validateManifest(s.env.KubeClient, req.Namespace, manifestDoc); err != nil {
return res, fmt.Errorf("validation failed: %s", err)
}
// pre-install hooks // pre-install hooks
if !req.DisableHooks { if !req.DisableHooks {
if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil { if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil {
......
...@@ -22,11 +22,44 @@ import ( ...@@ -22,11 +22,44 @@ import (
"testing" "testing"
"k8s.io/helm/pkg/helm" "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/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/version" "k8s.io/helm/pkg/version"
) )
func TestHasCRDHook(t *testing.T) {
tests := []struct {
hooks []*release.Hook
expect bool
}{
{
hooks: []*release.Hook{
{Events: []release.Hook_Event{release.Hook_PRE_DELETE}},
},
expect: false,
},
{
hooks: []*release.Hook{
{Events: []release.Hook_Event{release.Hook_CRD_INSTALL}},
},
expect: true,
},
{
hooks: []*release.Hook{
{Events: []release.Hook_Event{release.Hook_PRE_UPGRADE, release.Hook_CRD_INSTALL}},
},
expect: true,
},
}
for i, tt := range tests {
if tt.expect != hasCRDHook(tt.hooks) {
t.Errorf("test %d: expected %t, got %t", i, tt.expect, !tt.expect)
}
}
}
func TestInstallRelease(t *testing.T) { func TestInstallRelease(t *testing.T) {
c := helm.NewContext() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
...@@ -335,6 +368,55 @@ func TestInstallRelease_NoHooks(t *testing.T) { ...@@ -335,6 +368,55 @@ func TestInstallRelease_NoHooks(t *testing.T) {
} }
} }
func TestInstallRelease_CRDInstallHook(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rs.env.Releases.Create(releaseStub())
req := installRequest()
req.Chart.Templates = append(req.Chart.Templates, &chart.Template{
Name: "templates/crdhook",
Data: []byte(manifestWithCRDHook),
})
res, err := rs.InstallRelease(c, req)
if err != nil {
t.Errorf("Failed install: %s", err)
}
// The new hook should have been pulled from the manifest.
if l := len(res.Release.Hooks); l != 2 {
t.Fatalf("expected 2 hooks, got %d", l)
}
expect := "Install complete"
if got := res.Release.Info.Description; got != expect {
t.Errorf("Expected Description to be %q, got %q", expect, got)
}
}
func TestInstallRelease_DryRunCRDInstallHook(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rs.env.Releases.Create(releaseStub())
req := installRequest(withDryRun())
req.Chart.Templates = append(req.Chart.Templates, &chart.Template{
Name: "templates/crdhook",
Data: []byte(manifestWithCRDHook),
})
res, err := rs.InstallRelease(c, req)
if err != nil {
t.Errorf("Failed install: %s", err)
}
expect := "Validation skipped because CRDs are not installed"
if res.Release.Info.Description != expect {
t.Errorf("Expected Description %q, got %q", expect, res.Release.Info.Description)
}
}
func TestInstallRelease_FailedHooks(t *testing.T) { func TestInstallRelease_FailedHooks(t *testing.T) {
c := helm.NewContext() c := helm.NewContext()
rs := rsFixture() rs := rsFixture()
......
...@@ -363,7 +363,7 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin ...@@ -363,7 +363,7 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin
executingHooks = sortByHookWeight(executingHooks) executingHooks = sortByHookWeight(executingHooks)
for _, h := range executingHooks { for _, h := range executingHooks {
if err := s.deleteHookIfShouldBeDeletedByDeletePolicy(h, hooks.BeforeHookCreation, name, namespace, hook, kubeCli); err != nil { if err := s.deleteHookByPolicy(h, hooks.BeforeHookCreation, name, namespace, hook, kubeCli); err != nil {
return err return err
} }
...@@ -376,14 +376,17 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin ...@@ -376,14 +376,17 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin
b.Reset() b.Reset()
b.WriteString(h.Manifest) b.WriteString(h.Manifest)
if err := kubeCli.WatchUntilReady(namespace, b, timeout, false); err != nil { // We can't watch CRDs
s.Log("warning: Release %s %s %s could not complete: %s", name, hook, h.Path, err) if hook != hooks.CRDInstall {
// If a hook is failed, checkout the annotation of the hook to determine whether the hook should be deleted if err := kubeCli.WatchUntilReady(namespace, b, timeout, false); err != nil {
// under failed condition. If so, then clear the corresponding resource object in the hook s.Log("warning: Release %s %s %s could not complete: %s", name, hook, h.Path, err)
if err := s.deleteHookIfShouldBeDeletedByDeletePolicy(h, hooks.HookFailed, name, namespace, hook, kubeCli); err != nil { // If a hook is failed, checkout the annotation of the hook to determine whether the hook should be deleted
// under failed condition. If so, then clear the corresponding resource object in the hook
if err := s.deleteHookByPolicy(h, hooks.HookFailed, name, namespace, hook, kubeCli); err != nil {
return err
}
return err return err
} }
return err
} }
} }
...@@ -391,7 +394,7 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin ...@@ -391,7 +394,7 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin
// If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted // If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted
// under succeeded condition. If so, then clear the corresponding resource object in each hook // under succeeded condition. If so, then clear the corresponding resource object in each hook
for _, h := range executingHooks { for _, h := range executingHooks {
if err := s.deleteHookIfShouldBeDeletedByDeletePolicy(h, hooks.HookSucceeded, name, namespace, hook, kubeCli); err != nil { if err := s.deleteHookByPolicy(h, hooks.HookSucceeded, name, namespace, hook, kubeCli); err != nil {
return err return err
} }
h.LastRun = timeconv.Now() h.LastRun = timeconv.Now()
...@@ -418,7 +421,7 @@ func validateReleaseName(releaseName string) error { ...@@ -418,7 +421,7 @@ func validateReleaseName(releaseName string) error {
return nil return nil
} }
func (s *ReleaseServer) deleteHookIfShouldBeDeletedByDeletePolicy(h *release.Hook, policy string, name, namespace, hook string, kubeCli environment.KubeClient) error { func (s *ReleaseServer) deleteHookByPolicy(h *release.Hook, policy string, name, namespace, hook string, kubeCli environment.KubeClient) error {
b := bytes.NewBufferString(h.Manifest) b := bytes.NewBufferString(h.Manifest)
if hookHasDeletePolicy(h, policy) { if hookHasDeletePolicy(h, policy) {
s.Log("deleting %s hook %s for release %s due to %q policy", hook, h.Name, name, policy) s.Log("deleting %s hook %s for release %s due to %q policy", hook, h.Name, name, policy)
......
...@@ -55,6 +55,25 @@ metadata: ...@@ -55,6 +55,25 @@ metadata:
data: data:
name: value` name: value`
var manifestWithCRDHook = `
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: crontabs.stable.example.com
annotations:
"helm.sh/hook": crd-install
spec:
group: stable.example.com
version: v1
scope: Namespaced
names:
plural: crontabs
singular: crontab
kind: CronTab
shortNames:
- ct
`
var manifestWithTestHook = `kind: Pod var manifestWithTestHook = `kind: Pod
metadata: metadata:
name: finding-nemo, name: finding-nemo,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment