From d5ba6260af3417ecfd3af936f11dcc873b7f46f3 Mon Sep 17 00:00:00 2001 From: Gianluca Mardente Date: Wed, 22 Jul 2026 09:17:29 +0200 Subject: [PATCH] Add command to display outdated helm charts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` ./bin/sveltosctl show helm-updates --help Usage: sveltosctl show helm-updates [options] [--namespace=] [--cluster=] [--cluster-type=] [--verbose] --namespace= Show outdated helm charts deployed in clusters in this namespace. If not specified all namespaces are considered. --cluster= Show outdated helm charts deployed in cluster with name. If not specified all cluster names are considered. --cluster-type= Show outdated helm charts deployed in cluster with this type (Capi or Sveltos). If not specified all cluster types are considered. ``` ``` ./bin/sveltosctl show helm-updates --namespace=default --cluster=clusterapi-workload ┌─────────────────────────────┬──────────────────────┬────────────────┬─────────────────┬───────────────┬─────────────────────┐ │ CLUSTER │ NAMESPACE │ RELEASE NAME │ CURRENT VERSION │ NEWER VERSION │ NEWER PATCH VERSION │ ├─────────────────────────────┼──────────────────────┼────────────────┼─────────────────┼───────────────┼─────────────────────┤ │ default/clusterapi-workload │ nats │ nats │ 1.2.9 │ 2.14.2 │ 1.2.11 │ │ default/clusterapi-workload │ prometheus │ prometheus │ 26.0.0 │ 29.18.0 │ 26.0.1 │ │ default/clusterapi-workload │ grafana │ grafana │ 11.3.6 │ 12.8.0 │ 11.3.8 │ │ default/clusterapi-workload │ nginx │ nginx-latest │ 2.5.1 │ 2.6.4 │ 2.5.3 │ │ default/clusterapi-workload │ kyverno │ kyverno-latest │ 3.7.1 │ 3.8.2 │ 3.7.2 │ │ default/clusterapi-workload │ external-dns │ external-dns │ 1.15.0 │ 1.21.1 │ 1.15.2 │ │ default/clusterapi-workload │ traefik │ traefik │ 34.4.1 │ 41.0.2 │ │ │ default/clusterapi-workload │ cert-manager │ cert-manager │ v1.19.4 │ 1.21.0 │ 1.19.6 │ │ default/clusterapi-workload │ envoy-gateway-system │ envoy-gateway │ v1.7.1 │ 1.8.2 │ 1.7.5 │ └─────────────────────────────┴──────────────────────┴────────────────┴─────────────────┴───────────────┴─────────────────────┘ ``` --- .github/workflows/main.yaml | 4 +- Dockerfile | 2 +- go.mod | 40 ++-- go.sum | 76 +++---- hack/tools/go.mod | 2 +- internal/commands/show.go | 3 + internal/commands/show/export_test.go | 11 +- internal/commands/show/helm_updates.go | 226 ++++++++++++++++++++ internal/commands/show/helm_updates_test.go | 209 ++++++++++++++++++ internal/commands/show/utils.go | 8 + internal/utils/clustersummaries.go | 90 ++++++++ 11 files changed, 604 insertions(+), 67 deletions(-) create mode 100644 internal/commands/show/helm_updates.go create mode 100644 internal/commands/show/helm_updates_test.go create mode 100644 internal/utils/clustersummaries.go diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 575d305e..97276a30 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 with: - go-version: 1.26.4 + go-version: 1.26.5 - name: Build run: make build - name: FMT @@ -37,7 +37,7 @@ jobs: - name: Set up Go uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 with: - go-version: 1.26.4 + go-version: 1.26.5 - name: ut run: make test env: diff --git a/Dockerfile b/Dockerfile index a4478527..c280cd9c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM golang:1.26.4 AS builder +FROM golang:1.26.5 AS builder ARG ARCH ARG GIT_VERSION=unknown diff --git a/go.mod b/go.mod index 52900967..658ebaae 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/projectsveltos/sveltosctl -go 1.26.4 +go 1.26.5 require ( github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 @@ -10,16 +10,16 @@ require ( github.com/onsi/ginkgo/v2 v2.32.0 github.com/onsi/gomega v1.42.1 github.com/pkg/errors v0.9.1 - github.com/projectsveltos/addon-controller v1.12.0 - github.com/projectsveltos/event-manager v1.12.0 - github.com/projectsveltos/libsveltos v1.12.0 + github.com/projectsveltos/addon-controller v1.12.1-0.20260720182312-d5ae653d0bf6 + github.com/projectsveltos/event-manager v1.12.1-0.20260718113804-c7366cad218f + github.com/projectsveltos/libsveltos v1.12.1-0.20260718133251-421d1a9c21ff gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.36.2 k8s.io/apiextensions-apiserver v0.36.2 k8s.io/apimachinery v0.36.2 k8s.io/client-go v0.36.2 k8s.io/klog/v2 v2.140.0 - sigs.k8s.io/cluster-api v1.13.3 + sigs.k8s.io/cluster-api v1.13.4 sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/yaml v1.6.0 ) @@ -32,18 +32,18 @@ require ( github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 // indirect github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/aws/aws-sdk-go-v2 v1.42.1 // indirect - github.com/aws/aws-sdk-go-v2/config v1.32.27 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.19.26 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.30 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.29 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.30 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.31 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.2.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.31.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.8 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.43.5 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.4.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.32.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.44.1 // indirect github.com/aws/smithy-go v1.27.3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect @@ -103,16 +103,16 @@ require ( go.opentelemetry.io/otel/trace v1.44.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.53.0 // indirect - golang.org/x/mod v0.37.0 // indirect - golang.org/x/net v0.56.0 // indirect + golang.org/x/crypto v0.54.0 // indirect + golang.org/x/mod v0.38.0 // indirect + golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.21.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/term v0.44.0 // indirect - golang.org/x/text v0.38.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/term v0.45.0 // indirect + golang.org/x/text v0.40.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.46.0 // indirect + golang.org/x/tools v0.48.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect @@ -120,7 +120,7 @@ require ( k8s.io/cluster-bootstrap v0.36.0 // indirect k8s.io/component-base v0.36.2 // indirect k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25 // indirect - k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect + k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect sigs.k8s.io/kustomize/kyaml v0.21.1 // indirect diff --git a/go.sum b/go.sum index ffaaf442..2ed54661 100644 --- a/go.sum +++ b/go.sum @@ -17,10 +17,10 @@ github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAw github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/aws/aws-sdk-go-v2 v1.42.1 h1:9eOTgu1z/dVtYpNZ3/8/XbbaX0x/BqE3HUzAzs6K0ek= github.com/aws/aws-sdk-go-v2 v1.42.1/go.mod h1:5pKeft2eJj+gElQ38Jqg4ibCqh+/AK33/0X3hip7IjM= -github.com/aws/aws-sdk-go-v2/config v1.32.27 h1:SJwJ9Q4kM7v5QVSYYyXj3znRr6lNyZEhSgAXmXXcVbI= -github.com/aws/aws-sdk-go-v2/config v1.32.27/go.mod h1:uBfrzTRedDmB2u+b6+UlaKJy2O6VSH5un2jP24t/KvQ= -github.com/aws/aws-sdk-go-v2/credentials v1.19.26 h1:Si8kk1kyJnuJWCEgiwpBtTdtgSdR7i611596NnC0YIQ= -github.com/aws/aws-sdk-go-v2/credentials v1.19.26/go.mod h1:lBckz+W9SAdNtSDw3pYgQUJDJFcBBWry0GSzw+bK0TY= +github.com/aws/aws-sdk-go-v2/config v1.32.30 h1:XwsEzpTJfQYJbFicz/QMLwAZdyeNVVoOEkbF7R3gPJk= +github.com/aws/aws-sdk-go-v2/config v1.32.30/go.mod h1:Ud32SuMc+/9BGxfpSVld7HrE2o05JwKmXY4M3jOQNZU= +github.com/aws/aws-sdk-go-v2/credentials v1.19.29 h1:WHZGssHH887cO0ox07SIQZsFx3MKD4ps6w0xUEmnKYQ= +github.com/aws/aws-sdk-go-v2/credentials v1.19.29/go.mod h1:Mhl0xR6zjguiuj00XRx2wMx22sAltk7oya39sT7fdg8= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.30 h1:/hi1JADLEW9YYryEz1w4GQu0EtP23pP553Cf9KgsDV4= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.30/go.mod h1:/3AOgy4K17Dm4ucMZVC/MJkzy5kmfKUcINRHZyo0koQ= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 h1:xM/Is9cKMHa8Jj8zkvWhvrFkZsXJV9E+BB4g0HW0duQ= @@ -33,14 +33,14 @@ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13 h1:mbRIur github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13/go.mod h1:ITg9em2KbJx1s0y4aqRX5OYWG6HBZ5TVR//OdpEZ2CQ= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30 h1:/Z5jmNrKsSD7EmDjzAPsm/3L9IuOkzaynklJZ1qX7S4= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30/go.mod h1:lEzEZnOosE7zi8Z6royW1cFJTD9fpab4Ul1SBrllewk= -github.com/aws/aws-sdk-go-v2/service/signin v1.2.2 h1:69JEZSDTQ+UNbTWQJCZMmbpQb5sfc79KUt0O7Pyfjmo= -github.com/aws/aws-sdk-go-v2/service/signin v1.2.2/go.mod h1:mxC0nT/C8wMMS97DemZPzvUZxvIt+2Iq+eS3JdFZGgg= -github.com/aws/aws-sdk-go-v2/service/sso v1.31.5 h1:xlK3Tdc8FO7Tq1k0+hL+otF33glj+dE+qeM5iINiDvU= -github.com/aws/aws-sdk-go-v2/service/sso v1.31.5/go.mod h1:u8af9Nqkmqnr96f7v9nHqzZT9XBwbXEkTiqT4ROuJSE= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.8 h1:yX1IbiBfC7SdEgDwIGnRaZyPPDRbQPDOJxl8102PcGk= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.8/go.mod h1:DMPWJBjYs6+3+f/qhBFEFPPlQ6NlhWjai3dJNvipJ84= -github.com/aws/aws-sdk-go-v2/service/sts v1.43.5 h1:T3ANO8QWDbzQD8f4+UaX+fvJlyGnOFMKLbW+NGBHg04= -github.com/aws/aws-sdk-go-v2/service/sts v1.43.5/go.mod h1:9gdl4RrflIdpDb2TlXshWgR1F9TeCkvqDx77Vpr4Z/Q= +github.com/aws/aws-sdk-go-v2/service/signin v1.4.1 h1:V7ZZ300WPXGjvkyore5DGe0ljVPOxCXie/thWdtSBXE= +github.com/aws/aws-sdk-go-v2/service/signin v1.4.1/go.mod h1:mxC0nT/C8wMMS97DemZPzvUZxvIt+2Iq+eS3JdFZGgg= +github.com/aws/aws-sdk-go-v2/service/sso v1.32.1 h1:gYFYh4iLLcAOJRLNPY2aD2g9DIhKn4eof8UkIrr1rTk= +github.com/aws/aws-sdk-go-v2/service/sso v1.32.1/go.mod h1:u8af9Nqkmqnr96f7v9nHqzZT9XBwbXEkTiqT4ROuJSE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.1 h1:arjT9Cm3/WYbGmD5TUZHk4UQn4Lle1fUNZs5FC6CtF0= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.1/go.mod h1:DMPWJBjYs6+3+f/qhBFEFPPlQ6NlhWjai3dJNvipJ84= +github.com/aws/aws-sdk-go-v2/service/sts v1.44.1 h1:RvfHDg+xvAeZ+5741vUEjpOVtYSIm93W2zhx10Xtydw= +github.com/aws/aws-sdk-go-v2/service/sts v1.44.1/go.mod h1:9gdl4RrflIdpDb2TlXshWgR1F9TeCkvqDx77Vpr4Z/Q= github.com/aws/smithy-go v1.27.3 h1:F3Zb497UhhskkfpJmfkXswyo+t0sh9OTBnIHjogWbVY= github.com/aws/smithy-go v1.27.3/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -191,12 +191,12 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/projectsveltos/addon-controller v1.12.0 h1:CVCLmUrRytucRYNjGAIua68aR1lqd6C/pNSKfzjH8CA= -github.com/projectsveltos/addon-controller v1.12.0/go.mod h1:ummIcdJVlM3n7hAFTNirdtbtQ/Lu19fKxsSfAuvtzHk= -github.com/projectsveltos/event-manager v1.12.0 h1:lam4GaIF1GoxxM1h4Eh7UXxh1V2DWxeLFoDpFj0YSZI= -github.com/projectsveltos/event-manager v1.12.0/go.mod h1:ZSPwwQ97RKaXX9OB4lr8JNv8XwCqxdafLxKZjf+D6Zo= -github.com/projectsveltos/libsveltos v1.12.0 h1:xQfo/AEh3vVRbfWVazpsgBoRgBG5vzm/sJmXp5YrUEg= -github.com/projectsveltos/libsveltos v1.12.0/go.mod h1:4/vcbYFCFE8uEGIHmltriAoHxdB8jgS2zI+9gruOfJ4= +github.com/projectsveltos/addon-controller v1.12.1-0.20260720182312-d5ae653d0bf6 h1:xvBm6OYHwTBVS8SzyiE4C1zjd+r+IZL2p+gyym8RGvU= +github.com/projectsveltos/addon-controller v1.12.1-0.20260720182312-d5ae653d0bf6/go.mod h1:/UyKGLmjTqtnffA8pMTEwuc+lWgH4iQOhrBcHiA1u3s= +github.com/projectsveltos/event-manager v1.12.1-0.20260718113804-c7366cad218f h1:VkqyxjSpapGUQH22Tx+YyoaLeL6j+VoAHsX4vhYTTEo= +github.com/projectsveltos/event-manager v1.12.1-0.20260718113804-c7366cad218f/go.mod h1:yg3wS+wNjsV5be1z7lodAZgEkj9lS3+7aE53O00vblE= +github.com/projectsveltos/libsveltos v1.12.1-0.20260718133251-421d1a9c21ff h1:4++JutHZ6nuxmQEjcUFVh/Q4wlHU5IuINAtNtcBk7Bs= +github.com/projectsveltos/libsveltos v1.12.1-0.20260718133251-421d1a9c21ff/go.mod h1:JyK6LtI4O272UXr5Ye8xpyUQyLWrjoTM5mwmVlV77dM= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= @@ -249,28 +249,28 @@ go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= -golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= -golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk= -golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= @@ -301,10 +301,10 @@ k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25 h1:mPMaPMpBij2V1Wv/fR+HW124vVGXXvOSS9ver/9yjWs= k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY= -k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= -k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -sigs.k8s.io/cluster-api v1.13.3 h1:BlNVnjg644NnlWnxIWHbkltleFLVQwm8FmjWCSB9wGY= -sigs.k8s.io/cluster-api v1.13.3/go.mod h1:7xB2mYn7oOxSlUw7wk6TukNCjR2phn+MI0gRju3TKSk= +k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE= +k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM= +sigs.k8s.io/cluster-api v1.13.4 h1:27PPEKSqESKeUIf475Ga6hSW1+gKxd5V5sCmMDgeBDo= +sigs.k8s.io/cluster-api v1.13.4/go.mod h1:suV3Ixu7yUMD651EtBULa/RtwHRxU30eojF3wifGe8Y= sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= diff --git a/hack/tools/go.mod b/hack/tools/go.mod index 22a82c19..a12f1799 100644 --- a/hack/tools/go.mod +++ b/hack/tools/go.mod @@ -1,6 +1,6 @@ module github.com/projectsveltos/addon-controller/hack/tools -go 1.26.4 +go 1.26.5 require ( github.com/a8m/envsubst v1.4.3 diff --git a/internal/commands/show.go b/internal/commands/show.go index b4ac42d8..f9c538bb 100644 --- a/internal/commands/show.go +++ b/internal/commands/show.go @@ -42,6 +42,7 @@ func Show(ctx context.Context, args []string, logger logr.Logger) error { take effect if a ClusterProfile were to be moved out of DryRun mode. admin-rbac Displays information about RBACs assigned to admins in each managed cluster. classifier-labels Displays labels managed by Classifier and ManagementClusterClassifier instances on each cluster. + helm-updates Displays information on Helm charts deployed in clusters for which a newer version or patch is available. Options: -h --help Show this screen. @@ -85,6 +86,8 @@ See 'sveltosctl show --help' to read about a specific subcommand. err = show.AdminPermissions(ctx, arguments, logger) case "classifier-labels": err = show.ClassifierLabels(ctx, arguments, logger) + case "helm-updates": + err = show.HelmUpdates(ctx, arguments, logger) default: //nolint: forbidigo // print doc fmt.Println(doc) diff --git a/internal/commands/show/export_test.go b/internal/commands/show/export_test.go index 31a92b58..cc14714a 100644 --- a/internal/commands/show/export_test.go +++ b/internal/commands/show/export_test.go @@ -17,9 +17,10 @@ limitations under the License. package show var ( - DisplayAddOns = displayAddOns - DisplayDryRun = displayDryRun - ShowUsage = showUsage - DisplayAdminRbacs = displayAdminRbacs - DisplayResources = displayResources + DisplayAddOns = displayAddOns + DisplayDryRun = displayDryRun + ShowUsage = showUsage + DisplayAdminRbacs = displayAdminRbacs + DisplayResources = displayResources + DisplayHelmUpdates = displayHelmUpdates ) diff --git a/internal/commands/show/helm_updates.go b/internal/commands/show/helm_updates.go new file mode 100644 index 00000000..e0cdcd29 --- /dev/null +++ b/internal/commands/show/helm_updates.go @@ -0,0 +1,226 @@ +/* +Copyright 2025. projectsveltos.io. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package show + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + + "github.com/docopt/docopt-go" + "github.com/go-logr/logr" + "github.com/olekukonko/tablewriter" + + configv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" + logs "github.com/projectsveltos/libsveltos/lib/logsettings" + "github.com/projectsveltos/sveltosctl/internal/utils" +) + +var ( + // cluster represents the cluster => namespace/name + // resourceNamespace and resourceName is the helm release namespace/name + // currentVersion is the helm chart version currently deployed + // newerVersion and newerPatchVersion are the highest version and highest same-minor + // patch version published upstream, if newer than currentVersion + genHelmUpdateRow = func(cluster, resourceNamespace, resourceName, + currentVersion, newerVersion, newerPatchVersion string) []string { + return []string{ + cluster, + resourceNamespace, + resourceName, + currentVersion, + newerVersion, + newerPatchVersion, + } + } +) + +func displayHelmUpdates(ctx context.Context, passedNamespace, passedCluster, passedClusterType string, + logger logr.Logger) error { + + table := tablewriter.NewWriter(os.Stdout) + table.Header("CLUSTER", "NAMESPACE", "RELEASE NAME", "CURRENT VERSION", "NEWER VERSION", "NEWER PATCH VERSION") + + if err := displayHelmUpdatesInNamespaces(ctx, passedNamespace, passedCluster, + passedClusterType, table, logger); err != nil { + return err + } + + return table.Render() +} + +func displayHelmUpdatesInNamespaces(ctx context.Context, passedNamespace, passedCluster, passedClusterType string, + table *tablewriter.Table, logger logr.Logger) error { + + instance := utils.GetAccessInstance() + + namespaces, err := instance.ListNamespaces(ctx, logger) + if err != nil { + return err + } + + for i := range namespaces.Items { + ns := &namespaces.Items[i] + if doConsiderNamespace(ns, passedNamespace) { + logger.V(logs.LogDebug).Info(fmt.Sprintf("Considering namespace: %s", ns.Name)) + err = displayHelmUpdatesInNamespace(ctx, ns.Name, passedCluster, passedClusterType, table, logger) + if err != nil { + return err + } + } + } + + return nil +} + +func displayHelmUpdatesInNamespace(ctx context.Context, namespace, passedCluster, passedClusterType string, + table *tablewriter.Table, logger logr.Logger) error { + + instance := utils.GetAccessInstance() + + logger = logger.WithValues("namespace", namespace) + logger.V(logs.LogDebug).Info("Get all ClusterConfiguration") + clusterConfigurations, err := instance.ListClusterConfigurations(ctx, namespace, logger) + if err != nil { + return err + } + + for i := range clusterConfigurations.Items { + cc := &clusterConfigurations.Items[i] + if doConsiderClusterConfiguration(cc, passedCluster) && + doConsiderClusterType(clusterConfigurationType(cc), passedClusterType) { + + logger.V(logs.LogDebug).Info(fmt.Sprintf("Considering ClusterConfiguration: %s", cc.Name)) + if err := displayHelmUpdatesForCluster(ctx, cc, table, logger); err != nil { + return err + } + } + } + + return nil +} + +// clusterConfigurationType returns the cluster type (Capi/Sveltos) a ClusterConfiguration +// was generated for. +func clusterConfigurationType(clusterConfiguration *configv1beta1.ClusterConfiguration) string { + if clusterConfiguration.Labels == nil { + return "" + } + return clusterConfiguration.Labels[configv1beta1.ClusterTypeLabel] +} + +func displayHelmUpdatesForCluster(ctx context.Context, clusterConfiguration *configv1beta1.ClusterConfiguration, + table *tablewriter.Table, logger logr.Logger) error { + + instance := utils.GetAccessInstance() + + logger = logger.WithValues("clusterConfiguration", clusterConfiguration.Name) + logger.V(logs.LogDebug).Info("Get ClusterConfiguration") + + clusterName := instance.GetClusterNameFromClusterConfiguration(clusterConfiguration) + clusterType := clusterConfigurationType(clusterConfiguration) + clusterInfo := fmt.Sprintf("%s/%s", clusterConfiguration.Namespace, clusterName) + + outdatedInfo, err := instance.GetOutdatedHelmChartInfo(ctx, clusterConfiguration.Namespace, + clusterName, clusterType, logger) + if err != nil { + return err + } + + if len(outdatedInfo) == 0 { + return nil + } + + helmCharts := instance.GetHelmReleases(clusterConfiguration, logger) + for chart := range helmCharts { + info, ok := outdatedInfo[utils.HelmReleaseKey(chart.Namespace, chart.ReleaseName)] + if !ok { + continue + } + + if err := table.Append(genHelmUpdateRow(clusterInfo, chart.Namespace, chart.ReleaseName, + chart.ChartVersion, info.LatestVersion, info.LatestPatchVersion)); err != nil { + return err + } + } + + return nil +} + +// HelmUpdates displays information about Helm charts deployed in clusters for which a +// newer version or same-minor patch is available upstream +func HelmUpdates(ctx context.Context, args []string, logger logr.Logger) error { + doc := `Usage: + sveltosctl show helm-updates [options] [--namespace=] [--cluster=] [--cluster-type=] [--verbose] + + --namespace= Show outdated helm charts deployed in clusters in this namespace. + If not specified all namespaces are considered. + --cluster= Show outdated helm charts deployed in cluster with name. + If not specified all cluster names are considered. + --cluster-type= Show outdated helm charts deployed in cluster with this type + (Capi or Sveltos). If not specified all cluster types are considered. + +Options: + -h --help Show this screen. + --verbose Verbose mode. Print each step. + +Description: + The show helm-updates command shows, for each Helm chart currently deployed in a + cluster, the newer version and/or newer same-minor patch version available upstream. + Charts already on the latest version are omitted. +` + parsedArgs, err := docopt.ParseArgs(doc, nil, "1.0") + if err != nil { + logger.V(logs.LogInfo).Error(err, "failed to parse args") + return fmt.Errorf( + "invalid option: 'sveltosctl %s'. Use flag '--help' to read about a specific subcommand. Error: %w", + strings.Join(args, " "), + err, + ) + } + if len(parsedArgs) == 0 { + return nil + } + + _ = flag.Lookup("v").Value.Set(fmt.Sprint(logs.LogInfo)) + verbose := parsedArgs["--verbose"].(bool) + if verbose { + err = flag.Lookup("v").Value.Set(fmt.Sprint(logs.LogDebug)) + if err != nil { + return err + } + } + + namespace := "" + if passedNamespace := parsedArgs["--namespace"]; passedNamespace != nil { + namespace = passedNamespace.(string) + } + + cluster := "" + if passedCluster := parsedArgs["--cluster"]; passedCluster != nil { + cluster = passedCluster.(string) + } + + clusterType := "" + if passedClusterType := parsedArgs["--cluster-type"]; passedClusterType != nil { + clusterType = passedClusterType.(string) + } + + return displayHelmUpdates(ctx, namespace, cluster, clusterType, logger) +} diff --git a/internal/commands/show/helm_updates_test.go b/internal/commands/show/helm_updates_test.go new file mode 100644 index 00000000..083ba686 --- /dev/null +++ b/internal/commands/show/helm_updates_test.go @@ -0,0 +1,209 @@ +/* +Copyright 2025. projectsveltos.io. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package show_test + +import ( + "bytes" + "context" + "io" + "os" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog/v2/textlogger" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + configv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" + libsveltosv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" + "github.com/projectsveltos/sveltosctl/internal/commands/show" + "github.com/projectsveltos/sveltosctl/internal/utils" +) + +var _ = Describe("HelmUpdates", func() { + var namespace string + var clusterName string + var ns *corev1.Namespace + + BeforeEach(func() { + namespace = namePrefix + randomString() + clusterName = randomString() + + ns = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + }) + + // generateClusterConfiguration returns a ClusterConfiguration labeled so it, and any + // ClusterSummary sharing the same labels/namespace, are considered to belong to the + // same cluster. + generateClusterConfiguration := func(namespace, clusterName string, + clusterType libsveltosv1beta1.ClusterType) *configv1beta1.ClusterConfiguration { + + return &configv1beta1.ClusterConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: randomString(), + Labels: map[string]string{ + configv1beta1.ClusterNameLabel: clusterName, + configv1beta1.ClusterTypeLabel: string(clusterType), + }, + }, + } + } + + // generateClusterSummary returns a ClusterSummary reporting outdated-version info for + // the given chart, labeled to match the cluster identified by namespace/clusterName/clusterType. + generateClusterSummary := func(namespace, clusterName string, clusterType libsveltosv1beta1.ClusterType, + chart *configv1beta1.Chart, latestVersion, latestPatchVersion string) *configv1beta1.ClusterSummary { + + return &configv1beta1.ClusterSummary{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: randomString(), + Labels: map[string]string{ + configv1beta1.ClusterNameLabel: clusterName, + configv1beta1.ClusterTypeLabel: string(clusterType), + }, + }, + Spec: configv1beta1.ClusterSummarySpec{ + ClusterNamespace: namespace, + ClusterName: clusterName, + ClusterType: clusterType, + }, + Status: configv1beta1.ClusterSummaryStatus{ + HelmReleaseSummaries: []configv1beta1.HelmChartSummary{ + { + ReleaseName: chart.ReleaseName, + ReleaseNamespace: chart.Namespace, + Status: configv1beta1.HelmChartStatusManaging, + LatestVersion: strPtr(latestVersion), + LatestPatchVersion: strPtr(latestPatchVersion), + }, + }, + }, + } + } + + It("show helm-updates lists only releases with a newer version or patch available", func() { + clusterConfiguration := generateClusterConfiguration(namespace, clusterName, libsveltosv1beta1.ClusterTypeCapi) + + outdatedChart := generateChart() + clusterConfiguration = addDeployedHelmCharts(clusterConfiguration, randomString(), + []configv1beta1.Chart{*outdatedChart}) + outdatedClusterSummary := generateClusterSummary(namespace, clusterName, libsveltosv1beta1.ClusterTypeCapi, + outdatedChart, "v2.0.0", "") + + upToDateChart := generateChart() + clusterConfiguration = addDeployedHelmCharts(clusterConfiguration, randomString(), + []configv1beta1.Chart{*upToDateChart}) + + initObjects := []client.Object{ns, clusterConfiguration, outdatedClusterSummary} + + scheme, err := utils.GetScheme() + Expect(err).To(BeNil()) + c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjects...).Build() + + utils.InitalizeManagementClusterAcces(scheme, nil, nil, c) + + old := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + err = show.DisplayHelmUpdates(context.TODO(), "", "", "", + textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(1)))) + Expect(err).To(BeNil()) + + w.Close() + var buf bytes.Buffer + _, err = io.Copy(&buf, r) + Expect(err).To(BeNil()) + os.Stdout = old + + lines := strings.Split(buf.String(), "\n") + + found := false + for i := range lines { + if strings.Contains(lines[i], outdatedChart.Namespace) && + strings.Contains(lines[i], outdatedChart.ReleaseName) && + strings.Contains(lines[i], outdatedChart.ChartVersion) && + strings.Contains(lines[i], "v2.0.0") { + + found = true + break + } + } + Expect(found).To(BeTrue()) + + for i := range lines { + Expect(strings.Contains(lines[i], upToDateChart.ReleaseName)).To(BeFalse()) + } + }) + + It("show helm-updates filters by --cluster and --cluster-type", func() { + clusterConfiguration := generateClusterConfiguration(namespace, clusterName, libsveltosv1beta1.ClusterTypeCapi) + chart := generateChart() + clusterConfiguration = addDeployedHelmCharts(clusterConfiguration, randomString(), + []configv1beta1.Chart{*chart}) + clusterSummary := generateClusterSummary(namespace, clusterName, libsveltosv1beta1.ClusterTypeCapi, + chart, "v3.0.0", "") + + initObjects := []client.Object{ns, clusterConfiguration, clusterSummary} + + scheme, err := utils.GetScheme() + Expect(err).To(BeNil()) + c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjects...).Build() + + utils.InitalizeManagementClusterAcces(scheme, nil, nil, c) + + verifyOutputContainsRelease := func(clusterFilter, clusterTypeFilter string, expectFound bool) { + old := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + err = show.DisplayHelmUpdates(context.TODO(), namespace, clusterFilter, clusterTypeFilter, + textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(1)))) + Expect(err).To(BeNil()) + + w.Close() + var buf bytes.Buffer + _, err = io.Copy(&buf, r) + Expect(err).To(BeNil()) + os.Stdout = old + + found := strings.Contains(buf.String(), chart.ReleaseName) + Expect(found).To(Equal(expectFound)) + } + + verifyOutputContainsRelease(clusterName, string(libsveltosv1beta1.ClusterTypeCapi), true) + verifyOutputContainsRelease(randomString(), string(libsveltosv1beta1.ClusterTypeCapi), false) + verifyOutputContainsRelease(clusterName, string(libsveltosv1beta1.ClusterTypeSveltos), false) + }) +}) + +func strPtr(s string) *string { + if s == "" { + return nil + } + return &s +} diff --git a/internal/commands/show/utils.go b/internal/commands/show/utils.go index d639b6ad..720fc541 100644 --- a/internal/commands/show/utils.go +++ b/internal/commands/show/utils.go @@ -55,6 +55,14 @@ func doConsiderClusterReport(clusterReport *configv1beta1.ClusterReport, return clusterReport.Spec.ClusterName == passedCluster } +func doConsiderClusterType(clusterType, passedClusterType string) bool { + if passedClusterType == "" { + return true + } + + return clusterType == passedClusterType +} + func doConsiderProfile(profileNames []string, passedProfile string) bool { if passedProfile == "" { return true diff --git a/internal/utils/clustersummaries.go b/internal/utils/clustersummaries.go new file mode 100644 index 00000000..df01ec6c --- /dev/null +++ b/internal/utils/clustersummaries.go @@ -0,0 +1,90 @@ +/* +Copyright 2025. projectsveltos.io. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + "sigs.k8s.io/controller-runtime/pkg/client" + + configv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" + logs "github.com/projectsveltos/libsveltos/lib/logsettings" +) + +// OutdatedHelmChartInfo mirrors the subset of HelmChartSummary reporting the outcome of +// addon-controller's periodic outdated-Helm-chart check for a single helm release. +type OutdatedHelmChartInfo struct { + LatestVersion string + LatestPatchVersion string +} + +// GetOutdatedHelmChartInfo lists every ClusterSummary for the cluster identified by +// namespace/clusterName/clusterType and returns a map, keyed by "/", +// of outdated-version info from each actively-managed (Status == Managing) HelmChartSummary entry. +func (a *k8sAccess) GetOutdatedHelmChartInfo(ctx context.Context, namespace, clusterName, clusterType string, + logger logr.Logger) (map[string]OutdatedHelmChartInfo, error) { + + logger = logger.WithValues("namespace", namespace, "cluster", clusterName, "clusterType", clusterType) + logger.V(logs.LogDebug).Info("Get ClusterSummaries for outdated helm chart info") + + clusterSummaries := &configv1beta1.ClusterSummaryList{} + listOptions := []client.ListOption{ + client.InNamespace(namespace), + client.MatchingLabels{ + configv1beta1.ClusterNameLabel: clusterName, + configv1beta1.ClusterTypeLabel: clusterType, + }, + } + + if err := a.client.List(ctx, clusterSummaries, listOptions...); err != nil { + return nil, err + } + + result := map[string]OutdatedHelmChartInfo{} + for i := range clusterSummaries.Items { + cs := &clusterSummaries.Items[i] + for j := range cs.Status.HelmReleaseSummaries { + rs := &cs.Status.HelmReleaseSummaries[j] + if rs.Status != configv1beta1.HelmChartStatusManaging { + continue + } + + if rs.LatestVersion == nil && rs.LatestPatchVersion == nil { + continue + } + + info := OutdatedHelmChartInfo{} + if rs.LatestVersion != nil { + info.LatestVersion = *rs.LatestVersion + } + if rs.LatestPatchVersion != nil { + info.LatestPatchVersion = *rs.LatestPatchVersion + } + result[HelmReleaseKey(rs.ReleaseNamespace, rs.ReleaseName)] = info + } + } + + return result, nil +} + +// HelmReleaseKey returns the join key used to match a deployed Chart (from ClusterConfiguration) +// to its OutdatedHelmChartInfo (from ClusterSummary). +func HelmReleaseKey(releaseNamespace, releaseName string) string { + return fmt.Sprintf("%s/%s", releaseNamespace, releaseName) +}