Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
cadc36c
Managed by Terraform
Jul 1, 2024
2745d79
docs: update documentation from template (#322)
sudermanjr Jul 18, 2024
c5a2e2b
INSIGHTS-229 Fix nova vulnerability CVE-2024-41110 (#328)
jdesouza Jul 31, 2024
20409ff
INSIGHTS-247 Fix CVE-2024-41110 vulnerability for nova (#330)
jdesouza Aug 12, 2024
b74db4d
Managed by Terraform
Sep 20, 2024
e7a63b3
Managed by Terraform
Sep 20, 2024
2f3dd21
Managed by Terraform
Sep 20, 2024
06b71f5
Managed by Terraform
Sep 20, 2024
4500288
Managed by Terraform
Sep 20, 2024
2f9f47c
Managed by Terraform
Sep 20, 2024
858180f
Managed by Terraform
Sep 20, 2024
d23ab9e
Managed by Terraform
Sep 20, 2024
002be66
Bump alpine from 3.19 to 3.20 (#313)
dependabot[bot] Sep 20, 2024
ac6e833
Bumped Go to 1.22.7 for fixing vulnerabilities (#335)
jdesouza Sep 25, 2024
f4ac782
feat: kubeconfig path flag (#329)
bbensky Oct 1, 2024
3b702b5
upgrade modules and docker base image (#351)
vitorvezani Dec 20, 2024
0affeff
update preferred repo list (#352)
vitorvezani Dec 20, 2024
da9a61c
feat: add stars field to ArtifactHub package structs and update scori…
vitorvezani Jan 8, 2025
39d87c9
INS-1144: Fix CVE-2025-22874 for nova (#374)
jdesouza Jun 17, 2025
7eff5a3
INS-1202: Fix nova vulnerability (#381)
jdesouza Jul 9, 2025
49362fe
fix: remove Insights notification (#382)
sudermanjr Jul 15, 2025
2d63253
docs: update documentation from template (#387)
sudermanjr Aug 1, 2025
a15d321
INS-1304: fix CVE-2025-47907 (#389)
jdesouza Aug 18, 2025
98beaae
Managed by Terraform
Sep 5, 2025
a24b89a
INS-1561: Fix nova vulnerabilities (#403)
jdesouza Nov 3, 2025
749eccd
INS-1686: nova: Bump go to 1.25.5 for fixing vulnerability (#412)
jdesouza Dec 8, 2025
dbb3323
INS-1830: Bump libs and fix vulnerabilities (#422)
jdesouza Jan 30, 2026
af994fc
INS-1952: Go 1.26 and libs bump for nova (#423)
jdesouza Feb 24, 2026
1390f2f
Bump libs for nova (#429)
jdesouza Feb 24, 2026
0e9d67b
INS-2028: INS-2026: Bump libs and fix vulnerabilities for nova (#436)
jdesouza Mar 9, 2026
149d8cb
Managed by Terraform
Mar 11, 2026
d76b20a
Fix nova release (#437)
jdesouza Mar 11, 2026
97ce75a
INS-2154: nova libs bump (#445)
jdesouza Apr 1, 2026
0b136a9
Managed by Terraform
Apr 15, 2026
7cc8b25
Fix nova vulnerabilities (#451)
jdesouza Apr 21, 2026
86da8f1
Managed by Terraform
Apr 23, 2026
795caf6
Managed by Terraform
Apr 23, 2026
4f32e91
Update CircleCI configuration to skip signing during GoReleaser snaps…
vitorvezani Apr 23, 2026
281b517
add notice to include registry change and immutable images notice on …
vitorvezani Apr 24, 2026
b7c4879
Managed by Terraform
Apr 27, 2026
dc7abe9
fix env injection for nova release (#458)
vitorvezani Apr 27, 2026
0803b08
Update CircleCI configuration to use GCP Docker login instead of Quay…
vitorvezani Apr 27, 2026
2f7c056
Merge remote-tracking branch 'upstream/master' into chore/sync-upstre…
mayankpande-1234 May 12, 2026
5610fd3
address review: use value instead of loop-variable address in star sc…
mayankpande-1234 May 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ node_modules
/dist

/nova.yaml
docs/README.md
28 changes: 18 additions & 10 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func init() {
findCmd,
genConfigCmd,
)

rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "Config file to use. If empty, flags will be used instead")
rootCmd.PersistentFlags().String("output-file", "", "Path on local filesystem to write file output to")
err := viper.BindPFlag("output-file", rootCmd.PersistentFlags().Lookup("output-file"))
Expand Down Expand Up @@ -92,6 +93,12 @@ func init() {
klog.Exitf("Failed to bind context flag: %v", err)
}

rootCmd.PersistentFlags().String("kubeconfig", "", "A path to a kubeconfig file.")
err = viper.BindPFlag("kubeconfig", rootCmd.PersistentFlags().Lookup("kubeconfig"))
if err != nil {
klog.Exitf("Failed to bind kubeconfig flag: %v", err)
}

rootCmd.PersistentFlags().Bool("wide", false, "Output chart name and namespace")
err = viper.BindPFlag("wide", rootCmd.PersistentFlags().Lookup("wide"))
if err != nil {
Expand Down Expand Up @@ -247,14 +254,15 @@ var findCmd = &cobra.Command{
klog.V(5).Infof("All Keys: %v", viper.AllKeys())

kubeContext := viper.GetString("context")
kubeConfigPath := viper.GetString("kubeconfig")

format := viper.GetString("format")
if !(format == output.TableFormat || format == output.JSONFormat) {
klog.Exitf("--format flag value is not valid. Run `nova find --help` to see flag options")
}

if viper.GetBool("helm") && viper.GetBool("containers") {
output, err := handleHelmAndContainers(kubeContext)
output, err := handleHelmAndContainers(kubeContext, kubeConfigPath)
if err != nil {
klog.Exit(err)
}
Expand All @@ -271,15 +279,15 @@ var findCmd = &cobra.Command{
}

if viper.GetBool("containers") {
output, err := handleContainers(kubeContext)
output, err := handleContainers(kubeContext, kubeConfigPath)
if err != nil {
klog.Exit(err)
}
output.Print(format)
return
}

output, err := handleHelm(kubeContext)
output, err := handleHelm(kubeContext, kubeConfigPath)
if err != nil {
klog.Exit(err)
}
Expand Down Expand Up @@ -316,7 +324,7 @@ func Execute(VERSION, COMMIT string) {
}
}

func handleContainers(kubeContext string) (*output.ContainersOutput, error) {
func handleContainers(kubeContext, kubeConfigPath string) (*output.ContainersOutput, error) {
// Set up a context we can use to cancel all operations to external container registries if we need to
timeout := time.Duration(viper.GetUint16("timeout")) * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout)
Expand All @@ -334,7 +342,7 @@ func handleContainers(kubeContext string) (*output.ContainersOutput, error) {
case <-ctx.Done():
}
}()
iClient := containers.NewClient(kubeContext)
iClient := containers.NewClient(kubeContext, kubeConfigPath)
namespace := viper.GetString("namespace")
if viper.IsSet("namespace") {
klog.V(3).Infof("Scanning namespace %v", namespace)
Expand All @@ -351,8 +359,8 @@ func handleContainers(kubeContext string) (*output.ContainersOutput, error) {
return output.NewContainersOutput(containers.Images, containers.ErrImages, showNonSemver, showErrored, includeAll), nil
}

func handleHelm(kubeContext string) (*output.Output, error) {
h := nova_helm.NewHelm(kubeContext)
func handleHelm(kubeContext, kubeConfigPath string) (*output.Output, error) {
h := nova_helm.NewHelm(kubeContext, kubeConfigPath)
if viper.IsSet("desired-versions") {
klog.V(3).Infof("desired-versions is set - attempting to load them")
klog.V(8).Infof("raw desired-versions: %v", viper.Get("desired-versions"))
Expand Down Expand Up @@ -414,12 +422,12 @@ func handleHelm(kubeContext string) (*output.Output, error) {
return &out, nil
}

func handleHelmAndContainers(kubeContext string) (*output.HelmAndContainersOutput, error) {
helmOutput, err := handleHelm(kubeContext)
func handleHelmAndContainers(kubeContext, kubeConfigPath string) (*output.HelmAndContainersOutput, error) {
helmOutput, err := handleHelm(kubeContext, kubeConfigPath)
if err != nil {
return nil, err
}
containersOutput, err := handleContainers(kubeContext)
containersOutput, err := handleContainers(kubeContext, kubeConfigPath)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/containers/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ type Tag struct {
}

// NewClient is a constructor to create a new Client
func NewClient(kubeContext string) *Client {
func NewClient(kubeContext, kubeConfigPath string) *Client {
return &Client{
Kube: kube.GetConfigInstance(kubeContext),
Kube: kube.GetConfigInstance(kubeContext, kubeConfigPath),
}
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/containers/images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,15 @@ func TestGetContainerImages(t *testing.T) {
if err != nil {
t.Error(err)
}
var obj map[string]interface{}
var obj map[string]any
err = json.Unmarshal(b, &obj)
if err != nil {
t.Error(err)
}
fakeTopControllerGetter := func(ns string) ([]controller.Workload, error) {
return []controller.Workload{
{
TopController: unstructured.Unstructured{Object: map[string]interface{}{"kind": "Deployment", "metadata": map[string]interface{}{"name": "name", "namespace": "my-namespace"}}},
TopController: unstructured.Unstructured{Object: map[string]any{"kind": "Deployment", "metadata": map[string]any{"name": "name", "namespace": "my-namespace"}}},
Pods: []unstructured.Unstructured{{Object: obj}},
},
}, nil
Expand Down
1 change: 1 addition & 0 deletions pkg/helm/artifacthub.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ type ArtifactHubHelmPackage struct {
AvailableVersions []AvailableVersion `json:"available_versions"`
Maintainers []Maintainer `json:"maintainers"`
Links []Link `json:"links"`
Stars int `json:"stars"`
}

// AvailableVersion is a sub struct of ArtifactHubHelmPackage and provides a version that is available for a given helm chart.
Expand Down
2 changes: 2 additions & 0 deletions pkg/helm/artifacthub_cached.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type ArtifactHubCachedPackage struct {
Links []Link `json:"links"`
Maintainers []Maintainer `json:"maintainers"`
Deprecated bool `json:"deprecated"`
Stars int `json:"stars"`
}

// ArtifactHubCachedRepository is a sub-struct of the Package struct, and represents the repository containing the package.
Expand Down Expand Up @@ -126,6 +127,7 @@ func (ac *ArtifactHubCachedPackageClient) List() ([]ArtifactHubHelmPackage, erro
HomeURL: cachedPackage.HomeURL,
Links: cachedPackage.Links,
Official: cachedPackage.Official,
Stars: cachedPackage.Stars,
Repository: ArtifactHubRepository{
Name: cachedPackage.Repository.Name,
URL: cachedPackage.Repository.URL,
Expand Down
12 changes: 5 additions & 7 deletions pkg/helm/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package helm

import (
"fmt"
"slices"

"github.com/fairwindsops/nova/pkg/kube"
"github.com/fairwindsops/nova/pkg/output"
Expand All @@ -39,9 +40,9 @@ type DesiredVersion struct {
}

// NewHelm returns a basic helm struct with the version of helm requested
func NewHelm(kubeContext string) *Helm {
func NewHelm(kubeContext, kubeConfigPath string) *Helm {
return &Helm{
Kube: kube.GetConfigInstance(kubeContext),
Kube: kube.GetConfigInstance(kubeContext, kubeConfigPath),
}
}

Expand Down Expand Up @@ -96,11 +97,8 @@ func filterIgnoredReleases(deployed []*release.Release, releaseIgnoreList []stri
for _, release := range deployed {
isIgnoredRelease := false
isIgnoredChart := false
for _, ignoreListedRelease := range releaseIgnoreList {
if release.Name == ignoreListedRelease {
isIgnoredRelease = true
break
}
if slices.Contains(releaseIgnoreList, release.Name) {
isIgnoredRelease = true
}
for _, ignoreListedChart := range chartIgnoreList {
// Check for nil to avoid a potential nil pointer exception
Expand Down
74 changes: 51 additions & 23 deletions pkg/helm/findscore.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package helm

import (
"slices"
"strings"

"github.com/fairwindsops/nova/pkg/output"
Expand All @@ -23,22 +24,56 @@ import (
"k8s.io/klog/v2"
)

const useStarCountThreshold = 10

type packageKey struct {
Name string
Repository string
}

// FindBestArtifactHubMatch takes the helm releases found in the cluster and attempts to match those to a package in artifacthub
func FindBestArtifactHubMatch(clusterRelease *release.Release, ahubPackages []ArtifactHubHelmPackage) *output.ReleaseOutput {
var highScore int
var highScorePackage ArtifactHubHelmPackage
packagesByName := map[packageKey]ArtifactHubHelmPackage{}
packageScores := map[packageKey]float32{}
packageStars := map[packageKey]int{}
var useStars bool
for _, p := range ahubPackages {
score := 0
if p.Name != clusterRelease.Chart.Metadata.Name {
continue
}
score = scoreChartSimilarity(clusterRelease, p)

key := packageKey{Name: p.Name, Repository: p.Repository.Name}
packageScores[key] = scoreChartSimilarity(clusterRelease, p)
packagesByName[key] = p
packageStars[key] = p.Stars

if p.Stars >= useStarCountThreshold {
useStars = true // If any package has more than 10 stars, we add a point to the highest star package
}
}

var highestStarPackage packageKey
var highStars int
for p, stars := range packageStars {
if stars > highStars {
highStars = stars
highestStarPackage = p
}
}

var highScore float32
var highScorePackage ArtifactHubHelmPackage
for k, score := range packageScores {
if useStars && highStars > 0 && k == highestStarPackage {
klog.V(10).Infof("adding a point to the highest star package: %s:%s", k.Repository, k.Name)
score++ // Add a point to the highest star package
}
if score > highScore {
highScore = score
highScorePackage = p
highScorePackage = packagesByName[k]
}
}
klog.V(10).Infof("highScore for '%s': %d, highScorePackage Repo: %s", clusterRelease.Chart.Metadata.Name, highScore, highScorePackage.Repository.Name)
klog.V(10).Infof("highScore for '%s': %f, highScorePackage Repo: %s", clusterRelease.Chart.Metadata.Name, highScore, highScorePackage.Repository.Name)
return prepareOutput(clusterRelease, highScorePackage)
}

Expand Down Expand Up @@ -98,14 +133,12 @@ func prepareOutput(release *release.Release, pkg ArtifactHubHelmPackage) *output
}
}

func scoreChartSimilarity(release *release.Release, pkg ArtifactHubHelmPackage) int {
ret := 0
var preferredRepositories = []string{
"bitnami",
"fairwinds-stable",
"ingress-nginx",
"cert-manager",
}
var preferredRepositories = []string{"bitnami", "fairwinds-stable", "fairwinds-incubator", "ingress-nginx", "cert-manager", "projectcalico",
"grafana", "prometheus-community", "elastic", "hashicorp", "argo", "metrics-server", "gitlab", "jenkins", "harbor", "minio", "cluster-autoscaler",
"aws-ebs-csi-driver", "coredns", "datadog", "deliveryhero", "falcosecurity", "kedacore", "kured", "oauth2-proxy", "rimusz"}

func scoreChartSimilarity(release *release.Release, pkg ArtifactHubHelmPackage) float32 {
var ret float32
if release.Chart.Metadata.Home == pkg.HomeURL {
klog.V(10).Infof("+1 score for %s Home URL (ahub package repo %s)", release.Chart.Metadata.Name, pkg.Repository.Name)
ret++
Expand Down Expand Up @@ -153,10 +186,10 @@ func scoreChartSimilarity(release *release.Release, pkg ArtifactHubHelmPackage)
ret++
}
if containsString(preferredRepositories, pkg.Repository.Name) {
klog.V(10).Infof("+1 score for %s, preffered repo (ahub package repo %s)", release.Chart.Metadata.Name, pkg.Repository.Name)
ret++
klog.V(10).Infof("+1.5 score for %s, preferred repo (ahub package repo %s)", release.Chart.Metadata.Name, pkg.Repository.Name)
ret += 1.5
}
klog.V(10).Infof("calculated score repo: %s, release: %s, score: %d\n\n", pkg.Repository.Name, release.Name, ret)
klog.V(10).Infof("calculated score repo: %s, release: %s, stars: %d, score: %f\n\n", pkg.Repository.Name, release.Name, pkg.Stars, ret)
return ret
}

Expand All @@ -170,12 +203,7 @@ func clusterVersionExistsInPackage(clusterVersion string, pkg ArtifactHubHelmPac
}

func containsString(arr []string, val string) bool {
for _, item := range arr {
if item == val {
return true
}
}
return false
return slices.Contains(arr, val)
}

// IsValidRelease returns a bool indicating whether a version string is valid or not.
Expand Down
4 changes: 2 additions & 2 deletions pkg/helm/findscore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,13 @@ func Test_scoreChartSimilarity(t *testing.T) {
name string
release *release.Release
pkg ArtifactHubHelmPackage
want int
want float32
}{
{
name: "highest score",
release: helmRelease,
pkg: ahubPackage,
want: 7,
want: 7.5,
},
{
name: "empty pkg struct",
Expand Down
Loading