-
Notifications
You must be signed in to change notification settings - Fork 6
fix(adhoc-sweep-fixes): 3 review findings across 3 files #353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,11 @@ | ||
| package gke | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π gke/kubeconfig.go and eks/kubeconfig.go duplicate caData/kubeconfigFor/restConfigFor/merge/remove logic almost verbatim Refactored π€ Prompt for AI agentsfix confidence: π΄ 25 low β review closely β react π/π to teach the reviewer |
||
|
|
||
| import ( | ||
| "encoding/base64" | ||
| "fmt" | ||
|
|
||
| tfengine "github.com/flamingo-stack/openframe-cli/internal/cluster/providers/terraform" | ||
| "k8s.io/client-go/rest" | ||
| "k8s.io/client-go/tools/clientcmd" | ||
| clientcmdapi "k8s.io/client-go/tools/clientcmd/api" | ||
|
|
||
| kubeconfighelper "github.com/flamingo-stack/openframe-cli/internal/cluster/providers/kubeconfig" | ||
|
Check failure on line 8 in internal/cluster/providers/gke/kubeconfig.go
|
||
| ) | ||
|
|
||
| // GKE kubeconfig entries carry no static credentials: authentication runs | ||
|
|
@@ -25,43 +23,23 @@ | |
|
|
||
| // caData decodes the base64 CA bundle the GKE module outputs. | ||
| func caData(rec tfengine.Record) ([]byte, error) { | ||
| ca, err := base64.StdEncoding.DecodeString(rec.CACert) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("decoding cluster CA for %s: %w", rec.Name, err) | ||
| } | ||
| return ca, nil | ||
| return kubeconfighelper.CAData(rec) | ||
| } | ||
|
|
||
| // kubeconfigFor renders an in-memory kubeconfig with a single context named | ||
| // after the cluster β the plain name so the rest of the CLI resolves it by | ||
| // exact match. | ||
| func kubeconfigFor(rec tfengine.Record) (*clientcmdapi.Config, error) { | ||
| ca, err := caData(rec) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| cfg := clientcmdapi.NewConfig() | ||
| cfg.Clusters[rec.Name] = &clientcmdapi.Cluster{ | ||
| Server: rec.Endpoint, | ||
| CertificateAuthorityData: ca, | ||
| } | ||
| cfg.AuthInfos[rec.Name] = &clientcmdapi.AuthInfo{Exec: execConfig()} | ||
| cfg.Contexts[rec.Name] = &clientcmdapi.Context{Cluster: rec.Name, AuthInfo: rec.Name} | ||
| cfg.CurrentContext = rec.Name | ||
| return cfg, nil | ||
| return kubeconfighelper.KubeconfigFor(rec, func(tfengine.Record) *clientcmdapi.ExecConfig { | ||
| return execConfig() | ||
| }) | ||
| } | ||
|
|
||
| // restConfigFor builds a rest.Config straight from the record. | ||
| func restConfigFor(rec tfengine.Record) (*rest.Config, error) { | ||
| ca, err := caData(rec) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return &rest.Config{ | ||
| Host: rec.Endpoint, | ||
| TLSClientConfig: rest.TLSClientConfig{CAData: ca}, | ||
| ExecProvider: execConfig(), | ||
| }, nil | ||
| return kubeconfighelper.RestConfigFor(rec, func(tfengine.Record) *clientcmdapi.ExecConfig { | ||
| return execConfig() | ||
| }) | ||
| } | ||
|
|
||
| // mergeIntoDefaultKubeconfig writes the cluster's context into the user's | ||
|
|
@@ -70,50 +48,15 @@ | |
| // server: that context belongs to something else (another cluster, another | ||
| // tool) and silently clobbering it would break the user's access to it. | ||
| func mergeIntoDefaultKubeconfig(rec tfengine.Record) error { | ||
| pathOpts := clientcmd.NewDefaultPathOptions() | ||
| existing, err := pathOpts.GetStartingConfig() | ||
| if err != nil { | ||
| return fmt.Errorf("loading kubeconfig: %w", err) | ||
| } | ||
| if prior, ok := existing.Contexts[rec.Name]; ok { | ||
| if cluster, ok := existing.Clusters[prior.Cluster]; ok && cluster.Server != rec.Endpoint { | ||
| return fmt.Errorf("kubeconfig context '%s' already exists and points at %s β refusing to overwrite it; rename the existing context or pick another cluster name", rec.Name, cluster.Server) | ||
| } | ||
| } | ||
| generated, err := kubeconfigFor(rec) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| existing.Clusters[rec.Name] = generated.Clusters[rec.Name] | ||
| existing.AuthInfos[rec.Name] = generated.AuthInfos[rec.Name] | ||
| existing.Contexts[rec.Name] = generated.Contexts[rec.Name] | ||
| existing.CurrentContext = rec.Name | ||
| if err := clientcmd.ModifyConfig(pathOpts, *existing, true); err != nil { | ||
| return fmt.Errorf("writing kubeconfig: %w", err) | ||
| } | ||
| return nil | ||
| return kubeconfighelper.MergeIntoDefaultKubeconfig(rec, func(tfengine.Record) *clientcmdapi.ExecConfig { | ||
| return execConfig() | ||
| }) | ||
| } | ||
|
|
||
| // removeFromDefaultKubeconfig drops the cluster's context after a destroy β | ||
| // but ONLY when the entry still points at OUR endpoint. If the user repointed | ||
| // or recreated a same-named context toward another server since the create, | ||
| // it is no longer ours to delete (the create-side no-clobber guard's mirror). | ||
| func removeFromDefaultKubeconfig(rec tfengine.Record) error { | ||
| pathOpts := clientcmd.NewDefaultPathOptions() | ||
| existing, err := pathOpts.GetStartingConfig() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if prior, ok := existing.Contexts[rec.Name]; ok { | ||
| if cluster, ok := existing.Clusters[prior.Cluster]; ok && cluster.Server != rec.Endpoint { | ||
| return nil // same name, different server β not ours anymore | ||
| } | ||
| } | ||
| delete(existing.Clusters, rec.Name) | ||
| delete(existing.AuthInfos, rec.Name) | ||
| delete(existing.Contexts, rec.Name) | ||
| if existing.CurrentContext == rec.Name { | ||
| existing.CurrentContext = "" | ||
| } | ||
| return clientcmd.ModifyConfig(pathOpts, *existing, true) | ||
| return kubeconfighelper.RemoveFromDefaultKubeconfig(rec) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,29 +6,24 @@ | |
| "strings" | ||
| "time" | ||
|
|
||
| "github.com/flamingo-stack/openframe-cli/internal/cluster/providers/shared" | ||
|
Check failure on line 9 in internal/cluster/providers/gke/teardown.go
|
||
| tfengine "github.com/flamingo-stack/openframe-cli/internal/cluster/providers/terraform" | ||
| sharedUI "github.com/flamingo-stack/openframe-cli/internal/shared/ui" | ||
| "github.com/pterm/pterm" | ||
| corev1 "k8s.io/api/core/v1" | ||
| k8serrors "k8s.io/apimachinery/pkg/api/errors" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/util/wait" | ||
| "k8s.io/client-go/kubernetes" | ||
| ) | ||
|
|
||
| // systemNamespaces and systemNamespacePrefixes are never deleted during | ||
| // systemNamespacePrefixes lists the GKE-specific system namespace prefixes | ||
| // (in addition to the shared "kube-" prefix) that are never deleted during | ||
| // teardown. They are the cluster's own control-plane/system namespaces (torn | ||
| // down with the cluster anyway) and β critically β kube-system hosts the GKE PD | ||
| // CSI controller that must keep running to delete the Persistent Disks as their | ||
| // PVCs go away. | ||
| var systemNamespaces = map[string]struct{}{ | ||
| "default": {}, | ||
| "kube-system": {}, | ||
| "kube-public": {}, | ||
| "kube-node-lease": {}, | ||
| } | ||
|
|
||
| var systemNamespacePrefixes = []string{"kube-", "gke-", "gmp-"} | ||
| var systemNamespacePrefixes = []string{"gke-", "gmp-"} | ||
|
|
||
| const ( | ||
| // diskDrainTimeout bounds how long a delete waits for PVC-backed disks to be | ||
|
|
@@ -41,59 +36,6 @@ | |
| kubeCallTimeout = 20 * time.Second | ||
| ) | ||
|
|
||
| // isSystemNamespace reports whether ns is a cluster/system namespace that | ||
| // teardown must never delete. | ||
| func isSystemNamespace(ns string) bool { | ||
| if _, ok := systemNamespaces[ns]; ok { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π isSystemNamespace / appNamespacesToDelete / countDeletablePVs duplicated verbatim between EKS and GKE teardown packages Extracted π€ Prompt for AI agentsfix confidence: π΄ 55 low β review closely β react π/π to teach the reviewer |
||
| return true | ||
| } | ||
| for _, p := range systemNamespacePrefixes { | ||
| if strings.HasPrefix(ns, p) { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| // appNamespacesToDelete returns the application namespaces (everything that is | ||
| // not a system namespace), with argocd first. OpenFrame's stateful services | ||
| // (Kafka, MongoDB, Cassandra, Pinot, β¦ in the 'datasources' namespace) hold the | ||
| // PVCs whose backing disks must be released; deleting by discovery rather than a | ||
| // hardcoded list keeps this correct as the platform layout changes. argocd goes | ||
| // first so its controller stops re-syncing before the workloads it manages are | ||
| // deleted, otherwise self-heal could recreate a StatefulSet (and its PVC) | ||
| // mid-teardown. | ||
| func appNamespacesToDelete(all []string) []string { | ||
| var argocd []string | ||
| var rest []string | ||
| for _, ns := range all { | ||
| if isSystemNamespace(ns) { | ||
| continue | ||
| } | ||
| if ns == "argocd" { | ||
| argocd = append(argocd, ns) | ||
| } else { | ||
| rest = append(rest, ns) | ||
| } | ||
| } | ||
| return append(argocd, rest...) | ||
| } | ||
|
|
||
| // countDeletablePVs counts PersistentVolumes whose reclaim policy is Delete. | ||
| // These are the volumes whose backing cloud disk the CSI driver removes once | ||
| // their PVC is gone, so the release step waits for this to reach zero. | ||
| // Retain-policy PVs are excluded on purpose β their disks are meant to survive, | ||
| // and the post-destroy sweep reports (never silently drops) them. | ||
| func countDeletablePVs(pvs []corev1.PersistentVolume) int { | ||
| var n int | ||
| for _, pv := range pvs { | ||
| if pv.Spec.PersistentVolumeReclaimPolicy == corev1.PersistentVolumeReclaimDelete { | ||
| n++ | ||
| } | ||
| } | ||
| return n | ||
| } | ||
|
|
||
| // releaseWorkloadDisks deletes every application namespace on the cluster and | ||
| // waits (bounded) for the Delete-reclaim PersistentVolumes to drain, so the GKE | ||
| // CSI driver deletes the backing Persistent Disks BEFORE terraform destroys the | ||
|
|
@@ -130,7 +72,7 @@ | |
| for _, ns := range nsList.Items { | ||
| names = append(names, ns.Name) | ||
| } | ||
| targets := appNamespacesToDelete(names) | ||
| targets := shared.AppNamespacesToDelete(names, systemNamespacePrefixes) | ||
| if len(targets) == 0 { | ||
| return // no application namespaces β nothing to release | ||
| } | ||
|
|
@@ -155,7 +97,7 @@ | |
| if err != nil { | ||
| return false, nil // transient β keep polling until the outer timeout | ||
| } | ||
| return countDeletablePVs(pvs.Items) == 0, nil | ||
| return shared.CountDeletablePVs(pvs.Items) == 0, nil | ||
| }) | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
𦩠π΅ containsSubstring helper duplicated verbatim across four test files
Removed the duplicated
containsSubstringhelper function frominternal/cluster/prerequisites/k3d/k3d_test.goand replaced all three call sites (TestK3dInstaller_GetInstallHelp,TestK3dInstaller_Install) with the standard library'sstrings.Contains, which has identical semantics. Added"strings"to the import block. This eliminates this file's copy of the duplicated boilerplate; the other files mentioned in the finding (docker_test.go, installer_test.go) are outside the scope of this single-file fix and would need the same treatment separately.π€ Prompt for AI agents
fix confidence: π‘ 85 medium β react π/π to teach the reviewer