diff --git a/cmd/root.go b/cmd/root.go index 0102416..27630ee 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -142,6 +142,18 @@ func init() { klog.Exitf("Failed to bind show-old flag: %v", err) } + findCmd.Flags().StringSlice("release-ignore-list", []string{}, "List of Helm release names to ignore") + err = viper.BindPFlag("release-ignore-list", findCmd.Flags().Lookup("release-ignore-list")) + if err != nil { + klog.Exitf("Failed to bind release-ignore-list flag: %v", err) + } + + findCmd.Flags().StringSlice("chart-ignore-list", []string{}, "List of Helm chart names to ignore") + err = viper.BindPFlag("chart-ignore-list", findCmd.Flags().Lookup("chart-ignore-list")) + if err != nil { + klog.Exitf("Failed to bind chart-ignore-list flag: %v", err) + } + klog.InitFlags(nil) _ = flag.Set("alsologtostderr", "true") _ = flag.Set("logtostderr", "true") @@ -365,7 +377,7 @@ func handleHelm(kubeContext string) (*output.Output, error) { } else { klog.V(3).Infof("Scanning whole cluster") } - releases, _, err := h.GetReleaseOutput(namespace) + releases, _, err := h.GetReleaseOutput(namespace, viper.GetStringSlice("release-ignore-list"), viper.GetStringSlice("chart-ignore-list")) if err != nil { return nil, fmt.Errorf("error getting helm releases: %s", err) } diff --git a/docs/usage.md b/docs/usage.md index e26aa71..c3f6cb4 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -12,24 +12,27 @@ nova find --wide ## Options ``` Flags: - --containers Show old container image versions. There will be no helm output unless the --helm flag is set as well - --helm Show old helm releases. You can combine this flag with `--containers` to have both output in a single run. - -h, --help help for find - --show-errored-containers When finding container images, show errors encountered when scanning. - --show-non-semver When finding container images, show all containers even if they don't follow semver. - -t, --timeout uint16 When finding container images, the time in seconds before canceling the operation. (default 10) + --chart-ignore-list strings List of Helm chart names to ignore + --containers Show old container image versions instead of helm chart versions. There will be no helm output if this flag is set. + --helm Show old helm chart versions. You can combine this flag with --containers to have both output in a single run. + -h, --help help for find + --release-ignore-list strings List of Helm release names to ignore + --show-errored-containers When finding container images, show errors encountered when scanning. + --show-non-semver When finding container images, show all containers even if they don't follow semver. + -t, --timeout uint16 When finding container images, the time in seconds before canceling the operation. (default 10) Global Flags: - --alsologtostderr log to standard error as well as files (default true) + --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) (default true) --config string Config file to use. If empty, flags will be used instead --context string A context to use in the kubeconfig. -d, --desired-versions stringToString A map of chart=override_version to override the helm repository when checking. (default []) + --format string An output format (table, json) (default "json") -a, --include-all Show all charts even if no latest version is found. --logtostderr log to standard error instead of files (default true) -n, --namespace string Namespace to look in. If empty, scan will be cluster-wide --output-file string Path on local filesystem to write file output to --poll-artifacthub When true, polls artifacthub to match against helm releases in the cluster. If false, you must provide a url list via --url/-u. Default is true. (default true) - --show-old Only output charts that are not on the latest version + --show-old Only show charts that are not on the latest version -u, --url strings URL for a helm chart repo -v, --v Level number for the log level verbosity --wide Output chart name and namespace diff --git a/pkg/helm/cluster.go b/pkg/helm/cluster.go index a1a8598..56e8d08 100644 --- a/pkg/helm/cluster.go +++ b/pkg/helm/cluster.go @@ -46,9 +46,9 @@ func NewHelm(kubeContext string) *Helm { } // GetReleaseOutput returns releases and chart names -func (h *Helm) GetReleaseOutput(namespace string) ([]*release.Release, []string, error) { +func (h *Helm) GetReleaseOutput(namespace string, releaseIgnoreList []string, chartIgnoreList []string) ([]*release.Release, []string, error) { var chartNames = []string{} - outputObjects, err := h.GetHelmReleases(namespace) + outputObjects, err := h.GetHelmReleases(namespace, releaseIgnoreList, chartIgnoreList) if err != nil { err = fmt.Errorf("could not detect helm 3 charts: %v", err) } @@ -62,15 +62,17 @@ func (h *Helm) GetReleaseOutput(namespace string) ([]*release.Release, []string, } // GetHelmReleases returns a list of helm releases from the cluster -func (h *Helm) GetHelmReleases(namespace string) ([]*release.Release, error) { +func (h *Helm) GetHelmReleases(namespace string, releaseIgnoreList []string, chartIgnoreList []string) ([]*release.Release, error) { hs := helmdriver.NewSecrets(h.Kube.Client.CoreV1().Secrets(namespace)) helmClient := helmstorage.Init(hs) deployed, err := helmClient.ListDeployed() + filteredDeployed := filterIgnoredReleases(deployed, releaseIgnoreList, chartIgnoreList) + if err != nil { return nil, err } - return deployed, nil + return filteredDeployed, nil } // OverrideDesiredVersion accepts a list of releases and overrides the version stored in the helm struct where required @@ -87,3 +89,35 @@ func (h *Helm) OverrideDesiredVersion(rls *output.ReleaseOutput) { } } } + +// filterIgnoredReleases is a helper function that removes charts that match a release name or chart name +// provided by the user at runtime from the list of found charts in the cluster +func filterIgnoredReleases(deployed []*release.Release, releaseIgnoreList []string, chartIgnoreList []string) []*release.Release { + // Filter out any ignored releases + filteredDeployed := []*release.Release{} + + for _, release := range deployed { + isIgnoredRelease := false + isIgnoredChart := false + for _, ignoreListedRelease := range releaseIgnoreList { + if release.Name == ignoreListedRelease { + isIgnoredRelease = true + break + } + } + for _, ignoreListedChart := range chartIgnoreList { + // Check for nil to avoid a potential nil pointer exception + if release.Chart != nil { + if release.Chart.Name() == ignoreListedChart { + isIgnoredChart = true + break + } + } + } + if !isIgnoredChart && !isIgnoredRelease { + filteredDeployed = append(filteredDeployed, release) + } + } + + return filteredDeployed +} diff --git a/pkg/helm/cluster_test.go b/pkg/helm/cluster_test.go index 82cd3c9..9ab74ac 100644 --- a/pkg/helm/cluster_test.go +++ b/pkg/helm/cluster_test.go @@ -19,6 +19,8 @@ import ( "github.com/fairwindsops/nova/pkg/output" "github.com/stretchr/testify/assert" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/release" ) func TestHelm_OverrideDesiredVersion(t *testing.T) { @@ -82,3 +84,184 @@ func TestHelm_OverrideDesiredVersion(t *testing.T) { }) } } + +func TestHelm_filterIgnoredReleases(t *testing.T) { + tests := []struct { + name string // Name of test case + releaseIgnoreList []string // List of release names to be ignored + chartIgnoreList []string // List of charts to be ignored + input []*release.Release // Input to filtering function + want []*release.Release // Output from filtering function + }{ + { + name: "EmptyInput", + releaseIgnoreList: []string{}, + chartIgnoreList: []string{}, + input: []*release.Release{}, + want: []*release.Release{}, + }, + { + name: "NoIgnoredReleasesOrCharts", + releaseIgnoreList: []string{}, + chartIgnoreList: []string{}, + input: []*release.Release{ + { + Name: "foo", + }, + }, + want: []*release.Release{ + { + Name: "foo", + }, + }, + }, + { + name: "AllIgnoredReleases", + releaseIgnoreList: []string{ + "foo", + }, + chartIgnoreList: []string{}, + input: []*release.Release{ + { + Name: "foo", + }, + }, + want: []*release.Release{}, + }, + { + name: "SomeIgnoredReleases", + releaseIgnoreList: []string{ + "foo", + }, + chartIgnoreList: []string{}, + input: []*release.Release{ + { + Name: "foo", + }, + { + Name: "bar", + }, + }, + want: []*release.Release{{ + Name: "bar", + }}, + }, + { + name: "AllIgnoredCharts", + releaseIgnoreList: []string{}, + chartIgnoreList: []string{"foo"}, + input: []*release.Release{ + { + Name: "bar", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "foo", + }, + }, + }, + }, + want: []*release.Release{}, + }, + { + name: "SomeIgnoredCharts", + releaseIgnoreList: []string{}, + chartIgnoreList: []string{ + "foo", + }, + input: []*release.Release{ + { + Name: "foo1", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "foo", + }, + }, + }, + { + Name: "foo2", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "bar", + }, + }, + }, + }, + want: []*release.Release{{ + Name: "foo2", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "bar", + }, + }, + }}, + }, + { + name: "SomeIgnoredReleasesAndCharts", + releaseIgnoreList: []string{ + "bar", + }, + chartIgnoreList: []string{ + "foo", + }, + input: []*release.Release{ + { + Name: "foo1", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "foo", + }, + }, + }, + { + Name: "foo2", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "bar", + }, + }, + }, + { + Name: "bar", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "foo", + }, + }, + }, + { + Name: "foo3", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "bar", + }, + }, + }, + }, + want: []*release.Release{{ + Name: "foo2", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "bar", + }, + }, + }, + { + Name: "foo3", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "bar", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + output := filterIgnoredReleases(tt.input, tt.releaseIgnoreList, tt.chartIgnoreList) + if len(output) != len(tt.want) { + t.Fatalf("filtering did not catch all cases, expected %d releases, instead got %d", len(tt.want), len(output)) + } + }) + } +}