Skip to content

Commit

Permalink
Add support to ignore helm hooks while identifying the drifts
Browse files Browse the repository at this point in the history
  • Loading branch information
nikhilsbhat committed Jul 27, 2023
1 parent e320c93 commit 311763e
Show file tree
Hide file tree
Showing 13 changed files with 210 additions and 41 deletions.
4 changes: 4 additions & 0 deletions cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ func registerCommonFlags(cmd *cobra.Command) {
"kubernetes resource names to limit the drift identification (--kind takes higher precedence over --name)")
cmd.PersistentFlags().StringSliceVarP(&drifts.SkipKinds, "skip", "", nil,
"kubernetes resource names to skip the drift identification (ex: --skip Deployments)")
cmd.PersistentFlags().BoolVarP(&drifts.ConsiderHooks, "consider-hooks", "", false,
"when this is enabled, the flag 'ignore-hooks' holds no value")
cmd.PersistentFlags().StringSliceVarP(&drifts.IgnoreHookTypes, "ignore-hooks", "", []string{"hook-succeeded", "hook-failed"},
"list of hooks to ignore while identifying the drifts")
}

// Registers flags specific to command, run.
Expand Down
4 changes: 4 additions & 0 deletions completion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ flags:
commands:
- name: run
flags:
- consider-hooks
- custom-diff
- disable-error-on-drift
- from-release
- ignore-hooks
- json
- kind
- name
Expand All @@ -26,8 +28,10 @@ commands:
- yaml
- name: all
flags:
- consider-hooks
- custom-diff
- disable-error-on-drift
- ignore-hooks
- is-default-namespace
- json
- kind
Expand Down
2 changes: 1 addition & 1 deletion docs/doc/drift.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ drift [command] [flags]
* [drift run](drift_run.md) - Identifies drifts from a selected chart or release.
* [drift version](drift_version.md) - Command to fetch the version of helm-drift installed

###### Auto generated by spf13/cobra on 15-Apr-2023
###### Auto generated by spf13/cobra on 25-Jul-2023
4 changes: 3 additions & 1 deletion docs/doc/drift_all.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ drift all [flags]
### Options

```
--consider-hooks when this is enabled, the flag 'ignore-hooks' holds no value
--custom-diff KUBECTL_EXTERNAL_DIFF custom diff command to use instead of default, the command passed here would be set under KUBECTL_EXTERNAL_DIFF.More information can be found here https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#diff
-d, --disable-error-on-drift enabling this would disable exiting with error if drifts were identified (works only when --summary is enabled)
-h, --help help for all
--ignore-hooks strings list of hooks to ignore while identifying the drifts (default [hook-succeeded,hook-failed])
--is-default-namespace set this flag if drifts have to be checked specifically in 'default' namespace
-j, --json enable the flag to render drifts in json format (disabled by default)
--kind strings kubernetes resource names to limit the drift identification (--kind takes higher precedence over --name)
Expand Down Expand Up @@ -54,4 +56,4 @@ drift all [flags]

* [drift](drift.md) - A utility that helps in identifying drifts in infrastructure

###### Auto generated by spf13/cobra on 25-Apr-2023
###### Auto generated by spf13/cobra on 25-Jul-2023
4 changes: 3 additions & 1 deletion docs/doc/drift_run.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ drift run [RELEASE] [CHART] [flags]
### Options

```
--consider-hooks when this is enabled, the flag 'ignore-hooks' holds no value
--custom-diff KUBECTL_EXTERNAL_DIFF custom diff command to use instead of default, the command passed here would be set under KUBECTL_EXTERNAL_DIFF.More information can be found here https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#diff
-d, --disable-error-on-drift enabling this would disable exiting with error if drifts were identified (works only when --summary is enabled)
--from-release enable the flag to identify drifts from a release instead (disabled by default, works with command 'run' not with 'all')
-h, --help help for run
--ignore-hooks strings list of hooks to ignore while identifying the drifts (default [hook-succeeded,hook-failed])
-j, --json enable the flag to render drifts in json format (disabled by default)
--kind strings kubernetes resource names to limit the drift identification (--kind takes higher precedence over --name)
--name string name of the kubernetes resource to limit the drift identification
Expand Down Expand Up @@ -53,4 +55,4 @@ drift run [RELEASE] [CHART] [flags]

* [drift](drift.md) - A utility that helps in identifying drifts in infrastructure

###### Auto generated by spf13/cobra on 25-Apr-2023
###### Auto generated by spf13/cobra on 25-Jul-2023
2 changes: 1 addition & 1 deletion docs/doc/drift_version.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ drift version [flags]

* [drift](drift.md) - A utility that helps in identifying drifts in infrastructure

###### Auto generated by spf13/cobra on 15-Apr-2023
###### Auto generated by spf13/cobra on 25-Jul-2023
29 changes: 29 additions & 0 deletions example/chart/sample/templates/helm-hook-before.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-hook-before"
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:
# This is what defines this resource as a hook. Without this line, the
# job is considered part of the release.
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
template:
metadata:
name: "{{ .Release.Name }}"
labels:
app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
app.kubernetes.io/instance: {{ .Release.Name | quote }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
restartPolicy: Never
containers:
- name: post-install-job
image: "alpine:latest"
command: ["/bin/sleep","{{ default "10" .Values.sleepyTime }}"]
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ metadata:
# job is considered part of the release.
"helm.sh/hook": post-install
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded
"helm.sh/hook-delete-policy": hook-failed,hook-succeeded
spec:
template:
metadata:
Expand Down
3 changes: 2 additions & 1 deletion pkg/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const (
)

func (drift *Drift) renderToDisk(manifests []string, chartName, releaseName, releaseNamespace any) (deviation.DriftedRelease, error) {
manifests = NewHelmTemplates(manifests).FilterByHelmHook(drift)
manifests = NewHelmTemplates(manifests).FilterBySkip(drift)
manifests = NewHelmTemplates(manifests).FilterByKind(drift)
manifests = NewHelmTemplates(manifests).FilterByName(drift)
Expand Down Expand Up @@ -56,7 +57,7 @@ func (drift *Drift) renderToDisk(manifests []string, chartName, releaseName, rel

manifestPath := filepath.Join(templatePath, fmt.Sprintf("%s.%s.%s.yaml", template.Resource, template.Kind, releaseName))
if err = os.WriteFile(manifestPath, []byte(manifest), manifestFilePermission); err != nil {
log.Errorf("writting manifest '%s' to disk errored with '%v'", manifestPath, err)
log.Errorf("writing manifest '%s' to disk errored with '%v'", manifestPath, err)

return deviation.DriftedRelease{}, err
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/drift.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ type Drift struct {
CustomDiff string
All bool
IsDefaultNamespace bool
ConsiderHooks bool
Kind []string
SkipKinds []string
IgnoreHookTypes []string
Name string
release string
chart string
Expand Down
15 changes: 15 additions & 0 deletions pkg/from_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,21 @@ func (templates *HelmTemplates) FilterByName(drift *Drift) []string {
}).([]string)
}

func (templates *HelmTemplates) FilterByHelmHook(drift *Drift) []string {
if drift.ConsiderHooks {
return *templates
}

return funk.Filter(*templates, func(tmpl string) bool {
hook, err := k8s.NewResource().IsHelmHook(tmpl, drift.IgnoreHookTypes)
if err != nil {
log.Fatal(err)
}

return !hook
}).([]string)
}

func (templates *HelmTemplates) Get() ([]deviation.Deviation, error) {
deviations := make([]deviation.Deviation, 0)

Expand Down
56 changes: 50 additions & 6 deletions pkg/k8s/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package k8s

import (
"fmt"
"strings"

"github.com/nikhilsbhat/helm-drift/pkg/errors"
"github.com/thoas/go-funk"
"gopkg.in/yaml.v3"
)

Expand All @@ -16,6 +18,7 @@ type ResourceInterface interface {
GetName(dataMap string) (string, error)
GetKind(dataMap string) (string, error)
GetNameSpace(name, kind, dataMap string) (string, error)
IsHelmHook(dataMap string, hookKinds []string) (bool, error)
}

// GetName gets the name form the kubernetes resource.
Expand All @@ -26,8 +29,8 @@ func (resource *Resource) GetName(dataMap string) (string, error) {
}

if len(kindYaml) != 0 {
value, ok := kindYaml["metadata"].(map[string]interface{})["name"].(string)
if !ok {
value, failedManifest := kindYaml["metadata"].(map[string]interface{})["name"].(string)
if !failedManifest {
return "", &errors.DriftError{Message: "failed to get name from the manifest, 'name' is not type string"}
}

Expand All @@ -45,8 +48,8 @@ func (resource *Resource) GetKind(dataMap string) (string, error) {
}

if len(kindYaml) != 0 {
value, ok := kindYaml["kind"].(string)
if !ok {
value, failedManifest := kindYaml["kind"].(string)
if !failedManifest {
return "", &errors.DriftError{Message: "failed to get kube kind from the manifest, 'kind' is not type string"}
}

Expand All @@ -64,8 +67,8 @@ func (resource *Resource) GetNameSpace(name, kind, dataMap string) (string, erro
}

if len(kindYaml) != 0 {
value, ok := kindYaml["metadata"].(map[string]interface{})["namespace"].(string)
if !ok {
value, failedManifest := kindYaml["metadata"].(map[string]interface{})["namespace"].(string)
if !failedManifest {
return "", &errors.NotFoundError{Key: "namespace", Manifest: fmt.Sprintf("%s/%s", name, kind)}
}

Expand All @@ -75,6 +78,47 @@ func (resource *Resource) GetNameSpace(name, kind, dataMap string) (string, erro
return "", nil
}

// IsHelmHook gets the namespace form the kubernetes resource.
func (resource *Resource) IsHelmHook(dataMap string, hookKinds []string) (bool, error) {
var kindYaml map[string]interface{}
if err := yaml.Unmarshal([]byte(dataMap), &kindYaml); err != nil {
return false, err
}

if len(kindYaml) == 0 {
return false, nil
}

if _, failedManifest := kindYaml["metadata"].(map[string]interface{})["annotations"]; !failedManifest {
return false, nil
}

if _, failedManifest := kindYaml["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["helm.sh/hook"].(string); !failedManifest {
return false, &errors.NotFoundError{Key: "failed to identify the manifest as chart hook"}
}

hookType, failedManifest := kindYaml["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["helm.sh/hook-delete-policy"].(string)
if !failedManifest {
return false, &errors.NotFoundError{Key: "failed to identify the the chart hook type from the manifest"}
}

hookType = strings.TrimSpace(hookType)

hookTypes := make([]string, 0)

if len(strings.Split(hookType, ",")) > 1 {
hookTypes = strings.Split(hookType, ",")
}

for _, hkType := range hookTypes {
if funk.Contains(hookKinds, hkType) {
return true, nil
}
}

return false, nil
}

// NewResource returns aa new instance of ResourceInterface.
func NewResource() ResourceInterface {
return &Resource{}
Expand Down
Loading

0 comments on commit 311763e

Please sign in to comment.