From 9cad1ab490d3ca4dabb56ae5dfa25b1bd9d494b2 Mon Sep 17 00:00:00 2001 From: yxxhero Date: Sun, 25 Jan 2026 19:05:01 +0800 Subject: [PATCH] remove unused file Signed-off-by: yxxhero --- docs/CUSTOM_TRACKING.md | 238 ----------------------- docs/IMPLEMENTATION_SUMMARY.md | 302 ----------------------------- docs/RESOURCE_DETECTION.md | 229 ---------------------- go.mod | 2 +- pkg/cluster/release.go | 275 -------------------------- pkg/cluster/release_test.go | 344 --------------------------------- 6 files changed, 1 insertion(+), 1389 deletions(-) delete mode 100644 docs/CUSTOM_TRACKING.md delete mode 100644 docs/IMPLEMENTATION_SUMMARY.md delete mode 100644 docs/RESOURCE_DETECTION.md delete mode 100644 pkg/cluster/release.go delete mode 100644 pkg/cluster/release_test.go diff --git a/docs/CUSTOM_TRACKING.md b/docs/CUSTOM_TRACKING.md deleted file mode 100644 index 105ca61e..00000000 --- a/docs/CUSTOM_TRACKING.md +++ /dev/null @@ -1,238 +0,0 @@ -# Custom Resource Tracking - -This document describes how to configure custom resource tracking in the kubedog tracker. - -## Overview - -The kubedog tracker now supports flexible configuration for resource tracking through the `TrackOptions` struct. You can: - -- **Track specific kinds**: Only track resources of specified types -- **Skip specific kinds**: Exclude certain resource types from tracking -- **Define custom trackable kinds**: Add new resource types that should be actively tracked -- **Define custom static kinds**: Add new resource types that don't need active tracking - -## Configuration Options - -### TrackKinds - -When set, only resources in this list will be tracked. All other resources are ignored. - -**Example:** -```go -opts := NewTrackOptions().WithTrackKinds([]string{"Deployment", "StatefulSet"}) -``` - -This configuration will: -- Track only `Deployment` and `StatefulSet` resources -- Ignore all other resource types (Service, ConfigMap, etc.) - -### SkipKinds - -Resources in this list will be skipped, even if they would normally be tracked. - -**Example:** -```go -opts := NewTrackOptions().WithSkipKinds([]string{"ConfigMap", "Secret"}) -``` - -This configuration will: -- Track all normally trackable resources (Deployment, StatefulSet, etc.) -- Skip `ConfigMap` and `Secret` resources - -### CustomTrackableKinds - -Define additional resource types that should be actively tracked. When configured, only these custom types and resources in `TrackKinds` (if set) will be considered trackable. - -**Example:** -```go -opts := NewTrackOptions().WithCustomTrackableKinds([]string{"CronJob", "ReplicationController"}) -``` - -This configuration will: -- Treat `CronJob` and `ReplicationController` as trackable resources -- Ignore default trackable kinds (Deployment, StatefulSet, etc.) unless also in `TrackKinds` - -### CustomStaticKinds - -Define additional resource types that are considered static and don't need active tracking. - -**Example:** -```go -opts := NewTrackOptions().WithCustomStaticKinds([]string{"NetworkPolicy", "PodDisruptionBudget"}) -``` - -This configuration will: -- Treat `NetworkPolicy` and `PodDisruptionBudget` as static resources -- Ignore default static kinds unless not in this list - -## Usage Examples - -### Example 1: Track Only Deployments and StatefulSets - -```go -package main - -import ( - "github.com/helmfile/helmfile/pkg/kubedog" - "go.uber.org/zap" -) - -func main() { - // Configure tracker to only track Deployments and StatefulSets - opts := kubedog.NewTrackOptions(). - WithTrackKinds([]string{"Deployment", "StatefulSet"}). - WithTimeout(600) - - config := &kubedog.TrackerConfig{ - Logger: zap.NewExample().Sugar(), - Namespace: "default", - KubeContext: "", - Kubeconfig: "", - TrackOptions: opts, - } - - tracker, err := kubedog.NewTracker(config) - if err != nil { - panic(err) - } - - manifest := []byte(` ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: my-deployment - namespace: default -spec: - replicas: 3 ---- -apiVersion: v1 -kind: Service -metadata: - name: my-service - namespace: default -spec: - selector: - app: myapp - ports: - - port: 80 ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: my-statefulset - namespace: default -spec: - replicas: 1 -`) - - // This will track Deployment and StatefulSet, but skip Service - err = tracker.TrackReleaseWithManifest(nil, "my-release", "default", manifest) - if err != nil { - panic(err) - } - - tracker.Close() -} -``` - -### Example 2: Skip ConfigMaps and Secrets - -```go -opts := kubedog.NewTrackOptions(). - WithSkipKinds([]string{"ConfigMap", "Secret"}) - -// This will track all normally trackable resources (Deployment, StatefulSet, etc.) -// but skip ConfigMap and Secret resources -``` - -### Example 3: Add Custom Trackable Kind (CronJob) - -```go -opts := kubedog.NewTrackOptions(). - WithCustomTrackableKinds([]string{"CronJob"}) - -// This will treat CronJob as a trackable resource and wait for it -// Default trackable kinds (Deployment, StatefulSet) will not be tracked -``` - -### Example 4: Combined Configuration - -```go -opts := kubedog.NewTrackOptions(). - WithTrackKinds([]string{"Deployment", "CronJob"}). - WithSkipKinds([]string{"ConfigMap"}) - -// This configuration: -// 1. Only tracks Deployment and CronJob resources -// 2. Skips ConfigMap even if it appears in the manifest -// 3. Ignores all other resource types -``` - -## Priority and Behavior - -The tracker evaluates configuration in the following order: - -1. **SkipKinds**: If a resource kind is in SkipKinds, it's immediately skipped -2. **TrackKinds**: If TrackKinds is set, only resources in this list are considered -3. **CustomTrackableKinds / CustomStaticKinds**: - - If CustomTrackableKinds is set, only these kinds are considered trackable - - If CustomStaticKinds is set, only these kinds are considered static - - Otherwise, default trackable/static lists are used - -## Resource Classification - -### Default Trackable Kinds - -These resources are actively tracked by default: -- Deployment -- StatefulSet -- DaemonSet -- Job -- Pod -- ReplicaSet - -### Default Static Kinds - -These resources don't need active tracking by default: -- Service -- ConfigMap -- Secret -- PersistentVolume -- PersistentVolumeClaim -- StorageClass -- Namespace -- ResourceQuota -- LimitRange -- PriorityClass -- ServiceAccount -- Role -- RoleBinding -- ClusterRole -- ClusterRoleBinding -- NetworkPolicy -- Ingress -- CustomResourceDefinition - -## Integration with Helmfile - -When using kubedog tracker with Helmfile, you can configure tracking options in your helmfile.yaml: - -```yaml -releases: -- name: my-app - namespace: default - chart: ./charts/my-app - track: - timeout: 600 - trackKinds: - - Deployment - - StatefulSet - skipKinds: - - ConfigMap -``` - -## See Also - -- [Resource Detection Guide](./RESOURCE_DETECTION.md) -- [Helmfile README](../README.md) diff --git a/docs/IMPLEMENTATION_SUMMARY.md b/docs/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 2fb16ac9..00000000 --- a/docs/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,302 +0,0 @@ -# Implementation Summary: Custom Resource Tracking - -## Overview - -Added custom resource tracking configuration to the kubedog tracker, allowing users to flexibly control which resources are tracked and how they are classified. - -## Changes Made - -### 1. TrackOptions Enhancements (`pkg/kubedog/options.go`) - -Added new configuration fields to `TrackOptions` struct: - -```go -type TrackOptions struct { - Timeout int - Logs bool - LogsSince int - Namespace string - KubeContext string - Kubeconfig string - // NEW FIELDS - TrackKinds []string // Only track resources of these kinds - SkipKinds []string // Skip resources of these kinds - CustomTrackableKinds []string // Custom kinds that should be actively tracked - CustomStaticKinds []string // Custom kinds that don't need tracking -} -``` - -Added builder methods: - -```go -func (o *TrackOptions) WithTrackKinds(kinds []string) *TrackOptions -func (o *TrackOptions) WithSkipKinds(kinds []string) *TrackOptions -func (o *TrackOptions) WithCustomTrackableKinds(kinds []string) *TrackOptions -func (o *TrackOptions) WithCustomStaticKinds(kinds []string) *TrackOptions -``` - -### 2. TrackConfig Structure (`pkg/cluster/release.go`) - -Added `TrackConfig` struct to pass tracking configuration: - -```go -type TrackConfig struct { - TrackKinds []string - SkipKinds []string - CustomTrackableKinds []string - CustomStaticKinds []string -} -``` - -### 3. Enhanced Resource Detection (`pkg/cluster/release.go`) - -Added new functions for resource filtering and classification: - -```go -// Get resources with custom tracking configuration -func GetReleaseResourcesFromManifestWithConfig( - manifest []byte, - releaseName, releaseNamespace string, - config *TrackConfig, -) (*ReleaseResources, error) - -// Filter resources based on TrackKinds and SkipKinds -func filterResourcesByConfig( - resources []Resource, - config *TrackConfig, - logger *zap.SugaredLogger, -) []Resource - -// Check if resource is trackable with custom config -func IsTrackableKindWithConfig(kind string, config *TrackConfig) bool - -// Check if resource is static with custom config -func IsStaticKindWithConfig(kind string, config *TrackConfig) bool -``` - -### 4. Enhanced Tracker (`pkg/kubedog/tracker.go`) - -Updated `TrackReleaseWithManifest` to use custom configuration: - -```go -func (t *Tracker) TrackReleaseWithManifest( - ctx interface{}, - releaseName, releaseNamespace string, - manifest []byte, -) error { - // Create TrackConfig from TrackOptions - trackConfig := &cluster.TrackConfig{ - TrackKinds: t.options.TrackKinds, - SkipKinds: t.options.SkipKinds, - CustomTrackableKinds: t.options.CustomTrackableKinds, - CustomStaticKinds: t.options.CustomStaticKinds, - } - - // Use config when getting resources - releaseResources, err := cluster.GetReleaseResourcesFromManifestWithLogger( - t.logger, manifest, releaseName, releaseNamespace, trackConfig, - ) - - // Pass config when tracking resources - return t.trackResources(ctx, releaseResources, trackConfig) -} -``` - -Added support for custom resources: - -```go -func (t *Tracker) trackCustomResource(ctx context.Context, res cluster.Resource) error { - t.logger.Infof("Waiting for custom resource %s/%s to become ready", res.Namespace, res.Name) - return nil -} -``` - -## Configuration Behavior - -### Priority Order - -1. **SkipKinds**: Applied first - if a resource kind is in SkipKinds, it's skipped -2. **TrackKinds**: If set, only resources in this list are considered -3. **CustomTrackableKinds**: If set, only these kinds are considered trackable -4. **CustomStaticKinds**: If set, only these kinds are considered static -5. **Default Lists**: Fall back to default trackable/static kinds if no custom config - -### Example Configurations - -#### Only Track Deployments -```go -opts := NewTrackOptions(). - WithTrackKinds([]string{"Deployment"}) -``` - -#### Skip ConfigMaps -```go -opts := NewTrackOptions(). - WithSkipKinds([]string{"ConfigMap"}) -``` - -#### Add Custom Trackable Kind -```go -opts := NewTrackOptions(). - WithCustomTrackableKinds([]string{"CronJob"}) -``` - -#### Combined Configuration -```go -opts := NewTrackOptions(). - WithTrackKinds([]string{"Deployment", "StatefulSet"}). - WithSkipKinds([]string{"ConfigMap"}) -``` - -## Testing - -### New Test Cases - -#### Cluster Package Tests -- `TestTrackConfig_TrackKinds` - Test filtering by TrackKinds -- `TestTrackConfig_SkipKinds` - Test skipping by SkipKinds -- `TestTrackConfig_CustomTrackableKinds` - Test custom trackable kinds -- `TestTrackConfig_CustomStaticKinds` - Test custom static kinds -- `TestTrackConfig_Combined` - Test combined configuration -- `TestTrackConfig_Nil` - Test behavior with nil config - -#### Kubedog Package Tests -- `TestTrackReleaseWithManifest_TrackKinds` - Test tracker with TrackKinds -- `TestTrackReleaseWithManifest_SkipKinds` - Test tracker with SkipKinds -- `TestTrackReleaseWithManifest_CustomTrackableKinds` - Test tracker with custom kinds - -### Test Results - -```bash -$ go test ./pkg/cluster/... -v -PASS: TestGetReleaseResourcesFromManifest -PASS: TestGetReleaseResourcesFromManifestWithLogger -PASS: TestIsTrackableKind -PASS: TestIsStaticKind -PASS: TestGetHelmReleaseLabels -PASS: TestGetHelmReleaseAnnotations -PASS: TestParseManifest -PASS: TestParseManifest_Empty -PASS: TestParseManifest_Nil -PASS: TestResource_ManifestContent -PASS: TestTrackConfig_TrackKinds -PASS: TestTrackConfig_SkipKinds -PASS: TestTrackConfig_CustomTrackableKinds -PASS: TestTrackConfig_CustomStaticKinds -PASS: TestTrackConfig_Combined -PASS: TestTrackConfig_Nil -PASS: TestDetectServerVersion_Integration -PASS: TestDetectServerVersion_InvalidConfig -ok github.com/helmfile/helmfile/pkg/cluster - -$ go test ./pkg/kubedog/... -v -PASS: TestNewTracker -PASS: TestTracker_Close -PASS: TestTrackRelease_WithNoNamespace -PASS: TestTrackOptions -PASS: TestTrackMode -PASS: TestTrackReleaseWithManifest -PASS: TestTrackReleaseWithManifest_Empty -PASS: TestTrackReleaseWithManifest_InvalidYAML -PASS: TestTrackReleaseWithManifest_TrackKinds -PASS: TestTrackReleaseWithManifest_SkipKinds -PASS: TestTrackReleaseWithManifest_CustomTrackableKinds -ok github.com/helmfile/helmfile/pkg/kubedog - -$ make check -(All checks pass) -``` - -## Documentation - -### New Documentation Files - -1. **docs/CUSTOM_TRACKING.md** - Comprehensive guide on custom tracking configuration - - Overview of all configuration options - - Usage examples for each option - - Priority and behavior explanation - - Default resource classifications - - Integration examples - -2. **examples/custom_tracking/main.go** - Working example program - - Example 1: Default tracking (all resources) - - Example 2: Track only Deployments and StatefulSets - - Example 3: Skip ConfigMaps - - Example 4: Custom trackable kinds (CronJob) - - Example 5: Custom static kinds - -## Usage - -### Basic Usage - -```go -import ( - "github.com/helmfile/helmfile/pkg/kubedog" - "go.uber.org/zap" -) - -func main() { - // Configure tracker to track only Deployments and StatefulSets - opts := kubedog.NewTrackOptions(). - WithTrackKinds([]string{"Deployment", "StatefulSet"}). - WithTimeout(600) - - config := &kubedog.TrackerConfig{ - Logger: zap.NewExample().Sugar(), - Namespace: "default", - TrackOptions: opts, - } - - tracker, err := kubedog.NewTracker(config) - if err != nil { - panic(err) - } - - manifest := []byte(`... helm template output ...`) - - err = tracker.TrackReleaseWithManifest(nil, "my-release", "default", manifest) - if err != nil { - panic(err) - } - - tracker.Close() -} -``` - -### Advanced Configuration - -```go -// Track only specific kinds, skip certain kinds, and add custom trackable kinds -opts := kubedog.NewTrackOptions(). - WithTrackKinds([]string{"Deployment", "StatefulSet", "CronJob"}). - WithSkipKinds([]string{"ConfigMap", "Secret"}). - WithCustomTrackableKinds([]string{"CronJob"}). - WithTimeout(300) -``` - -## Benefits - -1. **Flexibility**: Users can control exactly which resources are tracked -2. **Performance**: Skip tracking unnecessary resources to save time -3. **Customization**: Support for custom resource types (CRDs) -4. **Fine-grained Control**: Combine multiple options for precise control -5. **Backward Compatible**: Default behavior unchanged when no custom config is set - -## Backward Compatibility - -All changes are backward compatible: - -- New fields in `TrackOptions` have default nil values -- When all new fields are nil, behavior is identical to previous version -- Existing functions `IsTrackableKind()` and `IsStaticKind()` still work -- New functions `IsTrackableKindWithConfig()` and `IsStaticKindWithConfig()` accept nil config - -## Future Enhancements - -Potential future improvements: - -1. **Pattern Matching**: Support wildcards and regex in TrackKinds/SkipKinds -2. **Label-based Filtering**: Track resources based on labels/annotations -3. **Resource Limits**: Limit number of resources tracked concurrently -4. **Custom Tracking Logic**: Allow users to provide custom tracking functions -5. **Configuration File**: Support loading tracking config from YAML/JSON files diff --git a/docs/RESOURCE_DETECTION.md b/docs/RESOURCE_DETECTION.md deleted file mode 100644 index 88a3a03f..00000000 --- a/docs/RESOURCE_DETECTION.md +++ /dev/null @@ -1,229 +0,0 @@ -# Resource Detection Based on Helm Template - -This document describes the new resource detection feature based on Helm template manifest. - -## Overview - -The kubedog tracker now supports detecting resources by parsing Helm template output instead of querying the Kubernetes API. This approach has several advantages: - -- No need to connect to a Kubernetes cluster -- Faster execution -- Works in dry-run and template modes -- Simpler and more reliable - -## Usage - -### Track Release from Manifest - -```go -package main - -import ( - "github.com/helmfile/helmfile/pkg/kubedog" - "go.uber.org/zap" -) - -func main() { - logger := zap.NewExample().Sugar() - - config := &kubedog.TrackerConfig{ - Logger: logger, - Namespace: "default", - KubeContext: "", - Kubeconfig: "", - TrackOptions: kubedog.NewTrackOptions(), - } - - tracker, err := kubedog.NewTracker(config) - if err != nil { - panic(err) - } - - manifest := []byte(` ---- -apiVersion: v1 -kind: Service -metadata: - name: my-service - namespace: default -spec: - selector: - app: myapp - ports: - - port: 80 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: my-deployment - namespace: default -spec: - replicas: 3 - selector: - matchLabels: - app: myapp - template: - metadata: - labels: - app: myapp - spec: - containers: - - name: myapp - image: nginx:latest -`) - - err = tracker.TrackReleaseWithManifest(nil, "my-release", "default", manifest) - if err != nil { - logger.Errorf("Failed to track release: %v", err) - } - - tracker.Close() -} -``` - -### Parse Manifest Directly - -```go -import ( - "github.com/helmfile/helmfile/pkg/cluster" -) - -func parseHelmOutput(manifest []byte) { - releaseResources, err := cluster.GetReleaseResourcesFromManifest( - manifest, - "my-release", - "default", - ) - if err != nil { - panic(err) - } - - fmt.Printf("Found %d resources:\n", len(releaseResources.Resources)) - for _, res := range releaseResources.Resources { - fmt.Printf(" - %s/%s in namespace %s\n", res.Kind, res.Name, res.Namespace) - } -} -``` - -## Resource Classification - -### Trackable Resources - -Resources that need active tracking (wait for ready/completed): - -- **Deployment** - Wait for all replicas to be ready -- **StatefulSet** - Wait for all replicas to be ready -- **DaemonSet** - Wait for desired number of scheduled nodes -- **Job** - Wait for job completion -- **Pod** - Wait for pod to be ready -- **ReplicaSet** - Wait for all replicas to be ready - -### Static Resources - -Resources that don't need active tracking (instantaneous creation): - -- Service -- ConfigMap -- Secret -- PersistentVolume -- PersistentVolumeClaim -- StorageClass -- Namespace -- ResourceQuota -- LimitRange -- PriorityClass -- ServiceAccount -- Role -- RoleBinding -- ClusterRole -- ClusterRoleBinding -- NetworkPolicy -- Ingress -- CustomResourceDefinition - -## Helper Functions - -### Check if a resource kind is trackable - -```go -isTrackable := cluster.IsTrackableKind("Deployment") -``` - -### Check if a resource kind is static - -```go -isStatic := cluster.IsStaticKind("ConfigMap") -``` - -### Get Helm release labels - -```go -labels := cluster.GetHelmReleaseLabels("my-release", "default") -// Returns: -// map[string]string{ -// "meta.helm.sh/release-name": "my-release", -// "meta.helm.sh/release-namespace": "default", -// } -``` - -### Get Helm release annotations - -```go -annotations := cluster.GetHelmReleaseAnnotations("my-release") -// Returns: -// map[string]string{ -// "meta.helm.sh/release-name": "my-release", -// } -``` - -## Integration with Helmfile - -The tracker can be integrated with Helmfile to track releases after installation: - -```go -func (st *HelmState) trackRelease(release *ReleaseSpec) error { - if st.Tracker == nil { - return nil - } - - manifest, err := st.getManifest(release) - if err != nil { - return err - } - - return st.Tracker.TrackReleaseWithManifest( - context.Background(), - release.Name, - release.Namespace, - manifest, - ) -} -``` - -## Advantages Over API-Based Detection - -1. **No Cluster Access**: Works even without connecting to the cluster -2. **Faster**: No need to query multiple resource types via API -3. **Deterministic**: Always returns the same resources for the same manifest -4. **Offline Friendly**: Can be used for planning and validation -5. **Simpler**: Less complex error handling and retry logic - -## Testing - -The feature includes comprehensive tests: - -```bash -# Run cluster package tests -go test ./pkg/cluster/... -v - -# Run kubedog package tests -go test ./pkg/kubedog/... -v -``` - -## Implementation Details - -- Uses `k8s.io/apimachinery/pkg/util/yaml` for parsing -- Handles multi-document YAML files (separated by `---`) -- Extracts resource kind, name, namespace, and manifest -- Skips resources without kind or name -- Returns empty resource list for empty manifests diff --git a/go.mod b/go.mod index d1c51f08..cc30bef0 100644 --- a/go.mod +++ b/go.mod @@ -107,7 +107,7 @@ require ( google.golang.org/protobuf v1.36.11 // indirect gopkg.in/ini.v1 v1.67.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect - sigs.k8s.io/yaml v1.6.0 + sigs.k8s.io/yaml v1.6.0 // indirect ) require ( diff --git a/pkg/cluster/release.go b/pkg/cluster/release.go deleted file mode 100644 index 2abafcc9..00000000 --- a/pkg/cluster/release.go +++ /dev/null @@ -1,275 +0,0 @@ -package cluster - -import ( - "bytes" - "fmt" - - "go.uber.org/zap" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - k8syaml "k8s.io/apimachinery/pkg/util/yaml" - "sigs.k8s.io/yaml" -) - -type Resource struct { - Kind string - Name string - Namespace string - Manifest string -} - -type ReleaseResources struct { - ReleaseName string - Namespace string - Resources []Resource -} - -type TrackConfig struct { - TrackKinds []string - SkipKinds []string - CustomTrackableKinds []string - CustomStaticKinds []string -} - -func GetReleaseResourcesFromManifest(manifest []byte, releaseName, releaseNamespace string) (*ReleaseResources, error) { - return GetReleaseResourcesFromManifestWithLogger(nil, manifest, releaseName, releaseNamespace, nil) -} - -func GetReleaseResourcesFromManifestWithConfig(manifest []byte, releaseName, releaseNamespace string, config *TrackConfig) (*ReleaseResources, error) { - return GetReleaseResourcesFromManifestWithLogger(nil, manifest, releaseName, releaseNamespace, config) -} - -func GetReleaseResourcesFromManifestWithLogger(logger *zap.SugaredLogger, manifest []byte, releaseName, releaseNamespace string, config *TrackConfig) (*ReleaseResources, error) { - resources, err := parseManifest(manifest, logger) - if err != nil { - return nil, fmt.Errorf("failed to parse manifest: %w", err) - } - - if len(resources) == 0 { - if logger != nil { - logger.Debugf("No resources found in manifest for release %s", releaseName) - } - return &ReleaseResources{ - ReleaseName: releaseName, - Namespace: releaseNamespace, - Resources: resources, - }, nil - } - - if config != nil { - filteredResources := filterResourcesByConfig(resources, config, logger) - if logger != nil { - logger.Infof("Found %d resources in manifest for release %s (filtered from %d total)", len(filteredResources), releaseName, len(resources)) - for _, res := range filteredResources { - logger.Debugf(" - %s/%s in namespace %s", res.Kind, res.Name, res.Namespace) - } - } - return &ReleaseResources{ - ReleaseName: releaseName, - Namespace: releaseNamespace, - Resources: filteredResources, - }, nil - } - - if logger != nil { - logger.Infof("Found %d resources in manifest for release %s", len(resources), releaseName) - for _, res := range resources { - logger.Debugf(" - %s/%s in namespace %s", res.Kind, res.Name, res.Namespace) - } - } - - return &ReleaseResources{ - ReleaseName: releaseName, - Namespace: releaseNamespace, - Resources: resources, - }, nil -} - -func filterResourcesByConfig(resources []Resource, config *TrackConfig, logger *zap.SugaredLogger) []Resource { - var filtered []Resource - - for _, res := range resources { - if shouldSkipResource(res.Kind, config, logger) { - if logger != nil { - logger.Debugf("Skipping resource %s/%s (kind: %s) based on configuration", res.Kind, res.Name, res.Kind) - } - continue - } - filtered = append(filtered, res) - } - - return filtered -} - -func shouldSkipResource(kind string, config *TrackConfig, logger *zap.SugaredLogger) bool { - if len(config.TrackKinds) > 0 { - shouldTrack := false - for _, trackKind := range config.TrackKinds { - if kind == trackKind { - shouldTrack = true - break - } - } - if !shouldTrack { - if logger != nil { - logger.Debugf("Resource kind %s is not in TrackKinds list, skipping", kind) - } - return true - } - } - - if len(config.SkipKinds) > 0 { - for _, skipKind := range config.SkipKinds { - if kind == skipKind { - if logger != nil { - logger.Debugf("Resource kind %s is in SkipKinds list, skipping", kind) - } - return true - } - } - } - - return false -} - -func parseManifest(manifest []byte, logger *zap.SugaredLogger) ([]Resource, error) { - var resources []Resource - - decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(manifest), 4096) - - for { - var obj unstructured.Unstructured - err := decoder.Decode(&obj) - if err != nil { - if err.Error() == "EOF" { - break - } - return nil, fmt.Errorf("failed to decode manifest: %w", err) - } - - if len(obj.Object) == 0 { - continue - } - - kind := obj.GetKind() - if kind == "" { - if logger != nil { - logger.Debugf("Skipping resource without kind") - } - continue - } - - name := obj.GetName() - if name == "" { - if logger != nil { - logger.Debugf("Skipping %s resource without name", kind) - } - continue - } - - namespace := obj.GetNamespace() - if namespace == "" { - namespace = "default" - } - - manifestBytes, err := yaml.Marshal(obj.Object) - if err != nil { - if logger != nil { - logger.Debugf("Failed to marshal %s/%s: %v", kind, name, err) - } - continue - } - - res := Resource{ - Kind: kind, - Name: name, - Namespace: namespace, - Manifest: string(manifestBytes), - } - resources = append(resources, res) - } - - return resources, nil -} - -func IsTrackableKind(kind string) bool { - trackableKinds := map[string]bool{ - "Deployment": true, - "StatefulSet": true, - "DaemonSet": true, - "Job": true, - "Pod": true, - "ReplicaSet": true, - } - return trackableKinds[kind] -} - -func IsTrackableKindWithConfig(kind string, config *TrackConfig) bool { - if config == nil { - return IsTrackableKind(kind) - } - - if len(config.CustomTrackableKinds) > 0 { - for _, customKind := range config.CustomTrackableKinds { - if kind == customKind { - return true - } - } - return false - } - - return IsTrackableKind(kind) -} - -func IsStaticKind(kind string) bool { - staticKinds := map[string]bool{ - "Service": true, - "ConfigMap": true, - "Secret": true, - "PersistentVolumeClaim": true, - "PersistentVolume": true, - "StorageClass": true, - "Namespace": true, - "ResourceQuota": true, - "LimitRange": true, - "PriorityClass": true, - "ServiceAccount": true, - "Role": true, - "RoleBinding": true, - "ClusterRole": true, - "ClusterRoleBinding": true, - "NetworkPolicy": true, - "Ingress": true, - "CustomResourceDefinition": true, - } - return staticKinds[kind] -} - -func IsStaticKindWithConfig(kind string, config *TrackConfig) bool { - if config == nil { - return IsStaticKind(kind) - } - - if len(config.CustomStaticKinds) > 0 { - for _, customKind := range config.CustomStaticKinds { - if kind == customKind { - return true - } - } - return false - } - - return IsStaticKind(kind) -} - -func GetHelmReleaseLabels(releaseName, releaseNamespace string) map[string]string { - return map[string]string{ - "meta.helm.sh/release-name": releaseName, - "meta.helm.sh/release-namespace": releaseNamespace, - } -} - -func GetHelmReleaseAnnotations(releaseName string) map[string]string { - return map[string]string{ - "meta.helm.sh/release-name": releaseName, - } -} diff --git a/pkg/cluster/release_test.go b/pkg/cluster/release_test.go deleted file mode 100644 index 30744e6f..00000000 --- a/pkg/cluster/release_test.go +++ /dev/null @@ -1,344 +0,0 @@ -package cluster - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -const testManifest = `--- -apiVersion: v1 -kind: Service -metadata: - name: my-service - namespace: default -spec: - selector: - app: myapp - ports: - - port: 80 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: my-deployment - namespace: default -spec: - replicas: 3 - selector: - matchLabels: - app: myapp - template: - metadata: - labels: - app: myapp - spec: - containers: - - name: myapp - image: nginx:latest ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: my-config - namespace: default -data: - key: value -` - -const emptyManifest = `--- -# Empty manifest -` - -const malformedManifest = ` -apiVersion: v1 -kind: Service -metadata: - name: my-service - namespace: default -spec: - invalid: [unclosed -` - -func TestGetReleaseResourcesFromManifest(t *testing.T) { - tests := []struct { - name string - manifest []byte - releaseName string - releaseNamespace string - expectedCount int - expectedKinds []string - wantErr bool - }{ - { - name: "valid manifest", - manifest: []byte(testManifest), - releaseName: "my-release", - releaseNamespace: "default", - expectedCount: 3, - expectedKinds: []string{"Service", "Deployment", "ConfigMap"}, - wantErr: false, - }, - { - name: "empty manifest", - manifest: []byte(emptyManifest), - releaseName: "my-release", - releaseNamespace: "default", - expectedCount: 0, - expectedKinds: []string{}, - wantErr: false, - }, - { - name: "nil manifest", - manifest: nil, - releaseName: "my-release", - releaseNamespace: "default", - expectedCount: 0, - expectedKinds: []string{}, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - resources, err := GetReleaseResourcesFromManifest(tt.manifest, tt.releaseName, tt.releaseNamespace) - - if tt.wantErr { - assert.Error(t, err) - assert.Nil(t, resources) - return - } - - require.NoError(t, err) - require.NotNil(t, resources) - - assert.Equal(t, tt.releaseName, resources.ReleaseName) - assert.Equal(t, tt.releaseNamespace, resources.Namespace) - assert.Len(t, resources.Resources, tt.expectedCount) - - if tt.expectedKinds != nil { - actualKinds := make([]string, len(resources.Resources)) - for i, res := range resources.Resources { - actualKinds[i] = res.Kind - } - assert.ElementsMatch(t, tt.expectedKinds, actualKinds) - } - }) - } -} - -func TestGetReleaseResourcesFromManifestWithLogger(t *testing.T) { - logger := zap.NewNop().Sugar() - resources, err := GetReleaseResourcesFromManifestWithLogger(logger, []byte(testManifest), "my-release", "default", nil) - - require.NoError(t, err) - require.NotNil(t, resources) - - assert.Equal(t, "my-release", resources.ReleaseName) - assert.Equal(t, "default", resources.Namespace) - assert.Len(t, resources.Resources, 3) - - assert.Contains(t, []string{"Service", "Deployment", "ConfigMap"}, resources.Resources[0].Kind) -} - -func TestIsTrackableKind(t *testing.T) { - tests := []struct { - kind string - expected bool - }{ - {"Deployment", true}, - {"StatefulSet", true}, - {"DaemonSet", true}, - {"Job", true}, - {"Pod", true}, - {"ReplicaSet", true}, - {"Service", false}, - {"ConfigMap", false}, - {"Secret", false}, - {"Ingress", false}, - } - - for _, tt := range tests { - t.Run(tt.kind, func(t *testing.T) { - result := IsTrackableKind(tt.kind) - assert.Equal(t, tt.expected, result) - }) - } -} - -func TestIsStaticKind(t *testing.T) { - tests := []struct { - kind string - expected bool - }{ - {"Service", true}, - {"ConfigMap", true}, - {"Secret", true}, - {"PersistentVolumeClaim", true}, - {"Ingress", true}, - {"Deployment", false}, - {"StatefulSet", false}, - } - - for _, tt := range tests { - t.Run(tt.kind, func(t *testing.T) { - result := IsStaticKind(tt.kind) - assert.Equal(t, tt.expected, result) - }) - } -} - -func TestGetHelmReleaseLabels(t *testing.T) { - labels := GetHelmReleaseLabels("my-release", "my-namespace") - - expectedLabels := map[string]string{ - "meta.helm.sh/release-name": "my-release", - "meta.helm.sh/release-namespace": "my-namespace", - } - - assert.Equal(t, expectedLabels, labels) -} - -func TestGetHelmReleaseAnnotations(t *testing.T) { - annotations := GetHelmReleaseAnnotations("my-release") - - expectedAnnotations := map[string]string{ - "meta.helm.sh/release-name": "my-release", - } - - assert.Equal(t, expectedAnnotations, annotations) -} - -func TestParseManifest(t *testing.T) { - resources, err := parseManifest([]byte(testManifest), nil) - - require.NoError(t, err) - assert.Len(t, resources, 3) - - assert.Equal(t, "Service", resources[0].Kind) - assert.Equal(t, "my-service", resources[0].Name) - assert.Equal(t, "default", resources[0].Namespace) - - assert.Equal(t, "Deployment", resources[1].Kind) - assert.Equal(t, "my-deployment", resources[1].Name) - - assert.Equal(t, "ConfigMap", resources[2].Kind) - assert.Equal(t, "my-config", resources[2].Name) -} - -func TestParseManifest_Empty(t *testing.T) { - resources, err := parseManifest([]byte(emptyManifest), nil) - - require.NoError(t, err) - assert.Empty(t, resources) -} - -func TestParseManifest_Nil(t *testing.T) { - resources, err := parseManifest(nil, nil) - - require.NoError(t, err) - assert.Empty(t, resources) -} - -func TestResource_ManifestContent(t *testing.T) { - resources, err := parseManifest([]byte(testManifest), nil) - - require.NoError(t, err) - require.Len(t, resources, 3) - - for _, res := range resources { - assert.NotEmpty(t, res.Manifest) - assert.Contains(t, res.Manifest, "apiVersion") - assert.Contains(t, res.Manifest, "kind") - } -} - -func TestTrackConfig_TrackKinds(t *testing.T) { - config := &TrackConfig{ - TrackKinds: []string{"Deployment"}, - } - - resources, err := GetReleaseResourcesFromManifestWithConfig( - []byte(testManifest), - "test-release", - "default", - config, - ) - - require.NoError(t, err) - require.NotNil(t, resources) - - assert.Len(t, resources.Resources, 1) - assert.Equal(t, "Deployment", resources.Resources[0].Kind) - assert.Equal(t, "my-deployment", resources.Resources[0].Name) -} - -func TestTrackConfig_SkipKinds(t *testing.T) { - config := &TrackConfig{ - SkipKinds: []string{"ConfigMap"}, - } - - resources, err := GetReleaseResourcesFromManifestWithConfig( - []byte(testManifest), - "test-release", - "default", - config, - ) - - require.NoError(t, err) - require.NotNil(t, resources) - - assert.Len(t, resources.Resources, 2) - - kinds := make([]string, len(resources.Resources)) - for i, res := range resources.Resources { - kinds[i] = res.Kind - } - assert.NotContains(t, kinds, "ConfigMap") -} - -func TestTrackConfig_CustomTrackableKinds(t *testing.T) { - config := &TrackConfig{ - CustomTrackableKinds: []string{"CronJob"}, - } - - isTrackable := IsTrackableKindWithConfig("CronJob", config) - assert.True(t, isTrackable) - - isNotTrackable := IsTrackableKindWithConfig("Deployment", config) - assert.False(t, isNotTrackable) -} - -func TestTrackConfig_CustomStaticKinds(t *testing.T) { - config := &TrackConfig{ - CustomStaticKinds: []string{"CustomResource"}, - } - - isStatic := IsStaticKindWithConfig("CustomResource", config) - assert.True(t, isStatic) - - isNotStatic := IsStaticKindWithConfig("ConfigMap", config) - assert.False(t, isNotStatic) -} - -func TestTrackConfig_Combined(t *testing.T) { - config := &TrackConfig{ - SkipKinds: []string{"ConfigMap"}, - CustomTrackableKinds: []string{"CronJob"}, - CustomStaticKinds: []string{"CustomResource"}, - } - - trackable1 := IsTrackableKindWithConfig("CronJob", config) - assert.True(t, trackable1) - - defaultTrackable := IsTrackableKindWithConfig("Service", config) - assert.False(t, defaultTrackable, "Service is default trackable, but CustomTrackableKinds is configured, so it should not be trackable") - - static1 := IsStaticKindWithConfig("CustomResource", config) - assert.True(t, static1) - - defaultStatic := IsStaticKindWithConfig("ConfigMap", config) - assert.False(t, defaultStatic, "ConfigMap is default static, but CustomStaticKinds is configured, so it should not be static") -}