Skip to content

Commit

Permalink
feat: make chart hook output configurable. (#18)
Browse files Browse the repository at this point in the history
Also logs a warning when chart hooks are contained within the output.

Relates to #16
  • Loading branch information
mgoltzsche authored May 23, 2021
1 parent 669fc6a commit e73b021
Show file tree
Hide file tree
Showing 27 changed files with 348 additions and 80 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ It exposes a `Helm` struct that provides a `Render()` function that returns the
| `exclude[].kind` | | Excludes resources by kind. |
| `exclude[].namespace` | | Excludes resources by namespace. |
| `exclude[].name` | | Excludes resources by name. |
| `excludeHooks` | `--no-hooks` | If enabled excludes chart hooks from the output. |
| `namespace` | `--namespace` | Set the namespace used by Helm templates. |
| `namespacedOnly` | `--namespaced-only` | If enabled fail on known cluster-scoped resources and those of unknown kinds. |
| `forceNamespace` | `--force-namespace` | Set namespace on all namespaced resources (and those of unknown kinds). |
Expand Down
3 changes: 1 addition & 2 deletions cmd/khelm/fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,9 @@ func mapOutputPaths(resources []*yaml.RNode, outputMappings []kptFnOutputMapping
continue
}

resID := meta.GetIdentifier()
outPath := defaultOutputPath
for i, m := range matchers {
if m.Match(&resID) {
if m.Match(&meta) {
outPath = outputMappings[i].OutputPath
break
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/khelm/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ func templateCommand(h *helm.Helm, writer io.Writer) *cobra.Command {
f.StringSliceVarP(&req.ValueFiles, "values", "f", nil, "Specify values in a YAML file or a URL (can specify multiple)")
f.StringSliceVar(&req.APIVersions, "api-versions", nil, "Kubernetes api versions used for Capabilities.APIVersions")
f.StringVar(&req.KubeVersion, "kube-version", req.KubeVersion, "Kubernetes version used as Capabilities.KubeVersion.Major/Minor")
f.BoolVar(&req.ExcludeHooks, "no-hooks", req.ExcludeHooks, "If enabled hooks are omitted from the output")
f.BoolVar(&req.ExcludeHooks, "exclude-hooks", req.ExcludeHooks, "If enabled hooks are omitted from the output")
f.Lookup("exclude-hooks").Hidden = true
f.StringVarP(&outOpts.FileOrDir, "output", "o", "-", "Write rendered output to given file or directory (as kustomization)")
f.BoolVar(&outOpts.Replace, "output-replace", false, "Delete and recreate the whole output directory or file")
return cmd
Expand Down
10 changes: 10 additions & 0 deletions cmd/khelm/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ func TestTemplateCommand(t *testing.T) {
[]string{filepath.Join(exampleDir, "force-namespace"), "--force-namespace=forced-namespace"},
5, "namespace: forced-namespace",
},
{
"chart-hooks",
[]string{filepath.Join(exampleDir, "chart-hooks")},
10, "helm.sh/hook",
},
{
"chart-hooks-excluded",
[]string{filepath.Join(exampleDir, "chart-hooks"), "--no-hooks"},
1, "myvalue",
},
} {
t.Run(c.name, func(t *testing.T) {
var out bytes.Buffer
Expand Down
3 changes: 2 additions & 1 deletion e2e/cli-tests.bats
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env bats

IMAGE=${IMAGE:-mgoltzsche/khelm:latest}
EXAMPLE_DIR="$(pwd)/example"
OUT_DIR="$(mktemp -d)"

teardown() {
Expand All @@ -18,7 +19,7 @@ teardown() {
}

@test "CLI should output kustomization" {
docker run --rm -u $(id -u):$(id -g) -v "$OUT_DIR:/out" -v "$(pwd)/example/namespace:/chart" "$IMAGE" template /chart \
docker run --rm -u $(id -u):$(id -g) -v "$OUT_DIR:/out" -v "$EXAMPLE_DIR/namespace:/chart" "$IMAGE" template /chart \
--output /out/kdir/ \
--debug
ls -la "$OUT_DIR" "$OUT_DIR/kdir" >&2
Expand Down
6 changes: 6 additions & 0 deletions example/chart-hooks-disabled/generator.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: khelm.mgoltzsche.github.com/v1
kind: ChartRenderer
metadata:
name: chart-hooks-disabled
chart: ../chart-hooks
excludeHooks: true
2 changes: 2 additions & 0 deletions example/chart-hooks-disabled/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
generators:
- generator.yaml
4 changes: 4 additions & 0 deletions example/chart-hooks/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
apiVersion: v1
description: example chart with helm hooks
name: hooks
version: 0.1.0
5 changes: 5 additions & 0 deletions example/chart-hooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Example with Helm Chart Hooks

This is an example kustomization that renders a local Helm chart that contains [hooks](https://helm.sh/docs/topics/charts_hooks/).

Corresponding to the `helm template` behaviour khelm returns all hook resources unless hooks are disabled explicitly.
8 changes: 8 additions & 0 deletions example/chart-hooks/generator.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: khelm.mgoltzsche.github.com/v1
kind: ChartRenderer
metadata:
name: chart-hooks
chart: .
exclude:
- kind: Job
name: chart-hooks-post-rollback
2 changes: 2 additions & 0 deletions example/chart-hooks/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
generators:
- generator.yaml
7 changes: 7 additions & 0 deletions example/chart-hooks/templates/configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: "{{ .Release.Name }}-myconfig"
namespace: {{ .Release.Namespace }}
data:
key: myvalue
21 changes: 21 additions & 0 deletions example/chart-hooks/templates/post-delete-hook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-post-delete"
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
app.kubernetes.io/instance: {{ .Release.Name | quote }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: task
image: "alpine:3.13"
21 changes: 21 additions & 0 deletions example/chart-hooks/templates/post-install-hook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-post-install"
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
app.kubernetes.io/instance: {{ .Release.Name | quote }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: task
image: "alpine:3.13"
16 changes: 16 additions & 0 deletions example/chart-hooks/templates/post-rollback-hook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-post-rollback"
namespace: {{ .Release.Namespace }}
annotations:
"helm.sh/hook": post-rollback
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: task
image: "alpine:3.13"
16 changes: 16 additions & 0 deletions example/chart-hooks/templates/post-upgrade-hook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-post-upgrade"
namespace: {{ .Release.Namespace }}
annotations:
"helm.sh/hook": post-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: task
image: "alpine:3.13"
16 changes: 16 additions & 0 deletions example/chart-hooks/templates/pre-delete-hook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-pre-delete"
namespace: {{ .Release.Namespace }}
annotations:
"helm.sh/hook": pre-delete
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: task
image: "alpine:3.13"
16 changes: 16 additions & 0 deletions example/chart-hooks/templates/pre-install-hook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-pre-install"
namespace: {{ .Release.Namespace }}
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: task
image: "alpine:3.13"
16 changes: 16 additions & 0 deletions example/chart-hooks/templates/pre-rollback-hook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-pre-rollback"
namespace: {{ .Release.Namespace }}
annotations:
"helm.sh/hook": pre-rollback
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: task
image: "alpine:3.13"
16 changes: 16 additions & 0 deletions example/chart-hooks/templates/pre-upgrade-hook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-pre-upgrade"
namespace: {{ .Release.Namespace }}
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: task
image: "alpine:3.13"
16 changes: 16 additions & 0 deletions example/chart-hooks/templates/test-hook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-test"
namespace: {{ .Release.Namespace }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: task
image: "alpine:3.13"
74 changes: 61 additions & 13 deletions internal/matcher/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,19 @@ package matcher

import (
"fmt"
"sort"
"strings"

"github.com/mgoltzsche/khelm/pkg/config"
"github.com/pkg/errors"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

// KubernetesResourceMeta represents a kubernetes resource's meta data
type KubernetesResourceMeta interface {
GetAPIVersion() string
GetKind() string
GetNamespace() string
GetName() string
}
const annotationHelmHook = "helm.sh/hook"

// ResourceMatchers is a group of matchers
type ResourceMatchers interface {
Match(o KubernetesResourceMeta) bool
Match(o *yaml.ResourceMeta) bool
RequireAllMatched() error
}

Expand All @@ -29,8 +25,8 @@ func Any() ResourceMatchers {

type matchAny struct{}

func (m *matchAny) RequireAllMatched() error { return nil }
func (m *matchAny) Match(KubernetesResourceMeta) bool { return true }
func (m *matchAny) RequireAllMatched() error { return nil }
func (m *matchAny) Match(*yaml.ResourceMeta) bool { return true }

type resourceMatchers []*resourceMatcher

Expand All @@ -53,17 +49,25 @@ func (m resourceMatchers) RequireAllMatched() error {
return nil
}

// MatchAny returns true if any matches matches the given object
func (m resourceMatchers) Match(o KubernetesResourceMeta) bool {
// Match returns true if any matches matches the given object
func (m resourceMatchers) Match(o *yaml.ResourceMeta) bool {
for _, e := range m {
if e.ResourceSelector.Match(o) {
if matchSelector(&e.ResourceSelector, o) {
e.Matched = true
return true
}
}
return false
}

// matchSelector returns true if all non-empty fields of the selector match the ones in the provided object
func matchSelector(id *config.ResourceSelector, o *yaml.ResourceMeta) bool {
return (id.APIVersion == "" || id.APIVersion == o.APIVersion) &&
(id.Kind == "" || id.Kind == o.Kind) &&
(id.Namespace == "" || id.Namespace == o.Namespace) &&
(id.Name == "" || id.Name == o.Name)
}

// FromResourceSelectors creates matchers from the provided selectors
func FromResourceSelectors(selectors []config.ResourceSelector) ResourceMatchers {
matchers := make([]*resourceMatcher, len(selectors))
Expand All @@ -72,3 +76,47 @@ func FromResourceSelectors(selectors []config.ResourceSelector) ResourceMatchers
}
return resourceMatchers(matchers)
}

// ChartHookMatcher matches chart hook resources when the delegated matcher doesn't match
type ChartHookMatcher struct {
ResourceMatchers
delegateOnly bool
hooks map[string]struct{}
}

// NewChartHookMatcher creates
func NewChartHookMatcher(delegate ResourceMatchers, delegateOnly bool) *ChartHookMatcher {
return &ChartHookMatcher{
ResourceMatchers: delegate,
delegateOnly: delegateOnly,
hooks: map[string]struct{}{},
}
}

// FoundHooks returns all hooks that weren't matched by the delegate matcher
func (m *ChartHookMatcher) FoundHooks() []string {
hooks := make([]string, 0, len(m.hooks))
for hook := range m.hooks {
hooks = append(hooks, hook)
}
sort.Strings(hooks)
return hooks
}

// Match returns true if any matches matches the given object
func (m *ChartHookMatcher) Match(o *yaml.ResourceMeta) bool {
if m.ResourceMatchers.Match(o) {
return true
}

isHook := false
if a := o.Annotations; a != nil {
for _, hook := range strings.Split(a[annotationHelmHook], ",") {
if hook = strings.TrimSpace(hook); hook != "" {
m.hooks[hook] = struct{}{}
isHook = true
}
}
}
return isHook && !m.delegateOnly
}
Loading

0 comments on commit e73b021

Please sign in to comment.