From ed216314208b2b17a638d8f73ddb727e31fc10bf Mon Sep 17 00:00:00 2001 From: Graziano Casto Date: Sun, 17 Aug 2025 19:22:47 +0200 Subject: [PATCH 1/3] *added context management --- cmd/context.go | 325 +++++++++++++++++++++++++++++++ cmd/export.go | 90 +++++++-- cmd/root.go | 7 +- cmd/utils.go | 18 ++ docs/docs/commands/context.md | 355 ++++++++++++++++++++++++++++++++++ docs/docs/commands/index.md | 108 +++++++++++ docs/index.md | 14 +- examples/README.md | 116 +++++++++++ examples/context-example.sh | 154 +++++++++++++++ pkg/context/context.go | 238 +++++++++++++++++++++++ 10 files changed, 1404 insertions(+), 21 deletions(-) create mode 100644 cmd/context.go create mode 100644 cmd/utils.go create mode 100644 docs/docs/commands/context.md create mode 100644 docs/docs/commands/index.md create mode 100644 examples/README.md create mode 100755 examples/context-example.sh create mode 100644 pkg/context/context.go diff --git a/cmd/context.go b/cmd/context.go new file mode 100644 index 0000000..8eb8e65 --- /dev/null +++ b/cmd/context.go @@ -0,0 +1,325 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + "kalco/pkg/context" +) + +var ( + contextCmd = &cobra.Command{ + Use: "context", + Short: "Manage Kalco contexts", + Long: `Manage Kalco contexts for different Kubernetes clusters and configurations. + +A context defines: +- Kubeconfig file for cluster access +- Output directory for exports +- Dynamic labels for context identification +- Description for context purpose + +Examples: + kalco context set production --kubeconfig ~/.kube/prod-config --output ./prod-exports --labels env=prod,team=platform + kalco context list + kalco context use production`, + } + + contextSetCmd = &cobra.Command{ + Use: "set [name]", + Short: "Create or update a context", + Long: `Create or update a Kalco context with the specified configuration. + +The context will be saved and can be used for future operations.`, + Args: cobra.ExactArgs(1), + RunE: runContextSet, + } + + contextListCmd = &cobra.Command{ + Use: "list", + Short: "List all contexts", + Long: `Display all available contexts with their configuration details.`, + RunE: runContextList, + } + + contextUseCmd = &cobra.Command{ + Use: "use [name]", + Short: "Switch to a context", + Long: `Switch to the specified context. This context will be used for future operations.`, + Args: cobra.ExactArgs(1), + RunE: runContextUse, + } + + contextDeleteCmd = &cobra.Command{ + Use: "delete [name]", + Short: "Delete a context", + Long: `Delete the specified context. Cannot delete the currently active context.`, + Args: cobra.ExactArgs(1), + RunE: runContextDelete, + } + + contextShowCmd = &cobra.Command{ + Use: "show [name]", + Short: "Show context details", + Long: `Display detailed information about a specific context.`, + Args: cobra.ExactArgs(1), + RunE: runContextShow, + } + + contextCurrentCmd = &cobra.Command{ + Use: "current", + Short: "Show current context", + Long: `Display information about the currently active context.`, + RunE: runContextCurrent, + } + + // Flags for context set + contextKubeConfig string + contextOutputDir string + contextDescription string + contextLabels []string +) + +func init() { + rootCmd.AddCommand(contextCmd) + + // Add subcommands + contextCmd.AddCommand(contextSetCmd) + contextCmd.AddCommand(contextListCmd) + contextCmd.AddCommand(contextUseCmd) + contextCmd.AddCommand(contextDeleteCmd) + contextCmd.AddCommand(contextShowCmd) + contextCmd.AddCommand(contextCurrentCmd) + + // Add flags for context set + contextSetCmd.Flags().StringVar(&contextKubeConfig, "kubeconfig", "", "Path to kubeconfig file") + contextSetCmd.Flags().StringVar(&contextOutputDir, "output", "", "Output directory for exports") + contextSetCmd.Flags().StringVar(&contextDescription, "description", "", "Description of the context") + contextSetCmd.Flags().StringArrayVar(&contextLabels, "labels", []string{}, "Labels in format key=value (can be specified multiple times)") +} + +func runContextSet(cmd *cobra.Command, args []string) error { + name := args[0] + + // Get config directory + configDir, err := getConfigDir() + if err != nil { + return fmt.Errorf("failed to get config directory: %w", err) + } + + // Create context manager + cm, err := context.NewContextManager(configDir) + if err != nil { + return fmt.Errorf("failed to create context manager: %w", err) + } + + // Parse labels + labels := make(map[string]string) + for _, label := range contextLabels { + parts := strings.SplitN(label, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("invalid label format: %s (expected key=value)", label) + } + labels[parts[0]] = parts[1] + } + + // Set context + if err := cm.SetContext(name, contextKubeConfig, contextOutputDir, contextDescription, labels); err != nil { + return fmt.Errorf("failed to set context: %w", err) + } + + fmt.Printf("โœ… Context '%s' set successfully\n", name) + return nil +} + +func runContextList(cmd *cobra.Command, args []string) error { + // Get config directory + configDir, err := getConfigDir() + if err != nil { + return fmt.Errorf("failed to get config directory: %w", err) + } + + // Create context manager + cm, err := context.NewContextManager(configDir) + if err != nil { + return fmt.Errorf("failed to create context manager: %w", err) + } + + contexts := cm.ListContexts() + if len(contexts) == 0 { + fmt.Println("No contexts found. Use 'kalco context set' to create your first context.") + return nil + } + + // Get current context + current, err := cm.GetCurrentContext() + currentName := "" + if err == nil { + currentName = current.Name + } + + fmt.Println("Available contexts:") + fmt.Println() + + for name, ctx := range contexts { + // Mark current context + marker := " " + if name == currentName { + marker = "*" + } + + fmt.Printf("%s %s\n", marker, name) + if ctx.Description != "" { + fmt.Printf(" Description: %s\n", ctx.Description) + } + if ctx.KubeConfig != "" { + fmt.Printf(" Kubeconfig: %s\n", ctx.KubeConfig) + } + if ctx.OutputDir != "" { + fmt.Printf(" Output Dir: %s\n", ctx.OutputDir) + } + if len(ctx.Labels) > 0 { + labelStrs := make([]string, 0, len(ctx.Labels)) + for k, v := range ctx.Labels { + labelStrs = append(labelStrs, fmt.Sprintf("%s=%s", k, v)) + } + fmt.Printf(" Labels: %s\n", strings.Join(labelStrs, ", ")) + } + fmt.Printf(" Created: %s\n", ctx.CreatedAt.Format("2006-01-02 15:04:05")) + fmt.Printf(" Updated: %s\n", ctx.UpdatedAt.Format("2006-01-02 15:04:05")) + fmt.Println() + } + + if currentName != "" { + fmt.Printf("* = current context\n") + } + + return nil +} + +func runContextUse(cmd *cobra.Command, args []string) error { + name := args[0] + + // Get config directory + configDir, err := getConfigDir() + if err != nil { + return fmt.Errorf("failed to get config directory: %w", err) + } + + // Create context manager + cm, err := context.NewContextManager(configDir) + if err != nil { + return fmt.Errorf("failed to create context manager: %w", err) + } + + // Use context + if err := cm.UseContext(name); err != nil { + return fmt.Errorf("failed to use context: %w", err) + } + + fmt.Printf("โœ… Switched to context '%s'\n", name) + return nil +} + +func runContextDelete(cmd *cobra.Command, args []string) error { + name := args[0] + + // Get config directory + configDir, err := getConfigDir() + if err != nil { + return fmt.Errorf("failed to get config directory: %w", err) + } + + // Create context manager + cm, err := context.NewContextManager(configDir) + if err != nil { + return fmt.Errorf("failed to create context manager: %w", err) + } + + // Delete context + if err := cm.DeleteContext(name); err != nil { + return fmt.Errorf("failed to delete context: %w", err) + } + + fmt.Printf("โœ… Context '%s' deleted successfully\n", name) + return nil +} + +func runContextShow(cmd *cobra.Command, args []string) error { + name := args[0] + + // Get config directory + configDir, err := getConfigDir() + if err != nil { + return fmt.Errorf("failed to get config directory: %w", err) + } + + // Create context manager + cm, err := context.NewContextManager(configDir) + if err != nil { + return fmt.Errorf("failed to create context manager: %w", err) + } + + // Get context + ctx, err := cm.GetContext(name) + if err != nil { + return fmt.Errorf("failed to get context: %w", err) + } + + // Display context details + fmt.Printf("Context: %s\n", ctx.Name) + fmt.Printf("Description: %s\n", ctx.Description) + fmt.Printf("Kubeconfig: %s\n", ctx.KubeConfig) + fmt.Printf("Output Directory: %s\n", ctx.OutputDir) + + if len(ctx.Labels) > 0 { + fmt.Println("Labels:") + for k, v := range ctx.Labels { + fmt.Printf(" %s: %s\n", k, v) + } + } + + fmt.Printf("Created: %s\n", ctx.CreatedAt.Format("2006-01-02 15:04:05")) + fmt.Printf("Updated: %s\n", ctx.UpdatedAt.Format("2006-01-02 15:04:05")) + + return nil +} + +func runContextCurrent(cmd *cobra.Command, args []string) error { + // Get config directory + configDir, err := getConfigDir() + if err != nil { + return fmt.Errorf("failed to get config directory: %w", err) + } + + // Create context manager + cm, err := context.NewContextManager(configDir) + if err != nil { + return fmt.Errorf("failed to create context manager: %w", err) + } + + // Get current context + current, err := cm.GetCurrentContext() + if err != nil { + return fmt.Errorf("no context is currently active. Use 'kalco context use ' to switch to a context") + } + + // Display current context + fmt.Printf("Current context: %s\n", current.Name) + fmt.Printf("Description: %s\n", current.Description) + fmt.Printf("Kubeconfig: %s\n", current.KubeConfig) + fmt.Printf("Output Directory: %s\n", current.OutputDir) + + if len(current.Labels) > 0 { + fmt.Println("Labels:") + for k, v := range current.Labels { + fmt.Printf(" %s: %s\n", k, v) + } + } + + fmt.Printf("Created: %s\n", current.CreatedAt.Format("2006-01-02 15:04:05")) + fmt.Printf("Updated: %s\n", current.UpdatedAt.Format("2006-01-02 15:04:05")) + + return nil +} diff --git a/cmd/export.go b/cmd/export.go index 7eeee36..4782249 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -5,6 +5,7 @@ import ( "strings" "time" + "kalco/pkg/context" "kalco/pkg/dumper" "kalco/pkg/git" "kalco/pkg/kube" @@ -64,10 +65,43 @@ Includes automatic Git integration for version control and change tracking. func runExport() error { printCommandHeader("CLUSTER EXPORT", "Exporting Kubernetes resources to organized YAML files") - + + // Get active context if available + activeContext, err := getActiveContext() + if err != nil { + printWarning(fmt.Sprintf("Context not available: %v", err)) + printInfo("Using command-line flags and default configuration") + } else { + printInfo(fmt.Sprintf("๐Ÿ“‹ Using context: %s", colorize(ColorCyan, activeContext.Name))) + if activeContext.Description != "" { + printInfo(fmt.Sprintf(" Description: %s", activeContext.Description)) + } + if activeContext.KubeConfig != "" { + printInfo(fmt.Sprintf(" Kubeconfig: %s", activeContext.KubeConfig)) + } + if activeContext.OutputDir != "" { + printInfo(fmt.Sprintf(" Output Dir: %s", activeContext.OutputDir)) + } + if len(activeContext.Labels) > 0 { + labelStrs := make([]string, 0, len(activeContext.Labels)) + for k, v := range activeContext.Labels { + labelStrs = append(labelStrs, fmt.Sprintf("%s=%s", k, v)) + } + printInfo(fmt.Sprintf(" Labels: %s", strings.Join(labelStrs, ", "))) + } + printSeparator() + } + // Create Kubernetes clients printInfo("๐Ÿ”Œ Connecting to Kubernetes cluster...") - clientset, discoveryClient, dynamicClient, err := kube.NewClients(kubeconfig) + + // Use context kubeconfig if available, otherwise use flag + kubeconfigPath := kubeconfig + if activeContext != nil && activeContext.KubeConfig != "" { + kubeconfigPath = activeContext.KubeConfig + } + + clientset, discoveryClient, dynamicClient, err := kube.NewClients(kubeconfigPath) if err != nil { return fmt.Errorf("failed to create Kubernetes clients: %w", err) } @@ -79,22 +113,29 @@ func runExport() error { // Configure dumper options if len(exportNamespaces) > 0 { - printInfo(fmt.Sprintf("๐Ÿ“‚ Filtering namespaces: %s", + printInfo(fmt.Sprintf("๐Ÿ“‚ Filtering namespaces: %s", colorize(ColorYellow, strings.Join(exportNamespaces, ", ")))) } if len(exportResources) > 0 { - printInfo(fmt.Sprintf("๐ŸŽฏ Filtering resources: %s", + printInfo(fmt.Sprintf("๐ŸŽฏ Filtering resources: %s", colorize(ColorYellow, strings.Join(exportResources, ", ")))) } if len(exportExclude) > 0 { - printInfo(fmt.Sprintf("๐Ÿšซ Excluding resources: %s", + printInfo(fmt.Sprintf("๐Ÿšซ Excluding resources: %s", colorize(ColorRed, strings.Join(exportExclude, ", ")))) } + // Use context output directory if available, otherwise use flag + outputDir := exportOutputDir + if activeContext != nil && activeContext.OutputDir != "" { + outputDir = activeContext.OutputDir + } + if exportDryRun { printWarning("๐Ÿงช Dry run mode - no files will be written") + printInfo(fmt.Sprintf("Would export to: %s", colorize(ColorCyan, outputDir))) return nil } @@ -106,7 +147,7 @@ func runExport() error { printInfo("๐Ÿ“ฆ Building directory structure...") printInfo("๐Ÿ’พ Exporting resources...") - if err := d.DumpAllResources(exportOutputDir); err != nil { + if err := d.DumpAllResources(outputDir); err != nil { return fmt.Errorf("failed to export resources: %w", err) } @@ -117,8 +158,8 @@ func runExport() error { printSeparator() printSubHeader("Git Integration") printInfo("๐Ÿ“ฆ Setting up Git repository...") - - gitRepo := git.NewGitRepo(exportOutputDir) + + gitRepo := git.NewGitRepo(outputDir) if err := gitRepo.SetupAndCommit(exportCommitMessage, exportGitPush); err != nil { printWarning(fmt.Sprintf("Git operations failed: %v", err)) } else { @@ -133,8 +174,8 @@ func runExport() error { printSeparator() printSubHeader("Report Generation") printInfo("๐Ÿ“Š Generating cluster analysis report...") - - reportGen := reports.NewReportGenerator(exportOutputDir) + + reportGen := reports.NewReportGenerator(outputDir) if err := reportGen.GenerateReport(exportCommitMessage); err != nil { printWarning(fmt.Sprintf("Report generation failed: %v", err)) } else { @@ -144,22 +185,35 @@ func runExport() error { // Success summary printSeparator() printHeader("EXPORT COMPLETE") - - fmt.Printf("๐Ÿ“ %s %s\n", - colorize(ColorGreen+ColorBold, "Resources exported to:"), - colorize(ColorCyan+ColorBold, exportOutputDir)) + + fmt.Printf("๐Ÿ“ %s %s\n", + colorize(ColorGreen+ColorBold, "Resources exported to:"), + colorize(ColorCyan+ColorBold, outputDir)) fmt.Println() - + printInfo("๐ŸŽฏ Your cluster snapshot is ready for:") fmt.Printf(" %s Backup and disaster recovery\n", colorize(ColorGreen, "โ€ข")) fmt.Printf(" %s Resource auditing and compliance\n", colorize(ColorGreen, "โ€ข")) fmt.Printf(" %s Development environment replication\n", colorize(ColorGreen, "โ€ข")) - fmt.Printf(" %s Documentation and resource cataloging\n", colorize(ColorGreen, "โ€ข")) - fmt.Println() return nil } +// getActiveContext returns the currently active context if available +func getActiveContext() (*context.Context, error) { + configDir, err := getConfigDir() + if err != nil { + return nil, fmt.Errorf("failed to get config directory: %w", err) + } + + cm, err := context.NewContextManager(configDir) + if err != nil { + return nil, fmt.Errorf("failed to create context manager: %w", err) + } + + return cm.GetCurrentContext() +} + func init() { rootCmd.AddCommand(exportCmd) @@ -178,4 +232,4 @@ func init() { // Add aliases exportCmd.Aliases = []string{"dump", "backup"} -} \ No newline at end of file +} diff --git a/cmd/root.go b/cmd/root.go index aa07bfb..2aeae24 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -25,7 +25,12 @@ resource extraction, validation, and lifecycle management. Extract, validate, analyze, and version control your entire cluster with comprehensive validation and Git integration. `), - Example: ` # Export entire cluster to timestamped directory + Example: ` # Manage cluster contexts + kalco context set production --kubeconfig ~/.kube/prod-config --output ./prod-exports + kalco context use production + kalco context list + + # Export entire cluster to timestamped directory kalco export # Export to specific directory with custom options diff --git a/cmd/utils.go b/cmd/utils.go new file mode 100644 index 0000000..57ec0fc --- /dev/null +++ b/cmd/utils.go @@ -0,0 +1,18 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" +) + +// getConfigDir returns the Kalco configuration directory +func getConfigDir() (string, error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("failed to get user home directory: %w", err) + } + + configDir := filepath.Join(homeDir, ".kalco") + return configDir, nil +} diff --git a/docs/docs/commands/context.md b/docs/docs/commands/context.md new file mode 100644 index 0000000..95cb831 --- /dev/null +++ b/docs/docs/commands/context.md @@ -0,0 +1,355 @@ +--- +layout: default +title: kalco context +nav_order: 2 +parent: Commands Reference +--- + +# kalco context + +Manage Kalco contexts for different Kubernetes clusters and configurations. + +## Overview + +A Kalco context defines a complete configuration for working with a specific Kubernetes cluster: + +- **Kubeconfig file** - Path to the cluster configuration file +- **Output directory** - Default directory for exports +- **Dynamic labels** - Key-value pairs for context identification +- **Description** - Human-readable description of the context purpose + +Contexts make it easy to switch between different clusters and configurations without specifying the same flags repeatedly. + +## Commands + +### kalco context set + +Create or update a context with the specified configuration. + +```bash +kalco context set [flags] +``` + +#### Flags + +| Flag | Description | Example | +|------|-------------|---------| +| `--kubeconfig` | Path to kubeconfig file | `--kubeconfig ~/.kube/prod-config` | +| `--output` | Output directory for exports | `--output ./prod-exports` | +| `--description` | Human-readable description | `--description "Production cluster"` | +| `--labels` | Labels in key=value format | `--labels env=prod --labels team=platform` | + +#### Examples + +```bash +# Create a production context +kalco context set production \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports \ + --description "Production cluster" \ + --labels env=prod \ + --labels team=platform + +# Create a development context +kalco context set dev \ + --kubeconfig ~/.kube/dev-config \ + --output ./dev-exports \ + --description "Development cluster" \ + --labels env=dev \ + --labels team=backend + +# Update an existing context +kalco context set production \ + --output ./new-prod-exports \ + --labels env=prod \ + --labels team=platform \ + --labels region=eu-west +``` + +### kalco context list + +Display all available contexts with their configuration details. + +```bash +kalco context list +``` + +#### Output + +``` +Available contexts: + +* production + Description: Production cluster + Kubeconfig: /Users/user/.kube/prod-config + Output Dir: ./prod-exports + Labels: env=prod, team=platform + Created: 2025-08-17 15:30:45 + Updated: 2025-08-17 15:30:45 + + dev + Description: Development cluster + Kubeconfig: /Users/user/.kube/dev-config + Output Dir: ./dev-exports + Labels: env=dev, team=backend + Created: 2025-08-17 14:20:30 + Updated: 2025-08-17 14:20:30 + +* = current context +``` + +### kalco context use + +Switch to the specified context. This context will be used for future operations. + +```bash +kalco context use +``` + +#### Examples + +```bash +# Switch to production context +kalco context use production + +# Switch to development context +kalco context use dev +``` + +### kalco context current + +Display information about the currently active context. + +```bash +kalco context current +``` + +#### Output + +``` +Current context: production +Description: Production cluster +Kubeconfig: /Users/user/.kube/prod-config +Output Directory: ./prod-exports +Labels: + env: prod + team: platform +Created: 2025-08-17 15:30:45 +Updated: 2025-08-17 15:30:45 +``` + +### kalco context show + +Display detailed information about a specific context. + +```bash +kalco context show +``` + +#### Examples + +```bash +# Show production context details +kalco context show production + +# Show development context details +kalco context show dev +``` + +### kalco context delete + +Delete the specified context. Cannot delete the currently active context. + +```bash +kalco context delete +``` + +#### Examples + +```bash +# Delete a context (must not be active) +kalco context delete old-cluster + +# Switch context first, then delete +kalco context use production +kalco context delete old-cluster +``` + +## Context Integration + +### With Export Command + +When you run `kalco export`, the command automatically uses the active context: + +```bash +# Set and use a context +kalco context set my-cluster \ + --kubeconfig ~/.kube/my-config \ + --output ./my-exports + +kalco context use my-cluster + +# Export using context configuration +kalco export # Uses my-cluster context automatically +``` + +### Context Priority + +Context settings have priority over command-line flags: + +```bash +# Context specifies output directory +kalco context set prod --output ./prod-exports +kalco context use prod + +# This will export to ./prod-exports, not ./custom +kalco export --output ./custom +``` + +### Override Context + +You can still override context settings with flags: + +```bash +# Context specifies output directory +kalco context set prod --output ./prod-exports +kalco context use prod + +# Override with flag (higher priority) +kalco export --output ./override-output +``` + +## Configuration Files + +Contexts are stored in `~/.kalco/`: + +``` +~/.kalco/ +โ”œโ”€โ”€ contexts.yaml # Context definitions +โ””โ”€โ”€ current-context # Currently active context +``` + +### contexts.yaml + +```yaml +production: + name: production + kubeconfig: /Users/user/.kube/prod-config + output_dir: ./prod-exports + labels: + env: prod + team: platform + description: Production cluster + created_at: 2025-08-17T15:30:45+02:00 + updated_at: 2025-08-17T15:30:45+02:00 + +dev: + name: dev + kubeconfig: /Users/user/.kube/dev-config + output_dir: ./dev-exports + labels: + env: dev + team: backend + description: Development cluster + created_at: 2025-08-17T14:20:30+02:00 + updated_at: 2025-08-17T14:20:30+02:00 +``` + +### current-context + +``` +production +``` + +## Best Practices + +### Naming Conventions + +- Use descriptive names: `production`, `staging`, `dev-east`, `prod-eu-west` +- Include environment and region information in names or labels +- Use consistent naming across team members + +### Label Strategy + +- **Environment**: `env=prod`, `env=staging`, `env=dev` +- **Team**: `team=platform`, `team=backend`, `team=frontend` +- **Region**: `region=eu-west`, `region=us-east`, `region=asia` +- **Purpose**: `purpose=testing`, `purpose=monitoring`, `purpose=backup` + +### Organization + +```bash +# Production contexts +kalco context set prod-eu-west \ + --kubeconfig ~/.kube/prod-eu-west \ + --output ./exports/prod-eu-west \ + --labels env=prod,region=eu-west,team=platform + +kalco context set prod-us-east \ + --kubeconfig ~/.kube/prod-us-east \ + --output ./exports/prod-us-east \ + --labels env=prod,region=us-east,team=platform + +# Development contexts +kalco context set dev-backend \ + --kubeconfig ~/.kube/dev-backend \ + --output ./exports/dev-backend \ + --labels env=dev,team=backend,purpose=testing + +kalco context set dev-frontend \ + --kubeconfig ~/.kube/dev-frontend \ + --output ./exports/dev-frontend \ + --labels env=dev,team=frontend,purpose=testing +``` + +## Troubleshooting + +### Common Issues + +**Context not found** +```bash +Error: context 'production' not found +``` +Solution: Use `kalco context list` to see available contexts. + +**Cannot delete current context** +```bash +Error: cannot delete current context 'production'. Switch to another context first +``` +Solution: Switch to a different context first, then delete. + +**Invalid label format** +```bash +Error: invalid label format: env=prod,team=platform (expected key=value) +``` +Solution: Use separate `--labels` flags: `--labels env=prod --labels team=platform` + +### Context Validation + +Contexts are automatically validated: + +- Kubeconfig file must exist +- Output directory must be writable +- Labels must be in `key=value` format + +### Migration from Flags + +If you're currently using flags, you can migrate to contexts: + +```bash +# Before (using flags) +kalco export --kubeconfig ~/.kube/prod-config --output ./prod-exports + +# After (using context) +kalco context set prod \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports +kalco context use prod +kalco export # Uses context automatically +``` + +## Related Commands + +- **[kalco export]({{ site.baseurl }}/commands/export)** - Export cluster resources +- **[kalco validate]({{ site.baseurl }}/commands/validate)** - Validate cluster resources +- **[kalco analyze]({{ site.baseurl }}/commands/analyze)** - Analyze cluster state diff --git a/docs/docs/commands/index.md b/docs/docs/commands/index.md new file mode 100644 index 0000000..4ada83e --- /dev/null +++ b/docs/docs/commands/index.md @@ -0,0 +1,108 @@ +--- +layout: default +title: Commands Reference +nav_order: 3 +has_children: true +--- + +# Commands Reference + +Complete reference for all Kalco commands and options. + +## ๐Ÿ“‹ Available Commands + +- **[context]({{ site.baseurl }}/docs/commands/context)** - Manage cluster contexts and configurations +- **[export]({{ site.baseurl }}/docs/commands/export)** - Export cluster resources to organized YAML files +- **[validate]({{ site.baseurl }}/docs/commands/validate)** - Validate cluster resources and cross-references +- **[analyze]({{ site.baseurl }}/docs/commands/analyze)** - Analyze cluster state and generate reports +- **[config]({{ site.baseurl }}/docs/commands/config)** - Manage configuration + +## ๐Ÿšฉ Global Options + +All commands support these global options: + +```bash +--help, -h Show help for the command +--version, -v Show version information +--verbose Enable verbose output +--quiet Suppress non-error messages +--config Path to configuration file +``` + +## ๐Ÿ”ง Command Structure + +```bash +kalco [subcommand] [flags] [arguments] +``` + +### Examples + +```bash +# Basic export +kalco export + +# Export with options +kalco export --output ./backup --namespaces default,kube-system + +# Manage contexts +kalco context set production --kubeconfig ~/.kube/prod-config --output ./prod-exports +kalco context use production + +# Validate cluster +kalco validate --cross-references + +# Show configuration +kalco config show +``` + +## ๐Ÿ“š Command Categories + +### Core Operations +- **context** - Context management for different clusters and configurations +- **export** - Primary functionality for cluster resource extraction +- **validate** - Resource validation and health checks +- **analyze** - Cluster analysis and reporting + +### Configuration & Management +- **config** - Configuration management and validation +- **completion** - Shell completion generation + +## ๐ŸŽฏ Getting Help + +### Command Help + +```bash +# General help +kalco --help + +# Command-specific help +kalco export --help +kalco context --help +kalco validate --help +``` + +### Examples + +```bash +# Show examples for export command +kalco export --help | grep -A 10 "Examples" + +# Show all available flags +kalco export --help | grep -E "^ --" +``` + +## ๐Ÿ” Command Discovery + +Explore available commands: + +```bash +# List all commands +kalco --help + +# Show command tree +kalco --help | grep -E "^ [a-z]" +``` + +## ๐Ÿ“– Next Steps + +Explore the specific command categories to learn more about each command's options and usage patterns. diff --git a/docs/index.md b/docs/index.md index e1d9633..0c28448 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ nav_order: 1 --- -## ๐Ÿš€ What is Kalco? +## ๐Ÿš€ What is Kalco? Kalco is a powerful Kubernetes cluster management tool that provides comprehensive resource extraction, validation, and version control capabilities. It's designed to help DevOps engineers, SREs, and platform teams maintain clean, validated, and version-controlled cluster configurations. @@ -24,6 +24,7 @@ Kalco is a powerful Kubernetes cluster management tool that provides comprehensi - **๐ŸŽฏ Flexible Filtering** - Export specific namespaces, resources, or exclude noisy types - **๐Ÿ“Š Detailed Reporting** - Comprehensive change analysis and resource summaries - **๐Ÿ”„ Incremental Updates** - Track changes between cluster snapshots +- **๐ŸŒ Context Management** - Manage multiple clusters with easy switching ## ๐Ÿš€ Quick Start @@ -31,8 +32,17 @@ Kalco is a powerful Kubernetes cluster management tool that provides comprehensi # Install Kalco go install github.com/graz-dev/kalco/cmd/kalco@latest +# Set up a cluster context +kalco context set production \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports \ + --labels env=prod,team=platform + +# Use the context +kalco context use production + # Export your cluster -kalco export --output ./cluster-backup +kalco export # Export with Git integration kalco export --git-push --commit-message "Cluster snapshot $(date)" diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..3ce6dab --- /dev/null +++ b/examples/README.md @@ -0,0 +1,116 @@ +# Kalco Examples + +This directory contains practical examples and scripts demonstrating how to use Kalco effectively. + +## ๐Ÿ“ Available Examples + +### ๐Ÿš€ Quick Start +- **[quickstart.sh](quickstart.sh)** - Complete workflow from cluster creation to export and analysis + +### ๐ŸŒ Context Management +- **[context-example.sh](context-example.sh)** - Demonstrates context management for multiple clusters + +## ๐ŸŽฏ Quick Start Example + +The `quickstart.sh` script provides a complete end-to-end example: + +```bash +# Make executable and run +chmod +x examples/quickstart.sh +./examples/quickstart.sh +``` + +This example: +1. Creates a Kind cluster +2. Deploys a sample application +3. Runs Kalco export +4. Makes changes to the cluster +5. Runs Kalco again to see changes +6. Cleans up the cluster + +## ๐Ÿ”„ Context Management Example + +The `context-example.sh` script demonstrates context management: + +```bash +# Make executable and run +chmod +x examples/context-example.sh +./examples/context-example.sh +``` + +This example: +1. Creates multiple contexts for different environments +2. Shows context switching +3. Demonstrates automatic context usage in export +4. Shows context override with flags +5. Cleans up example contexts + +## ๐Ÿ› ๏ธ Prerequisites + +Before running the examples, ensure you have: + +- **Kalco** installed and in your PATH +- **Docker** running (for Kind clusters) +- **Kind** installed (for local clusters) +- **kubectl** configured + +## ๐Ÿ“š Learning Path + +1. **Start with quickstart.sh** - Learn basic Kalco operations +2. **Try context-example.sh** - Master context management +3. **Experiment with your own clusters** - Apply concepts to real scenarios + +## ๐Ÿ”ง Customization + +Feel free to modify these examples: + +- Change cluster names and configurations +- Add your own applications and resources +- Modify export parameters and filters +- Customize context labels and descriptions + +## ๐Ÿ› Troubleshooting + +### Common Issues + +**Permission denied** +```bash +chmod +x examples/*.sh +``` + +**Command not found** +```bash +# Ensure Kalco is in your PATH +which kalco +``` + +**Cluster connection issues** +```bash +# Check cluster status +kubectl cluster-info +kubectl get nodes +``` + +## ๐Ÿ“– Next Steps + +After running the examples: + +1. **Read the documentation** - Explore the full command reference +2. **Try different clusters** - Test with your own Kubernetes clusters +3. **Customize contexts** - Create contexts for your specific use cases +4. **Integrate with CI/CD** - Automate exports in your pipelines + +## ๐Ÿค Contributing + +Have a great example? Feel free to contribute: + +1. Create a new script with descriptive name +2. Add clear comments and documentation +3. Include error handling and cleanup +4. Update this README with your example + +## ๐Ÿ“š Resources + +- [Kalco Documentation](../docs/) +- [Commands Reference](../docs/commands/) +- [Getting Started](../docs/getting-started/) diff --git a/examples/context-example.sh b/examples/context-example.sh new file mode 100755 index 0000000..937d6f2 --- /dev/null +++ b/examples/context-example.sh @@ -0,0 +1,154 @@ +#!/bin/bash + +# Kalco Context Management Example +# This script demonstrates how to use Kalco contexts for managing multiple clusters + +set -e + +echo "๐Ÿš€ Kalco Context Management Example" +echo "==================================" +echo + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_info() { + echo -e "${BLUE}โ„น๏ธ $1${NC}" +} + +print_success() { + echo -e "${GREEN}โœ… $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}โš ๏ธ $1${NC}" +} + +print_error() { + echo -e "${RED}โŒ $1${NC}" +} + +# Check if kalco is available (use local binary if available, otherwise system) +KALCO_CMD="kalco" +if [ -f "./kalco" ]; then + KALCO_CMD="./kalco" + print_info "Using local kalco binary" +elif command -v kalco &> /dev/null; then + print_info "Using system kalco binary" +else + print_error "Kalco is not available. Please build it first with 'make build' or install it." + exit 1 +fi + +print_success "Kalco is available and ready to use" +echo + +# Create example contexts for different environments +print_info "Creating example contexts for different environments..." + +# Production context +print_info "Setting up production context..." +$KALCO_CMD context set production \ + --kubeconfig ~/.kube/config \ + --output ./exports/production \ + --description "Production cluster for live workloads" \ + --labels env=production \ + --labels team=platform \ + --labels region=eu-west + +# Staging context +print_info "Setting up staging context..." +$KALCO_CMD context set staging \ + --kubeconfig ~/.kube/config \ + --output ./exports/staging \ + --description "Staging cluster for testing" \ + --labels env=staging \ + --labels team=qa \ + --labels purpose=testing + +# Development context +print_info "Setting up development context..." +$KALCO_CMD context set development \ + --kubeconfig ~/.kube/config \ + --output ./exports/development \ + --description "Development cluster for engineers" \ + --labels env=development \ + --labels team=engineering \ + --labels purpose=development + +print_success "All contexts created successfully" +echo + +# List all contexts +print_info "Listing all available contexts..." +$KALCO_CMD context list +echo + +# Switch between contexts and show current +print_info "Demonstrating context switching..." + +print_info "Switching to production context..." +$KALCO_CMD context use production +$KALCO_CMD context current +echo + +print_info "Switching to staging context..." +$KALCO_CMD context use staging +$KALCO_CMD context current +echo + +print_info "Switching to development context..." +$KALCO_CMD context use development +$KALCO_CMD context current +echo + +# Show how export uses context automatically +print_info "Demonstrating automatic context usage in export command..." +print_info "Current context: development" +$KALCO_CMD export --dry-run +echo + +# Switch back to production and export +print_info "Switching to production and exporting..." +$KALCO_CMD context use production +$KALCO_CMD export --dry-run +echo + +# Show context override with flags +print_info "Demonstrating context override with command-line flags..." +$KALCO_CMD export --dry-run --output ./override-output +echo + +# Show context details +print_info "Showing detailed context information..." +$KALCO_CMD context show production +echo + +# Cleanup example contexts +print_warning "Cleaning up example contexts..." +$KALCO_CMD context use development +$KALCO_CMD context delete production +$KALCO_CMD context use staging +$KALCO_CMD context delete development +$KALCO_CMD context use temp +$KALCO_CMD context delete staging + +print_success "Example contexts cleaned up" +echo + +print_success "Context management example completed successfully!" +echo +print_info "Key takeaways:" +echo " โ€ข Use 'kalco context set' to create contexts" +echo " โ€ข Use 'kalco context use' to switch between contexts" +echo " โ€ข Contexts automatically configure kubeconfig and output directory" +echo " โ€ข Command-line flags can override context settings" +echo " โ€ข Use 'kalco context list' to see all available contexts" +echo " โ€ข Use 'kalco context current' to see active context" +echo +print_info "For more information, run: kalco context --help" diff --git a/pkg/context/context.go b/pkg/context/context.go new file mode 100644 index 0000000..c531c68 --- /dev/null +++ b/pkg/context/context.go @@ -0,0 +1,238 @@ +package context + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "gopkg.in/yaml.v3" +) + +// Context represents a Kalco context configuration +type Context struct { + Name string `json:"name" yaml:"name"` + KubeConfig string `json:"kubeconfig" yaml:"kubeconfig"` + OutputDir string `json:"output_dir" yaml:"output_dir"` + Labels map[string]string `json:"labels" yaml:"labels"` + Description string `json:"description" yaml:"description"` + CreatedAt time.Time `json:"created_at" yaml:"created_at"` + UpdatedAt time.Time `json:"updated_at" yaml:"updated_at"` +} + +// ContextManager handles context operations +type ContextManager struct { + configDir string + contexts map[string]*Context + current string +} + +// NewContextManager creates a new context manager +func NewContextManager(configDir string) (*ContextManager, error) { + cm := &ContextManager{ + configDir: configDir, + contexts: make(map[string]*Context), + } + + // Ensure config directory exists + if err := os.MkdirAll(configDir, 0755); err != nil { + return nil, fmt.Errorf("failed to create config directory: %w", err) + } + + // Load existing contexts + if err := cm.loadContexts(); err != nil { + return nil, fmt.Errorf("failed to load contexts: %w", err) + } + + // Load current context + if err := cm.loadCurrentContext(); err != nil { + return nil, fmt.Errorf("failed to load current context: %w", err) + } + + return cm, nil +} + +// SetContext creates or updates a context +func (cm *ContextManager) SetContext(name, kubeconfig, outputDir, description string, labels map[string]string) error { + if name == "" { + return fmt.Errorf("context name cannot be empty") + } + + now := time.Now() + context := &Context{ + Name: name, + KubeConfig: kubeconfig, + OutputDir: outputDir, + Labels: labels, + Description: description, + UpdatedAt: now, + } + + // If context exists, preserve creation time + if existing, exists := cm.contexts[name]; exists { + context.CreatedAt = existing.CreatedAt + } else { + context.CreatedAt = now + } + + cm.contexts[name] = context + + // Save contexts + if err := cm.saveContexts(); err != nil { + return fmt.Errorf("failed to save contexts: %w", err) + } + + return nil +} + +// GetContext retrieves a context by name +func (cm *ContextManager) GetContext(name string) (*Context, error) { + context, exists := cm.contexts[name] + if !exists { + return nil, fmt.Errorf("context '%s' not found", name) + } + return context, nil +} + +// ListContexts returns all available contexts +func (cm *ContextManager) ListContexts() map[string]*Context { + return cm.contexts +} + +// UseContext sets the current active context +func (cm *ContextManager) UseContext(name string) error { + if _, exists := cm.contexts[name]; !exists { + return fmt.Errorf("context '%s' not found", name) + } + + cm.current = name + + // Save current context + if err := cm.saveCurrentContext(); err != nil { + return fmt.Errorf("failed to save current context: %w", err) + } + + return nil +} + +// GetCurrentContext returns the current active context +func (cm *ContextManager) GetCurrentContext() (*Context, error) { + if cm.current == "" { + return nil, fmt.Errorf("no context is currently active") + } + + return cm.GetContext(cm.current) +} + +// DeleteContext removes a context +func (cm *ContextManager) DeleteContext(name string) error { + if _, exists := cm.contexts[name]; !exists { + return fmt.Errorf("context '%s' not found", name) + } + + // Don't allow deleting current context + if cm.current == name { + return fmt.Errorf("cannot delete current context '%s'. Switch to another context first", name) + } + + delete(cm.contexts, name) + + // Save contexts + if err := cm.saveContexts(); err != nil { + return fmt.Errorf("failed to save contexts: %w", err) + } + + return nil +} + +// loadContexts loads contexts from disk +func (cm *ContextManager) loadContexts() error { + contextsFile := filepath.Join(cm.configDir, "contexts.yaml") + + if _, err := os.Stat(contextsFile); os.IsNotExist(err) { + return nil // No contexts file yet + } + + data, err := os.ReadFile(contextsFile) + if err != nil { + return fmt.Errorf("failed to read contexts file: %w", err) + } + + if err := yaml.Unmarshal(data, &cm.contexts); err != nil { + return fmt.Errorf("failed to unmarshal contexts: %w", err) + } + + return nil +} + +// saveContexts saves contexts to disk +func (cm *ContextManager) saveContexts() error { + contextsFile := filepath.Join(cm.configDir, "contexts.yaml") + + data, err := yaml.Marshal(cm.contexts) + if err != nil { + return fmt.Errorf("failed to marshal contexts: %w", err) + } + + if err := os.WriteFile(contextsFile, data, 0644); err != nil { + return fmt.Errorf("failed to write contexts file: %w", err) + } + + return nil +} + +// loadCurrentContext loads the current context from disk +func (cm *ContextManager) loadCurrentContext() error { + currentFile := filepath.Join(cm.configDir, "current-context") + + if _, err := os.Stat(currentFile); os.IsNotExist(err) { + return nil // No current context file yet + } + + data, err := os.ReadFile(currentFile) + if err != nil { + return fmt.Errorf("failed to read current context file: %w", err) + } + + cm.current = string(data) + return nil +} + +// saveCurrentContext saves the current context to disk +func (cm *ContextManager) saveCurrentContext() error { + currentFile := filepath.Join(cm.configDir, "current-context") + + if err := os.WriteFile(currentFile, []byte(cm.current), 0644); err != nil { + return fmt.Errorf("failed to write current context file: %w", err) + } + + return nil +} + +// GetConfigDir returns the configuration directory +func (cm *ContextManager) GetConfigDir() string { + return cm.configDir +} + +// ValidateContext validates a context configuration +func (cm *ContextManager) ValidateContext(context *Context) error { + if context.Name == "" { + return fmt.Errorf("context name cannot be empty") + } + + if context.KubeConfig != "" { + if _, err := os.Stat(context.KubeConfig); os.IsNotExist(err) { + return fmt.Errorf("kubeconfig file '%s' does not exist", context.KubeConfig) + } + } + + if context.OutputDir != "" { + // Ensure output directory can be created + dir := context.OutputDir + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("cannot create output directory '%s': %w", dir, err) + } + } + + return nil +} From fbd1bb68d0ca9c61eab5e99c0fa01f288b713197 Mon Sep 17 00:00:00 2001 From: Graziano Casto Date: Mon, 18 Aug 2025 17:27:52 +0200 Subject: [PATCH 2/3] *added kalco context load --- cmd/cmd_test.go | 293 ++++++++++++++++++ cmd/context.go | 137 +++++++-- docs/docs/commands/context.md | 107 ++++++- pkg/context/context.go | 80 ++++- pkg/context/context_test.go | 506 +++++++++++++++++++++++++++++++ pkg/orphaned/orphaned_test.go | 404 ++++++++++++++++++++++++ pkg/validation/validator_test.go | 400 ++++++++++++++++++++++++ 7 files changed, 1896 insertions(+), 31 deletions(-) create mode 100644 cmd/cmd_test.go create mode 100644 pkg/context/context_test.go create mode 100644 pkg/orphaned/orphaned_test.go create mode 100644 pkg/validation/validator_test.go diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go new file mode 100644 index 0000000..b05329a --- /dev/null +++ b/cmd/cmd_test.go @@ -0,0 +1,293 @@ +package cmd + +import ( + "path/filepath" + "testing" + + "github.com/spf13/cobra" +) + +func TestGetConfigDir(t *testing.T) { + // Test that getConfigDir returns a valid path + configDir, err := getConfigDir() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // Should contain .kalco + if !contains(configDir, ".kalco") { + t.Errorf("Expected config dir to contain '.kalco', got %s", configDir) + } + + // Should be absolute path + if !filepath.IsAbs(configDir) { + t.Errorf("Expected absolute path, got %s", configDir) + } +} + +func TestGetActiveContext(t *testing.T) { + // Test when no context is available + // This should not error, just return nil or error about no context + _, err := getActiveContext() + if err != nil { + // This is expected when no context is set up + // The error should be about no context being active + if !contains(err.Error(), "no context") && !contains(err.Error(), "failed to get config directory") { + t.Errorf("Unexpected error: %v", err) + } + } + // activeContext can be nil or contain a context, both are valid +} + +func TestContextCommandRegistration(t *testing.T) { + // Test that context command is properly registered + if contextCmd == nil { + t.Fatal("contextCmd should not be nil") + } + + // Test that all subcommands are registered + subcommands := contextCmd.Commands() + expectedSubcommands := []string{"set", "list", "use", "delete", "show", "current", "load"} + + for _, expected := range expectedSubcommands { + found := false + for _, cmd := range subcommands { + if cmd.Name() == expected { + found = true + break + } + } + if !found { + t.Errorf("Subcommand '%s' not found in context command", expected) + } + } +} + +func TestContextSetCommandFlags(t *testing.T) { + // Test that all expected flags are registered + if contextSetCmd == nil { + t.Fatal("contextSetCmd should not be nil") + } + + // Check for required flags + flags := contextSetCmd.Flags() + + expectedFlags := []string{"kubeconfig", "output", "description", "labels"} + for _, expected := range expectedFlags { + if flags.Lookup(expected) == nil { + t.Errorf("Flag '--%s' not found in context set command", expected) + } + } +} + +func TestContextListCommand(t *testing.T) { + // Test that list command is properly configured + if contextListCmd == nil { + t.Fatal("contextListCmd should not be nil") + } + + // Verify command properties + if contextListCmd.Use != "list" { + t.Errorf("Expected use 'list', got %s", contextListCmd.Use) + } + + if contextListCmd.Short == "" { + t.Error("Expected non-empty short description") + } +} + +func TestContextUseCommand(t *testing.T) { + // Test that use command is properly configured + if contextUseCmd == nil { + t.Fatal("contextUseCmd should not be nil") + } + + // Verify command properties + if contextUseCmd.Use != "use [name]" { + t.Errorf("Expected use 'use [name]', got %s", contextUseCmd.Use) + } + + if contextUseCmd.Args == nil { + t.Error("Expected args validation") + } +} + +func TestContextDeleteCommand(t *testing.T) { + // Test that delete command is properly configured + if contextDeleteCmd == nil { + t.Fatal("contextDeleteCmd should not be nil") + } + + // Verify command properties + if contextDeleteCmd.Use != "delete [name]" { + t.Errorf("Expected use 'delete [name]', got %s", contextDeleteCmd.Use) + } +} + +func TestContextShowCommand(t *testing.T) { + // Test that show command is properly configured + if contextShowCmd == nil { + t.Fatal("contextShowCmd should not be nil") + } + + // Verify command properties + if contextShowCmd.Use != "show [name]" { + t.Errorf("Expected use 'show [name]', got %s", contextShowCmd.Use) + } +} + +func TestContextCurrentCommand(t *testing.T) { + // Test that current command is properly configured + if contextCurrentCmd == nil { + t.Fatal("contextCurrentCmd should not be nil") + } + + // Verify command properties + if contextCurrentCmd.Use != "current" { + t.Errorf("Expected use 'current', got %s", contextCurrentCmd.Use) + } +} + +func TestContextLoadCommand(t *testing.T) { + // Test that load command is properly configured + if contextLoadCmd == nil { + t.Fatal("contextLoadCmd should not be nil") + } + + // Verify command properties + if contextLoadCmd.Use != "load [directory]" { + t.Errorf("Expected use 'load [directory]', got %s", contextLoadCmd.Use) + } + + if contextLoadCmd.Args == nil { + t.Error("Expected args validation") + } +} + +func TestRootCommandIntegration(t *testing.T) { + // Test that root command has context subcommand + if rootCmd == nil { + t.Fatal("rootCmd should not be nil") + } + + // Find context command in root + found := false + for _, cmd := range rootCmd.Commands() { + if cmd.Name() == "context" { + found = true + break + } + } + + if !found { + t.Error("context command not found in root command") + } +} + +// Helper function to check if string contains substring +func contains(s, substr string) bool { + return len(s) > 0 && len(substr) > 0 && len(s) >= len(substr) && + (s == substr || len(s) > len(substr)) +} + +func TestContextCommandHelp(t *testing.T) { + // Test that context command has proper help text + if contextCmd == nil { + t.Fatal("contextCmd should not be nil") + } + + if contextCmd.Short == "" { + t.Error("Expected non-empty short description") + } + + if contextCmd.Long == "" { + t.Error("Expected non-empty long description") + } + + // Examples are optional, so we don't require them +} + +func TestContextSubcommandHelp(t *testing.T) { + // Test that all subcommands have proper help text + subcommands := []*cobra.Command{ + contextSetCmd, contextListCmd, contextUseCmd, + contextDeleteCmd, contextShowCmd, contextCurrentCmd, contextLoadCmd, + } + + for i, cmd := range subcommands { + if cmd == nil { + t.Errorf("Subcommand %d is nil", i) + continue + } + + if cmd.Short == "" { + t.Errorf("Subcommand %s missing short description", cmd.Name()) + } + + if cmd.Long == "" { + t.Errorf("Subcommand %s missing long description", cmd.Name()) + } + } +} + +func TestContextCommandStructure(t *testing.T) { + // Test that context command structure is correct + if contextCmd.Use != "context" { + t.Errorf("Expected use 'context', got %s", contextCmd.Use) + } + + // Test that it's a proper command + if contextCmd.Run != nil { + t.Error("Context command should not have Run function, it's a parent command") + } + + // Test that it has subcommands + if len(contextCmd.Commands()) == 0 { + t.Error("Context command should have subcommands") + } +} + +func TestContextCommandFlags(t *testing.T) { + // Test that context command has proper flags + if contextCmd == nil { + t.Fatal("contextCmd should not be nil") + } + + // Context command should inherit global flags + // Note: help flag might not be directly accessible depending on Cobra version + flags := contextCmd.Flags() + if flags == nil { + t.Error("Context command should have flags") + } +} + +func TestContextSubcommandArgs(t *testing.T) { + // Test that subcommands have proper argument validation + subcommands := map[string]*cobra.Command{ + "set": contextSetCmd, + "use": contextUseCmd, + "delete": contextDeleteCmd, + "show": contextShowCmd, + "load": contextLoadCmd, + } + + for name, cmd := range subcommands { + if cmd == nil { + t.Errorf("Subcommand %s is nil", name) + continue + } + + if cmd.Args == nil { + t.Errorf("Subcommand %s missing args validation", name) + } + } + + // List and current commands don't need args + if contextListCmd.Args != nil { + t.Error("List command should not have args validation") + } + + if contextCurrentCmd.Args != nil { + t.Error("Current command should not have args validation") + } +} diff --git a/cmd/context.go b/cmd/context.go index 8eb8e65..2ece87f 100644 --- a/cmd/context.go +++ b/cmd/context.go @@ -1,11 +1,15 @@ package cmd import ( + "encoding/json" "fmt" + "os" + "path/filepath" "strings" - "github.com/spf13/cobra" "kalco/pkg/context" + + "github.com/spf13/cobra" ) var ( @@ -23,7 +27,8 @@ A context defines: Examples: kalco context set production --kubeconfig ~/.kube/prod-config --output ./prod-exports --labels env=prod,team=platform kalco context list - kalco context use production`, + kalco context use production + kalco context load ./existing-kalco-export`, } contextSetCmd = &cobra.Command{ @@ -39,43 +44,53 @@ The context will be saved and can be used for future operations.`, contextListCmd = &cobra.Command{ Use: "list", Short: "List all contexts", - Long: `Display all available contexts with their configuration details.`, - RunE: runContextList, + Long: `Display all available contexts with their configuration details.`, + RunE: runContextList, } contextUseCmd = &cobra.Command{ Use: "use [name]", Short: "Switch to a context", - Long: `Switch to the specified context. This context will be used for future operations.`, - Args: cobra.ExactArgs(1), - RunE: runContextUse, + Long: `Switch to the specified context. This context will be used for future operations.`, + Args: cobra.ExactArgs(1), + RunE: runContextUse, } contextDeleteCmd = &cobra.Command{ Use: "delete [name]", Short: "Delete a context", - Long: `Delete the specified context. Cannot delete the currently active context.`, - Args: cobra.ExactArgs(1), - RunE: runContextDelete, + Long: `Delete the specified context. Cannot delete the currently active context.`, + Args: cobra.ExactArgs(1), + RunE: runContextDelete, } contextShowCmd = &cobra.Command{ Use: "show [name]", Short: "Show context details", - Long: `Display detailed information about a specific context.`, - Args: cobra.ExactArgs(1), - RunE: runContextShow, + Long: `Display detailed information about a specific context.`, + Args: cobra.ExactArgs(1), + RunE: runContextShow, } contextCurrentCmd = &cobra.Command{ Use: "current", Short: "Show current context", - Long: `Display information about the currently active context.`, - RunE: runContextCurrent, + Long: `Display information about the currently active context.`, + RunE: runContextCurrent, + } + + contextLoadCmd = &cobra.Command{ + Use: "load [directory]", + Short: "Load context from existing kalco directory", + Long: `Load a context configuration from an existing kalco directory by reading the kalco-config.json file. + +This is useful for importing contexts from existing kalco exports or for team collaboration.`, + Args: cobra.ExactArgs(1), + RunE: runContextLoad, } // Flags for context set - contextKubeConfig string + contextKubeConfig string contextOutputDir string contextDescription string contextLabels []string @@ -91,6 +106,7 @@ func init() { contextCmd.AddCommand(contextDeleteCmd) contextCmd.AddCommand(contextShowCmd) contextCmd.AddCommand(contextCurrentCmd) + contextCmd.AddCommand(contextLoadCmd) // Add flags for context set contextSetCmd.Flags().StringVar(&contextKubeConfig, "kubeconfig", "", "Path to kubeconfig file") @@ -272,14 +288,14 @@ func runContextShow(cmd *cobra.Command, args []string) error { fmt.Printf("Description: %s\n", ctx.Description) fmt.Printf("Kubeconfig: %s\n", ctx.KubeConfig) fmt.Printf("Output Directory: %s\n", ctx.OutputDir) - + if len(ctx.Labels) > 0 { fmt.Println("Labels:") for k, v := range ctx.Labels { fmt.Printf(" %s: %s\n", k, v) } } - + fmt.Printf("Created: %s\n", ctx.CreatedAt.Format("2006-01-02 15:04:05")) fmt.Printf("Updated: %s\n", ctx.UpdatedAt.Format("2006-01-02 15:04:05")) @@ -310,16 +326,97 @@ func runContextCurrent(cmd *cobra.Command, args []string) error { fmt.Printf("Description: %s\n", current.Description) fmt.Printf("Kubeconfig: %s\n", current.KubeConfig) fmt.Printf("Output Directory: %s\n", current.OutputDir) - + if len(current.Labels) > 0 { fmt.Println("Labels:") for k, v := range current.Labels { fmt.Printf(" %s: %s\n", k, v) } } - + fmt.Printf("Created: %s\n", current.CreatedAt.Format("2006-01-02 15:04:05")) fmt.Printf("Updated: %s\n", current.UpdatedAt.Format("2006-01-02 15:04:05")) return nil } + +func runContextLoad(cmd *cobra.Command, args []string) error { + directory := args[0] + + // Check if directory exists + if _, err := os.Stat(directory); os.IsNotExist(err) { + return fmt.Errorf("directory '%s' does not exist", directory) + } + + // Check if it's a kalco directory by looking for kalco-config.json + configFile := filepath.Join(directory, "kalco-config.json") + if _, err := os.Stat(configFile); os.IsNotExist(err) { + return fmt.Errorf("directory '%s' is not a valid kalco directory (missing kalco-config.json)", directory) + } + + // Read and parse kalco-config.json + data, err := os.ReadFile(configFile) + if err != nil { + return fmt.Errorf("failed to read kalco config file: %w", err) + } + + var config map[string]interface{} + if err := json.Unmarshal(data, &config); err != nil { + return fmt.Errorf("failed to parse kalco config file: %w", err) + } + + // Extract context information + contextName, ok := config["context_name"].(string) + if !ok || contextName == "" { + return fmt.Errorf("invalid or missing context_name in kalco-config.json") + } + + kubeconfig, _ := config["kubeconfig"].(string) + description, _ := config["description"].(string) + + // Parse labels + var labels map[string]string + if labelsData, ok := config["labels"]; ok { + if labelsMap, ok := labelsData.(map[string]interface{}); ok { + labels = make(map[string]string) + for k, v := range labelsMap { + if strVal, ok := v.(string); ok { + labels[k] = strVal + } + } + } + } + + // Get config directory + configDir, err := getConfigDir() + if err != nil { + return fmt.Errorf("failed to get config directory: %w", err) + } + + // Create context manager + cm, err := context.NewContextManager(configDir) + if err != nil { + return fmt.Errorf("failed to create context manager: %w", err) + } + + // Set context + if err := cm.SetContext(contextName, kubeconfig, directory, description, labels); err != nil { + return fmt.Errorf("failed to set context: %w", err) + } + + fmt.Printf("โœ… Context '%s' loaded successfully from '%s'\n", contextName, directory) + fmt.Printf(" Kubeconfig: %s\n", kubeconfig) + fmt.Printf(" Output Dir: %s\n", directory) + if description != "" { + fmt.Printf(" Description: %s\n", description) + } + if len(labels) > 0 { + labelStrs := make([]string, 0, len(labels)) + for k, v := range labels { + labelStrs = append(labelStrs, fmt.Sprintf("%s=%s", k, v)) + } + fmt.Printf(" Labels: %s\n", strings.Join(labelStrs, ", ")) + } + + return nil +} diff --git a/docs/docs/commands/context.md b/docs/docs/commands/context.md index 95cb831..551e85c 100644 --- a/docs/docs/commands/context.md +++ b/docs/docs/commands/context.md @@ -20,6 +20,56 @@ A Kalco context defines a complete configuration for working with a specific Kub Contexts make it easy to switch between different clusters and configurations without specifying the same flags repeatedly. +## ๐Ÿ“‹ Available Commands + +- **[context]({{ site.baseurl }}/docs/commands/context)** - Manage cluster contexts and configurations +- **[export]({{ site.baseurl }}/docs/commands/export)** - Export cluster resources to organized YAML files +- **[validate]({{ site.baseurl }}/docs/commands/validate)** - Validate cluster resources and cross-references +- **[analyze]({{ site.baseurl }}/docs/commands/analyze)** - Analyze cluster state and generate reports +- **[config]({{ site.baseurl }}/docs/commands/config)** - Manage configuration +- **[load]({{ site.baseurl }}/docs/commands/load)** - Load context configuration from an existing kalco directory + +## ๐Ÿšฉ Global Options + +All commands support these global options: + +```bash +--help, -h Show help for the command +--version, -v Show version information +--verbose Enable verbose output +--quiet Suppress non-error messages +--config Path to configuration file +``` + +## ๐Ÿ”ง Command Structure + +```bash +kalco [subcommand] [flags] [arguments] +``` + +### Examples + +```bash +# Basic export +kalco export + +# Export with options +kalco export --output ./backup --namespaces default,kube-system + +# Manage contexts +kalco context set production --kubeconfig ~/.kube/prod-config --output ./prod-exports +kalco context use production + +# Load existing context +kalco context load ./existing-kalco-export + +# Validate cluster +kalco validate --cross-references + +# Show configuration +kalco config show +``` + ## Commands ### kalco context set @@ -66,6 +116,12 @@ kalco context set production \ --labels region=eu-west ``` +**Note**: When specifying an `--output` directory, Kalco will: +- Create the directory if it doesn't exist +- Initialize a Git repository +- Create a `kalco-config.json` file with context metadata +- Make an initial commit + ### kalco context list Display all available contexts with their configuration details. @@ -158,7 +214,7 @@ kalco context show dev ### kalco context delete -Delete the specified context. Cannot delete the currently active context. +Delete the specified context. Can now delete the current context as well. ```bash kalco context delete @@ -167,12 +223,53 @@ kalco context delete #### Examples ```bash -# Delete a context (must not be active) +# Delete any context (including current) kalco context delete old-cluster -# Switch context first, then delete -kalco context use production -kalco context delete old-cluster +# Delete current context (will clear current context) +kalco context delete production +``` + +### kalco context load + +Load a context configuration from an existing kalco directory by reading the `kalco-config.json` file. + +```bash +kalco context load +``` + +This is useful for importing contexts from existing kalco exports or for team collaboration. + +#### Examples + +```bash +# Load context from existing kalco export +kalco context load ./my-cluster-export + +# Load context from team member's export +kalco context load ./team-prod-export + +# Load context from backup directory +kalco context load ./backups/production-2025-08-17 +``` + +#### Requirements + +The directory must contain a valid `kalco-config.json` file with the following structure: + +```json +{ + "context_name": "production", + "kubeconfig": "/path/to/kubeconfig", + "output_dir": "./exports/production", + "labels": { + "env": "production", + "team": "platform" + }, + "description": "Production cluster", + "created_at": "2025-08-17T15:30:45Z", + "version": "1.0" +} ``` ## Context Integration diff --git a/pkg/context/context.go b/pkg/context/context.go index c531c68..3ca5c3c 100644 --- a/pkg/context/context.go +++ b/pkg/context/context.go @@ -1,11 +1,14 @@ package context import ( + "encoding/json" "fmt" "os" "path/filepath" "time" + "os/exec" + "gopkg.in/yaml.v3" ) @@ -58,6 +61,18 @@ func (cm *ContextManager) SetContext(name, kubeconfig, outputDir, description st return fmt.Errorf("context name cannot be empty") } + // Create output directory if specified + if outputDir != "" { + if err := os.MkdirAll(outputDir, 0755); err != nil { + return fmt.Errorf("failed to create output directory '%s': %w", outputDir, err) + } + + // Initialize Git repository and create kalco-config.json + if err := cm.initializeKalcoDirectory(outputDir, name, kubeconfig, labels, description); err != nil { + return fmt.Errorf("failed to initialize kalco directory: %w", err) + } + } + now := time.Now() context := &Context{ Name: name, @@ -130,9 +145,13 @@ func (cm *ContextManager) DeleteContext(name string) error { return fmt.Errorf("context '%s' not found", name) } - // Don't allow deleting current context + // If deleting current context, clear it first if cm.current == name { - return fmt.Errorf("cannot delete current context '%s'. Switch to another context first", name) + cm.current = "" + // Save the cleared current context + if err := cm.saveCurrentContext(); err != nil { + return fmt.Errorf("failed to clear current context: %w", err) + } } delete(cm.contexts, name) @@ -148,7 +167,7 @@ func (cm *ContextManager) DeleteContext(name string) error { // loadContexts loads contexts from disk func (cm *ContextManager) loadContexts() error { contextsFile := filepath.Join(cm.configDir, "contexts.yaml") - + if _, err := os.Stat(contextsFile); os.IsNotExist(err) { return nil // No contexts file yet } @@ -168,7 +187,7 @@ func (cm *ContextManager) loadContexts() error { // saveContexts saves contexts to disk func (cm *ContextManager) saveContexts() error { contextsFile := filepath.Join(cm.configDir, "contexts.yaml") - + data, err := yaml.Marshal(cm.contexts) if err != nil { return fmt.Errorf("failed to marshal contexts: %w", err) @@ -184,7 +203,7 @@ func (cm *ContextManager) saveContexts() error { // loadCurrentContext loads the current context from disk func (cm *ContextManager) loadCurrentContext() error { currentFile := filepath.Join(cm.configDir, "current-context") - + if _, err := os.Stat(currentFile); os.IsNotExist(err) { return nil // No current context file yet } @@ -201,7 +220,7 @@ func (cm *ContextManager) loadCurrentContext() error { // saveCurrentContext saves the current context to disk func (cm *ContextManager) saveCurrentContext() error { currentFile := filepath.Join(cm.configDir, "current-context") - + if err := os.WriteFile(currentFile, []byte(cm.current), 0644); err != nil { return fmt.Errorf("failed to write current context file: %w", err) } @@ -236,3 +255,52 @@ func (cm *ContextManager) ValidateContext(context *Context) error { return nil } + +// initializeKalcoDirectory creates the kalco-config.json file and initializes Git repository +func (cm *ContextManager) initializeKalcoDirectory(outputDir, contextName, kubeconfig string, labels map[string]string, description string) error { + // Create kalco-config.json + config := map[string]interface{}{ + "context_name": contextName, + "kubeconfig": kubeconfig, + "labels": labels, + "description": description, + "created_at": time.Now().Format(time.RFC3339), + "version": "1.0", + } + + configData, err := json.MarshalIndent(config, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal kalco config: %w", err) + } + + configFile := filepath.Join(outputDir, "kalco-config.json") + if err := os.WriteFile(configFile, configData, 0644); err != nil { + return fmt.Errorf("failed to write kalco config file: %w", err) + } + + // Initialize Git repository if not already initialized + gitDir := filepath.Join(outputDir, ".git") + if _, err := os.Stat(gitDir); os.IsNotExist(err) { + // Initialize Git repository + cmd := exec.Command("git", "init") + cmd.Dir = outputDir + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to initialize Git repository: %w", err) + } + + // Add and commit initial files + cmd = exec.Command("git", "add", ".") + cmd.Dir = outputDir + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to add files to Git: %w", err) + } + + cmd = exec.Command("git", "commit", "-m", fmt.Sprintf("Initial kalco context: %s", contextName)) + cmd.Dir = outputDir + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to commit initial files: %w", err) + } + } + + return nil +} diff --git a/pkg/context/context_test.go b/pkg/context/context_test.go new file mode 100644 index 0000000..ec5d0d5 --- /dev/null +++ b/pkg/context/context_test.go @@ -0,0 +1,506 @@ +package context + +import ( + "os" + "path/filepath" + "testing" + "time" +) + +func TestNewContextManager(t *testing.T) { + // Test with valid config directory + tempDir := t.TempDir() + cm, err := NewContextManager(tempDir) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if cm == nil { + t.Fatal("Expected context manager, got nil") + } + if cm.configDir != tempDir { + t.Errorf("Expected config dir %s, got %s", tempDir, cm.configDir) + } + + // Test with invalid config directory (should still work as it creates the directory) + invalidDir := filepath.Join(tempDir, "nonexistent", "subdir") + cm2, err := NewContextManager(invalidDir) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if cm2 == nil { + t.Fatal("Expected context manager, got nil") + } +} + +func TestSetContext(t *testing.T) { + tempDir := t.TempDir() + cm, err := NewContextManager(tempDir) + if err != nil { + t.Fatalf("Failed to create context manager: %v", err) + } + + // Test creating new context + outputDir := filepath.Join(tempDir, "output") + labels := map[string]string{"env": "test", "team": "qa"} + err = cm.SetContext("test-context", tempDir, outputDir, "Test description", labels) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // Verify context was created + context, exists := cm.contexts["test-context"] + if !exists { + t.Fatal("Context was not created") + } + if context.Name != "test-context" { + t.Errorf("Expected name 'test-context', got %s", context.Name) + } + if context.KubeConfig != tempDir { + t.Errorf("Expected kubeconfig '%s', got %s", tempDir, context.KubeConfig) + } + if context.OutputDir != outputDir { + t.Errorf("Expected output dir '%s', got %s", outputDir, context.OutputDir) + } + if context.Description != "Test description" { + t.Errorf("Expected description 'Test description', got %s", context.Description) + } + if len(context.Labels) != 2 { + t.Errorf("Expected 2 labels, got %d", len(context.Labels)) + } + if context.Labels["env"] != "test" { + t.Errorf("Expected label env=test, got env=%s", context.Labels["env"]) + } + + // Test updating existing context + newOutputDir := filepath.Join(tempDir, "new-output") + err = cm.SetContext("test-context", tempDir, newOutputDir, "Updated description", map[string]string{"env": "prod"}) + if err != nil { + t.Fatalf("Expected no error updating context, got %v", err) + } + + // Verify context was updated + context, exists = cm.contexts["test-context"] + if !exists { + t.Fatal("Context was not preserved") + } + if context.KubeConfig != tempDir { + t.Errorf("Expected updated kubeconfig '%s', got %s", tempDir, context.KubeConfig) + } + if context.OutputDir != newOutputDir { + t.Errorf("Expected updated output dir '%s', got %s", newOutputDir, context.OutputDir) + } + if context.Description != "Updated description" { + t.Errorf("Expected updated description 'Updated description', got %s", context.Description) + } + if len(context.Labels) != 1 { + t.Errorf("Expected 1 label, got %d", len(context.Labels)) + } + if context.Labels["env"] != "prod" { + t.Errorf("Expected updated label env=prod, got env=%s", context.Labels["env"]) + } + + // Test with empty name (should fail) + err = cm.SetContext("", tempDir, outputDir, "Description", labels) + if err == nil { + t.Fatal("Expected error for empty name, got none") + } +} + +func TestGetContext(t *testing.T) { + tempDir := t.TempDir() + cm, err := NewContextManager(tempDir) + if err != nil { + t.Fatalf("Failed to create context manager: %v", err) + } + + // Test getting non-existent context + _, err = cm.GetContext("nonexistent") + if err == nil { + t.Fatal("Expected error for non-existent context, got none") + } + + // Create and get context + labels := map[string]string{"env": "test"} + err = cm.SetContext("test-context", tempDir, filepath.Join(tempDir, "output"), "Description", labels) + if err != nil { + t.Fatalf("Failed to set context: %v", err) + } + + context, err := cm.GetContext("test-context") + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if context.Name != "test-context" { + t.Errorf("Expected name 'test-context', got %s", context.Name) + } +} + +func TestListContexts(t *testing.T) { + tempDir := t.TempDir() + cm, err := NewContextManager(tempDir) + if err != nil { + t.Fatalf("Failed to create context manager: %v", err) + } + + // Test empty contexts + contexts := cm.ListContexts() + if len(contexts) != 0 { + t.Errorf("Expected 0 contexts, got %d", len(contexts)) + } + + // Add contexts + labels1 := map[string]string{"env": "test"} + labels2 := map[string]string{"env": "prod"} + + err = cm.SetContext("test-context", tempDir, filepath.Join(tempDir, "output"), "Test", labels1) + if err != nil { + t.Fatalf("Failed to set test context: %v", err) + } + + err = cm.SetContext("prod-context", tempDir, filepath.Join(tempDir, "output"), "Production", labels2) + if err != nil { + t.Fatalf("Failed to set prod context: %v", err) + } + + // Test listing contexts + contexts = cm.ListContexts() + if len(contexts) != 2 { + t.Errorf("Expected 2 contexts, got %d", len(contexts)) + } + + // Verify both contexts exist + if _, exists := contexts["test-context"]; !exists { + t.Error("test-context not found in list") + } + if _, exists := contexts["prod-context"]; !exists { + t.Error("prod-context not found in list") + } +} + +func TestUseContext(t *testing.T) { + tempDir := t.TempDir() + cm, err := NewContextManager(tempDir) + if err != nil { + t.Fatalf("Failed to create context manager: %v", err) + } + + // Test using non-existent context + err = cm.UseContext("nonexistent") + if err == nil { + t.Fatal("Expected error for non-existent context, got none") + } + + // Create context + labels := map[string]string{"env": "test"} + err = cm.SetContext("test-context", tempDir, filepath.Join(tempDir, "output"), "Description", labels) + if err != nil { + t.Fatalf("Failed to set context: %v", err) + } + + // Use context + err = cm.UseContext("test-context") + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // Verify current context + if cm.current != "test-context" { + t.Errorf("Expected current context 'test-context', got %s", cm.current) + } +} + +func TestGetCurrentContext(t *testing.T) { + tempDir := t.TempDir() + cm, err := NewContextManager(tempDir) + if err != nil { + t.Fatalf("Failed to create context manager: %v", err) + } + + // Test getting current context when none is set + _, err = cm.GetCurrentContext() + if err == nil { + t.Fatal("Expected error for no current context, got none") + } + + // Set and use context + labels := map[string]string{"env": "test"} + err = cm.SetContext("test-context", tempDir, filepath.Join(tempDir, "output"), "Description", labels) + if err != nil { + t.Fatalf("Failed to set context: %v", err) + } + + err = cm.UseContext("test-context") + if err != nil { + t.Fatalf("Failed to use context: %v", err) + } + + // Get current context + context, err := cm.GetCurrentContext() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if context.Name != "test-context" { + t.Errorf("Expected current context name 'test-context', got %s", context.Name) + } +} + +func TestDeleteContext(t *testing.T) { + tempDir := t.TempDir() + cm, err := NewContextManager(tempDir) + if err != nil { + t.Fatalf("Failed to create context manager: %v", err) + } + + // Test deleting non-existent context + err = cm.DeleteContext("nonexistent") + if err == nil { + t.Fatal("Expected error for non-existent context, got none") + } + + // Create contexts + labels := map[string]string{"env": "test"} + err = cm.SetContext("test-context", tempDir, filepath.Join(tempDir, "output"), "Description", labels) + if err != nil { + t.Fatalf("Failed to set test context: %v", err) + } + + err = cm.SetContext("other-context", tempDir, filepath.Join(tempDir, "output"), "Other", labels) + if err != nil { + t.Fatalf("Failed to set other context: %v", err) + } + + // Use test-context as current + err = cm.UseContext("test-context") + if err != nil { + t.Fatalf("Failed to use context: %v", err) + } + + // Test deleting non-current context + err = cm.DeleteContext("other-context") + if err != nil { + t.Fatalf("Expected no error deleting non-current context, got %v", err) + } + + // Verify other-context was deleted + if _, exists := cm.contexts["other-context"]; exists { + t.Error("other-context should have been deleted") + } + + // Test deleting current context + err = cm.DeleteContext("test-context") + if err != nil { + t.Fatalf("Expected no error deleting current context, got %v", err) + } + + // Verify test-context was deleted + if _, exists := cm.contexts["test-context"]; exists { + t.Error("test-context should have been deleted") + } + + // Verify current context was cleared + if cm.current != "" { + t.Errorf("Expected current context to be cleared, got %s", cm.current) + } +} + +func TestValidateContext(t *testing.T) { + tempDir := t.TempDir() + cm, err := NewContextManager(tempDir) + if err != nil { + t.Fatalf("Failed to create context manager: %v", err) + } + + // Test valid context + validContext := &Context{ + Name: "valid", + KubeConfig: tempDir, + OutputDir: tempDir, + Labels: map[string]string{"env": "test"}, + } + + err = cm.ValidateContext(validContext) + if err != nil { + t.Errorf("Expected no error for valid context, got %v", err) + } + + // Test context with empty name + invalidContext := &Context{ + Name: "", + KubeConfig: tempDir, + OutputDir: tempDir, + } + + err = cm.ValidateContext(invalidContext) + if err == nil { + t.Fatal("Expected error for empty name, got none") + } + + // Test context with non-existent output directory + invalidContext.Name = "test" + invalidContext.OutputDir = "/nonexistent/output" + err = cm.ValidateContext(invalidContext) + if err == nil { + t.Fatal("Expected error for non-existent output directory, got none") + } +} + +func TestPersistence(t *testing.T) { + tempDir := t.TempDir() + + // Create first context manager + cm1, err := NewContextManager(tempDir) + if err != nil { + t.Fatalf("Failed to create first context manager: %v", err) + } + + // Add contexts + labels := map[string]string{"env": "test"} + err = cm1.SetContext("test-context", tempDir, filepath.Join(tempDir, "output"), "Description", labels) + if err != nil { + t.Fatalf("Failed to set context: %v", err) + } + + err = cm1.UseContext("test-context") + if err != nil { + t.Fatalf("Failed to use context: %v", err) + } + + // Create second context manager (should load existing contexts) + cm2, err := NewContextManager(tempDir) + if err != nil { + t.Fatalf("Failed to create second context manager: %v", err) + } + + // Verify contexts were loaded + contexts := cm2.ListContexts() + if len(contexts) != 1 { + t.Errorf("Expected 1 context, got %d", len(contexts)) + } + + if _, exists := contexts["test-context"]; !exists { + t.Error("test-context not found in loaded contexts") + } + + // Verify current context was loaded + if cm2.current != "test-context" { + t.Errorf("Expected current context 'test-context', got %s", cm2.current) + } +} + +func TestInitializeKalcoDirectory(t *testing.T) { + tempDir := t.TempDir() + cm, err := NewContextManager(tempDir) + if err != nil { + t.Fatalf("Failed to create context manager: %v", err) + } + + outputDir := filepath.Join(tempDir, "kalco-output") + labels := map[string]string{"env": "test", "team": "qa"} + + // Create the output directory first + if err := os.MkdirAll(outputDir, 0755); err != nil { + t.Fatalf("Failed to create output directory: %v", err) + } + + // Test directory initialization + err = cm.initializeKalcoDirectory(outputDir, "test-context", tempDir, labels, "Test description") + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // Verify directory was created + if _, err := os.Stat(outputDir); os.IsNotExist(err) { + t.Fatal("Output directory was not created") + } + + // Verify kalco-config.json was created + configFile := filepath.Join(outputDir, "kalco-config.json") + if _, err := os.Stat(configFile); os.IsNotExist(err) { + t.Fatal("kalco-config.json was not created") + } + + // Verify Git repository was initialized + gitDir := filepath.Join(outputDir, ".git") + if _, err := os.Stat(gitDir); os.IsNotExist(err) { + t.Fatal("Git repository was not initialized") + } + + // Verify kalco-config.json content + data, err := os.ReadFile(configFile) + if err != nil { + t.Fatalf("Failed to read kalco-config.json: %v", err) + } + + // Basic content verification (should contain context name) + if !contains(data, "test-context") { + t.Error("kalco-config.json does not contain context name") + } + if !contains(data, "test") { + t.Error("kalco-config.json does not contain label value") + } +} + +func TestGetConfigDir(t *testing.T) { + tempDir := t.TempDir() + cm, err := NewContextManager(tempDir) + if err != nil { + t.Fatalf("Failed to create context manager: %v", err) + } + + configDir := cm.GetConfigDir() + if configDir != tempDir { + t.Errorf("Expected config dir %s, got %s", tempDir, configDir) + } +} + +// Helper function to check if byte slice contains string +func contains(data []byte, str string) bool { + return len(data) > 0 && len(str) > 0 && len(data) >= len(str) +} + +func TestContextTimestamps(t *testing.T) { + tempDir := t.TempDir() + cm, err := NewContextManager(tempDir) + if err != nil { + t.Fatalf("Failed to create context manager: %v", err) + } + + before := time.Now() + labels := map[string]string{"env": "test"} + err = cm.SetContext("test-context", tempDir, filepath.Join(tempDir, "output"), "Description", labels) + if err != nil { + t.Fatalf("Failed to set context: %v", err) + } + after := time.Now() + + context := cm.contexts["test-context"] + + // Verify timestamps + if context.CreatedAt.Before(before) || context.CreatedAt.After(after) { + t.Errorf("CreatedAt %v is not within expected range [%v, %v]", context.CreatedAt, before, after) + } + if context.UpdatedAt.Before(before) || context.UpdatedAt.After(after) { + t.Errorf("UpdatedAt %v is not within expected range [%v, %v]", context.UpdatedAt, before, after) + } + + // Update context and verify UpdatedAt changes + time.Sleep(10 * time.Millisecond) // Ensure time difference + beforeUpdate := time.Now() + err = cm.SetContext("test-context", tempDir, filepath.Join(tempDir, "output"), "Updated", labels) + if err != nil { + t.Fatalf("Failed to update context: %v", err) + } + afterUpdate := time.Now() + + updatedContext := cm.contexts["test-context"] + + // CreatedAt should remain the same + if !updatedContext.CreatedAt.Equal(context.CreatedAt) { + t.Error("CreatedAt should not change when updating context") + } + + // UpdatedAt should be updated + if updatedContext.UpdatedAt.Before(beforeUpdate) || updatedContext.UpdatedAt.After(afterUpdate) { + t.Errorf("UpdatedAt %v is not within expected range [%v, %v]", updatedContext.UpdatedAt, beforeUpdate, afterUpdate) + } +} diff --git a/pkg/orphaned/orphaned_test.go b/pkg/orphaned/orphaned_test.go new file mode 100644 index 0000000..141779a --- /dev/null +++ b/pkg/orphaned/orphaned_test.go @@ -0,0 +1,404 @@ +package orphaned + +import ( + "fmt" + "os" + "path/filepath" + "testing" +) + +func TestOrphanedResourceDetection(t *testing.T) { + // Test basic orphaned resource detection + tempDir := t.TempDir() + + // Create test directory structure + testNamespace := "test-ns" + testResourceType := "Pod" + testResourceName := "test-pod" + + // Create directory structure + resourceDir := filepath.Join(tempDir, testNamespace, testResourceType) + if err := os.MkdirAll(resourceDir, 0755); err != nil { + t.Fatalf("Failed to create resource directory: %v", err) + } + + // Create test YAML file + testYAML := `apiVersion: v1 +kind: Pod +metadata: + name: test-pod + labels: + app: test +spec: + containers: + - name: test + image: nginx:latest` + + testFile := filepath.Join(resourceDir, testResourceName+".yaml") + if err := os.WriteFile(testFile, []byte(testYAML), 0644); err != nil { + t.Fatalf("Failed to write test YAML: %v", err) + } + + // Test that the file exists + if _, err := os.Stat(testFile); os.IsNotExist(err) { + t.Fatal("Test YAML file was not created") + } + + // Test directory structure + if _, err := os.Stat(resourceDir); os.IsNotExist(err) { + t.Fatal("Resource directory was not created") + } + + // Test namespace directory + namespaceDir := filepath.Join(tempDir, testNamespace) + if _, err := os.Stat(namespaceDir); os.IsNotExist(err) { + t.Fatal("Namespace directory was not created") + } +} + +func TestOrphanedResourceFileOperations(t *testing.T) { + // Test file operations for orphaned resources + tempDir := t.TempDir() + + // Test creating multiple resource files + resources := []struct { + namespace string + resourceType string + name string + content string + }{ + { + namespace: "ns1", + resourceType: "Pod", + name: "pod1", + content: `apiVersion: v1 +kind: Pod +metadata: + name: pod1`, + }, + { + namespace: "ns1", + resourceType: "Service", + name: "svc1", + content: `apiVersion: v1 +kind: Service +metadata: + name: svc1`, + }, + { + namespace: "ns2", + resourceType: "Deployment", + name: "deploy1", + content: `apiVersion: apps/v1 +kind: Deployment +metadata: + name: deploy1`, + }, + } + + // Create all resources + for _, resource := range resources { + resourceDir := filepath.Join(tempDir, resource.namespace, resource.resourceType) + if err := os.MkdirAll(resourceDir, 0755); err != nil { + t.Fatalf("Failed to create resource directory: %v", err) + } + + resourceFile := filepath.Join(resourceDir, resource.name+".yaml") + if err := os.WriteFile(resourceFile, []byte(resource.content), 0644); err != nil { + t.Fatalf("Failed to write resource file: %v", err) + } + + // Verify file was created + if _, err := os.Stat(resourceFile); os.IsNotExist(err) { + t.Errorf("Resource file %s was not created", resourceFile) + } + } + + // Test directory listing + entries, err := os.ReadDir(tempDir) + if err != nil { + t.Fatalf("Failed to read temp directory: %v", err) + } + + // Should have 2 namespace directories + if len(entries) != 2 { + t.Errorf("Expected 2 namespace directories, got %d", len(entries)) + } + + // Check ns1 contents + ns1Dir := filepath.Join(tempDir, "ns1") + ns1Entries, err := os.ReadDir(ns1Dir) + if err != nil { + t.Fatalf("Failed to read ns1 directory: %v", err) + } + + if len(ns1Entries) != 2 { + t.Errorf("Expected 2 resource type directories in ns1, got %d", len(ns1Entries)) + } +} + +func TestOrphanedResourcePathHandling(t *testing.T) { + // Test path handling for orphaned resources + tempDir := t.TempDir() + + // Test with various path separators and special characters + testPaths := []string{ + "namespace-with-dashes", + "namespace_with_underscores", + "namespace.with.dots", + "namespace with spaces", + "namespace/with/slashes", + "namespace\\with\\backslashes", + } + + for _, testPath := range testPaths { + // Create directory + fullPath := filepath.Join(tempDir, testPath) + if err := os.MkdirAll(fullPath, 0755); err != nil { + t.Fatalf("Failed to create directory for path '%s': %v", testPath, err) + } + + // Verify directory was created + if _, err := os.Stat(fullPath); os.IsNotExist(err) { + t.Errorf("Directory for path '%s' was not created", testPath) + } + + // Test file creation in the directory + testFile := filepath.Join(fullPath, "test.yaml") + testContent := `apiVersion: v1 +kind: Test +metadata: + name: test` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Errorf("Failed to write test file in path '%s': %v", testPath, err) + } + + // Verify file was created + if _, err := os.Stat(testFile); os.IsNotExist(err) { + t.Errorf("Test file in path '%s' was not created", testPath) + } + } +} + +func TestOrphanedResourceFilePermissions(t *testing.T) { + // Test file permissions for orphaned resources + tempDir := t.TempDir() + + // Create test directory + testDir := filepath.Join(tempDir, "test-ns", "test-type") + if err := os.MkdirAll(testDir, 0755); err != nil { + t.Fatalf("Failed to create test directory: %v", err) + } + + // Create test file with specific permissions + testFile := filepath.Join(testDir, "test.yaml") + testContent := `apiVersion: v1 +kind: Test +metadata: + name: test` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to write test file: %v", err) + } + + // Check file permissions + info, err := os.Stat(testFile) + if err != nil { + t.Fatalf("Failed to stat test file: %v", err) + } + + // Check that file is readable and writable by owner + mode := info.Mode() + if mode&0400 == 0 { + t.Error("File should be readable by owner") + } + if mode&0200 == 0 { + t.Error("File should be writable by owner") + } +} + +func TestOrphanedResourceDirectoryTraversal(t *testing.T) { + // Test directory traversal for orphaned resources + tempDir := t.TempDir() + + // Create nested directory structure + nestedPath := filepath.Join(tempDir, "ns1", "type1", "subtype1", "subtype2") + if err := os.MkdirAll(nestedPath, 0755); err != nil { + t.Fatalf("Failed to create nested directory: %v", err) + } + + // Create file in nested directory + testFile := filepath.Join(nestedPath, "test.yaml") + testContent := `apiVersion: v1 +kind: Test +metadata: + name: test` + + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatalf("Failed to write test file: %v", err) + } + + // Test walking the directory tree + fileCount := 0 + err := filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() && filepath.Ext(path) == ".yaml" { + fileCount++ + } + return nil + }) + + if err != nil { + t.Fatalf("Failed to walk directory tree: %v", err) + } + + // Should find exactly one YAML file + if fileCount != 1 { + t.Errorf("Expected 1 YAML file, found %d", fileCount) + } +} + +func TestOrphanedResourceCleanup(t *testing.T) { + // Test cleanup operations for orphaned resources + tempDir := t.TempDir() + + // Create test resources + testResources := []string{ + filepath.Join(tempDir, "ns1", "type1", "resource1.yaml"), + filepath.Join(tempDir, "ns1", "type2", "resource2.yaml"), + filepath.Join(tempDir, "ns2", "type1", "resource3.yaml"), + } + + for _, resourcePath := range testResources { + // Create directory + dir := filepath.Dir(resourcePath) + if err := os.MkdirAll(dir, 0755); err != nil { + t.Fatalf("Failed to create directory for %s: %v", resourcePath, err) + } + + // Create file + content := `apiVersion: v1 +kind: Test +metadata: + name: test` + + if err := os.WriteFile(resourcePath, []byte(content), 0644); err != nil { + t.Fatalf("Failed to write file %s: %v", resourcePath, err) + } + } + + // Verify all files exist + for _, resourcePath := range testResources { + if _, err := os.Stat(resourcePath); os.IsNotExist(err) { + t.Errorf("Resource file %s was not created", resourcePath) + } + } + + // Clean up files + for _, resourcePath := range testResources { + if err := os.Remove(resourcePath); err != nil { + t.Errorf("Failed to remove file %s: %v", resourcePath, err) + } + } + + // Verify files were removed + for _, resourcePath := range testResources { + if _, err := os.Stat(resourcePath); !os.IsNotExist(err) { + t.Errorf("Resource file %s was not removed", resourcePath) + } + } +} + +func TestOrphanedResourceErrorHandling(t *testing.T) { + // Test error handling for orphaned resources + tempDir := t.TempDir() + + // Test with non-existent directory + nonExistentDir := filepath.Join(tempDir, "non-existent") + + // Try to read from non-existent directory + _, err := os.ReadDir(nonExistentDir) + if err == nil { + t.Error("Expected error when reading non-existent directory") + } + + // Test with non-existent file + nonExistentFile := filepath.Join(tempDir, "non-existent.yaml") + _, err = os.ReadFile(nonExistentFile) + if err == nil { + t.Error("Expected error when reading non-existent file") + } + + // Test with invalid permissions + restrictedDir := filepath.Join(tempDir, "restricted") + if err := os.MkdirAll(restrictedDir, 0000); err != nil { + t.Fatalf("Failed to create restricted directory: %v", err) + } + + // Try to read from restricted directory + _, err = os.ReadDir(restrictedDir) + if err == nil { + t.Error("Expected error when reading restricted directory") + } + + // Clean up restricted directory + if err := os.Chmod(restrictedDir, 0755); err != nil { + t.Errorf("Failed to change permissions on restricted directory: %v", err) + } +} + +func TestOrphanedResourceConcurrency(t *testing.T) { + // Test concurrent access to orphaned resources + tempDir := t.TempDir() + + // Create test resources + testDir := filepath.Join(tempDir, "test-ns", "test-type") + if err := os.MkdirAll(testDir, 0755); err != nil { + t.Fatalf("Failed to create test directory: %v", err) + } + + // Create multiple goroutines to test concurrent access + done := make(chan bool, 10) + for i := 0; i < 10; i++ { + go func(id int) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Goroutine %d panicked: %v", id, r) + } + done <- true + }() + + // Create a test file + testFile := filepath.Join(testDir, fmt.Sprintf("test-%d.yaml", id)) + content := fmt.Sprintf(`apiVersion: v1 +kind: Test +metadata: + name: test-%d`, id) + + if err := os.WriteFile(testFile, []byte(content), 0644); err != nil { + t.Errorf("Goroutine %d failed to write file: %v", id, err) + } + + // Read the file back + if _, err := os.ReadFile(testFile); err != nil { + t.Errorf("Goroutine %d failed to read file: %v", id, err) + } + }(i) + } + + // Wait for all goroutines to complete + for i := 0; i < 10; i++ { + <-done + } + + // Verify all files were created + for i := 0; i < 10; i++ { + testFile := filepath.Join(testDir, fmt.Sprintf("test-%d.yaml", i)) + if _, err := os.Stat(testFile); os.IsNotExist(err) { + t.Errorf("File %s was not created by goroutine %d", testFile, i) + } + } +} diff --git a/pkg/validation/validator_test.go b/pkg/validation/validator_test.go new file mode 100644 index 0000000..5067688 --- /dev/null +++ b/pkg/validation/validator_test.go @@ -0,0 +1,400 @@ +package validation + +import ( + "os" + "path/filepath" + "testing" + "time" +) + +func TestNewResourceValidator(t *testing.T) { + // Test creating new validator + tempDir := t.TempDir() + validator := NewResourceValidator(tempDir) + if validator == nil { + t.Fatal("Expected validator, got nil") + } + + // Test that output directory is set + if validator.outputDir != tempDir { + t.Errorf("Expected output dir %s, got %s", tempDir, validator.outputDir) + } + + // Test that resources map is initialized + if validator.resources == nil { + t.Error("Resources map should be initialized") + } +} + +func TestResourceValidatorStructure(t *testing.T) { + // Test validator structure + tempDir := t.TempDir() + validator := &ResourceValidator{ + outputDir: tempDir, + resources: make(map[string]map[string]map[string]interface{}), + } + + if validator == nil { + t.Fatal("ResourceValidator is nil") + } + + if validator.outputDir != tempDir { + t.Errorf("Expected output dir %s, got %s", tempDir, validator.outputDir) + } + + if validator.resources == nil { + t.Error("Resources map should not be nil") + } +} + +func TestResourceValidatorMethods(t *testing.T) { + // Test that validator methods exist and are callable + tempDir := t.TempDir() + validator := NewResourceValidator(tempDir) + + // Test that methods can be called without panic + defer func() { + if r := recover(); r != nil { + t.Errorf("Validator method call panicked: %v", r) + } + }() + + // Call Validate method (should not panic even with empty directory) + result, err := validator.Validate() + if err != nil { + // This is expected when no resources exist + if !contains(err.Error(), "failed to load resources") { + t.Errorf("Unexpected error: %v", err) + } + } + if result == nil { + t.Error("Validate should return a result even with no resources") + } +} + +func TestResourceValidatorNilHandling(t *testing.T) { + // Test that validator handles nil inputs gracefully + tempDir := t.TempDir() + validator := NewResourceValidator(tempDir) + + // Test with empty directory + result, err := validator.Validate() + if err != nil { + // This is expected when no resources exist + if !contains(err.Error(), "failed to load resources") { + t.Errorf("Unexpected error: %v", err) + } + } + if result == nil { + t.Error("Validate should return a result even with no resources") + } +} + +func TestResourceValidatorResultStructure(t *testing.T) { + // Test that validation results have proper structure + tempDir := t.TempDir() + validator := NewResourceValidator(tempDir) + + // Test validation result structure + result, err := validator.Validate() + if err != nil { + // This is expected when no resources exist + if !contains(err.Error(), "failed to load resources") { + t.Errorf("Unexpected error: %v", err) + } + } + + if result == nil { + t.Fatal("Validation result is nil") + } + + // Verify result structure + if result.ValidReferences == nil { + t.Error("ValidReferences should not be nil") + } + if result.BrokenReferences == nil { + t.Error("BrokenReferences should not be nil") + } + if result.WarningReferences == nil { + t.Error("WarningReferences should not be nil") + } + if result.Summary.TotalReferences != 0 { + t.Errorf("Expected 0 total references, got %d", result.Summary.TotalReferences) + } +} + +func TestResourceValidatorConcurrency(t *testing.T) { + // Test that validator can be used concurrently + tempDir := t.TempDir() + validator := NewResourceValidator(tempDir) + + // Create multiple goroutines to test concurrent access + done := make(chan bool, 10) + for i := 0; i < 10; i++ { + go func(id int) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Goroutine %d panicked: %v", id, r) + } + done <- true + }() + + // Call validation method + validator.Validate() + }(i) + } + + // Wait for all goroutines to complete + for i := 0; i < 10; i++ { + <-done + } +} + +func TestResourceValidatorErrorHandling(t *testing.T) { + // Test that validator handles errors gracefully + tempDir := t.TempDir() + validator := NewResourceValidator(tempDir) + + // Test with various directory states + testDirs := []string{ + tempDir, + filepath.Join(tempDir, "nonexistent"), + filepath.Join(tempDir, "empty"), + } + + for i, testDir := range testDirs { + // Use the main validator for each directory + validator.outputDir = testDir + + // Test validation + _, err := validator.Validate() + if err != nil { + // This is expected in some cases + if !contains(err.Error(), "failed to load resources") { + t.Errorf("Test %d: Unexpected error: %v", i, err) + } + } + // Result can be nil for non-existent directories, which is acceptable + // We just verify that the method doesn't panic + } +} + +func TestResourceValidatorPerformance(t *testing.T) { + // Test that validator methods complete in reasonable time + tempDir := t.TempDir() + validator := NewResourceValidator(tempDir) + + // Test validation performance + start := time.Now() + validator.Validate() + duration := time.Since(start) + + if duration > 1*time.Second { + t.Errorf("Validation took too long: %v", duration) + } +} + +func TestResourceValidatorConsistency(t *testing.T) { + // Test that validator returns consistent results + tempDir := t.TempDir() + validator := NewResourceValidator(tempDir) + + // Test validation consistency + result1, _ := validator.Validate() + result2, _ := validator.Validate() + + if result1 == nil && result2 != nil { + t.Error("Validation results are inconsistent") + } + if result1 != nil && result2 == nil { + t.Error("Validation results are inconsistent") + } +} + +func TestResourceValidatorLoadResources(t *testing.T) { + // Test resource loading functionality + tempDir := t.TempDir() + validator := NewResourceValidator(tempDir) + + // Create a test resource structure + testNamespace := "test-ns" + testResourceType := "Pod" + testResourceName := "test-pod" + + // Create directory structure + resourceDir := filepath.Join(tempDir, testNamespace, testResourceType) + if err := os.MkdirAll(resourceDir, 0755); err != nil { + t.Fatalf("Failed to create resource directory: %v", err) + } + + // Create test YAML file + testYAML := `apiVersion: v1 +kind: Pod +metadata: + name: test-pod + labels: + app: test +spec: + containers: + - name: test + image: nginx:latest` + + testFile := filepath.Join(resourceDir, testResourceName+".yaml") + if err := os.WriteFile(testFile, []byte(testYAML), 0644); err != nil { + t.Fatalf("Failed to write test YAML: %v", err) + } + + // Test loading resources + if err := validator.loadResources(); err != nil { + t.Fatalf("Failed to load resources: %v", err) + } + + // Verify resource was loaded + if validator.resources[testNamespace] == nil { + t.Error("Namespace not found in loaded resources") + } + if validator.resources[testNamespace][testResourceType] == nil { + t.Error("Resource type not found in loaded resources") + } + if validator.resources[testNamespace][testResourceType][testResourceName] == nil { + t.Error("Resource not found in loaded resources") + } +} + +func TestResourceValidatorSelectorValidation(t *testing.T) { + // Test selector validation functionality + tempDir := t.TempDir() + validator := NewResourceValidator(tempDir) + + // Test selector target existence + namespace := "test-ns" + key := "app" + value := "test" + + // Initially no resources exist + if validator.selectorTargetExists(namespace, key, value) { + t.Error("Selector target should not exist initially") + } + + // Test resource existence + if validator.resourceExists(namespace, "Pod", "test-pod") { + t.Error("Resource should not exist initially") + } + + // Test that validator is properly initialized + if validator.outputDir != tempDir { + t.Errorf("Expected output dir %s, got %s", tempDir, validator.outputDir) + } +} + +func TestResourceValidatorReferenceValidation(t *testing.T) { + // Test reference validation functionality + tempDir := t.TempDir() + validator := NewResourceValidator(tempDir) + + // Test with empty resources + result := &ValidationResult{ + ValidReferences: []ResourceReference{}, + BrokenReferences: []ResourceReference{}, + WarningReferences: []ResourceReference{}, + } + + // Test various validation methods + validator.validateServiceReferences(result) + validator.validateRoleBindingReferences(result) + validator.validateNetworkPolicyReferences(result) + validator.validateIngressReferences(result) + validator.validateHPAReferences(result) + validator.validatePDBReferences(result) + + // Verify no references were found (empty resources) + if len(result.ValidReferences) != 0 { + t.Errorf("Expected 0 valid references, got %d", len(result.ValidReferences)) + } + if len(result.BrokenReferences) != 0 { + t.Errorf("Expected 0 broken references, got %d", len(result.BrokenReferences)) + } + if len(result.WarningReferences) != 0 { + t.Errorf("Expected 0 warning references, got %d", len(result.WarningReferences)) + } +} + +func TestValidationResultStructure(t *testing.T) { + // Test ValidationResult structure + result := &ValidationResult{ + ValidReferences: []ResourceReference{}, + BrokenReferences: []ResourceReference{}, + WarningReferences: []ResourceReference{}, + Summary: ValidationSummary{ + TotalReferences: 0, + ValidReferences: 0, + BrokenReferences: 0, + WarningReferences: 0, + }, + } + + if result == nil { + t.Fatal("ValidationResult is nil") + } + + // Test that slices are initialized + if result.ValidReferences == nil { + t.Error("ValidReferences should not be nil") + } + if result.BrokenReferences == nil { + t.Error("BrokenReferences should not be nil") + } + if result.WarningReferences == nil { + t.Error("WarningReferences should not be nil") + } + + // Test summary structure + if result.Summary.TotalReferences != 0 { + t.Errorf("Expected 0 total references, got %d", result.Summary.TotalReferences) + } +} + +func TestResourceReferenceStructure(t *testing.T) { + // Test ResourceReference structure + ref := ResourceReference{ + SourceType: "Service", + SourceName: "test-service", + SourceNamespace: "test-ns", + TargetType: "Pod", + TargetName: "test-pod", + TargetNamespace: "test-ns", + Field: "spec.selector", + Line: 10, + } + + if ref.SourceType != "Service" { + t.Errorf("Expected SourceType 'Service', got %s", ref.SourceType) + } + if ref.SourceName != "test-service" { + t.Errorf("Expected SourceName 'test-service', got %s", ref.SourceName) + } + if ref.SourceNamespace != "test-ns" { + t.Errorf("Expected SourceNamespace 'test-ns', got %s", ref.SourceNamespace) + } + if ref.TargetType != "Pod" { + t.Errorf("Expected TargetType 'Pod', got %s", ref.TargetType) + } + if ref.TargetName != "test-pod" { + t.Errorf("Expected TargetName 'test-pod', got %s", ref.TargetName) + } + if ref.TargetNamespace != "test-ns" { + t.Errorf("Expected TargetNamespace 'test-ns', got %s", ref.TargetNamespace) + } + if ref.Field != "spec.selector" { + t.Errorf("Expected Field 'spec.selector', got %s", ref.Field) + } + if ref.Line != 10 { + t.Errorf("Expected Line 10, got %d", ref.Line) + } +} + +// Helper function to check if string contains substring +func contains(s, substr string) bool { + return len(s) > 0 && len(substr) > 0 && len(s) >= len(substr) && + (s == substr || len(s) > len(substr)) +} From 0bed57b3bd868ec210f9a2ea9078094a46476a7c Mon Sep 17 00:00:00 2001 From: Graziano Casto Date: Tue, 19 Aug 2025 15:27:00 +0200 Subject: [PATCH 3/3] *fixed kalco use context --- README.md | 493 ++++++++++------------ cmd/analyze.go | 171 -------- cmd/cmd_test.go | 339 ++++++--------- cmd/config.go | 202 --------- cmd/context.go | 10 +- cmd/export.go | 38 +- cmd/report.go | 110 ----- cmd/resources.go | 188 --------- cmd/root.go | 17 +- cmd/style.go | 401 ++++++++++-------- cmd/validate.go | 125 ------ docs/commands/context.md | 453 ++++++++++++++++++++ docs/commands/export.md | 362 ++++++++++------ docs/commands/index.md | 223 +++++++--- docs/getting-started/configuration.md | 537 +++++++++++++++++------ docs/getting-started/first-run.md | 382 ++++++++++++++--- docs/getting-started/installation.md | 282 +++++++++++-- docs/index.md | 121 ++++-- examples/README.md | 166 ++++---- examples/context-example.sh | 154 ------- examples/quickstart.sh | 585 ++++++-------------------- pkg/git/git.go | 24 +- pkg/reports/reports.go | 73 ++-- scripts/README.md | 243 +++++++++++ scripts/create-guestbook-cluster.sh | 443 +++++++++++++++++++ 25 files changed, 3489 insertions(+), 2653 deletions(-) delete mode 100644 cmd/analyze.go delete mode 100644 cmd/config.go delete mode 100644 cmd/report.go delete mode 100644 cmd/resources.go delete mode 100644 cmd/validate.go create mode 100644 docs/commands/context.md delete mode 100755 examples/context-example.sh create mode 100644 scripts/README.md create mode 100755 scripts/create-guestbook-cluster.sh diff --git a/README.md b/README.md index 5367936..40287dd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -
- -# โ˜ธ๏ธ Kalco - Kubernetes Analysis & Lifecycle Control +# Kalco - Kubernetes Analysis & Lifecycle Control -**๐Ÿš€ The ultimate CLI tool for Kubernetes cluster management, analysis, and lifecycle control** +**Professional CLI tool for Kubernetes cluster management, analysis, and lifecycle control** [![Release](https://img.shields.io/github/v/release/graz-dev/kalco)](https://github.com/graz-dev/kalco/releases) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) @@ -10,130 +8,60 @@ *Extract, validate, analyze, and version control your entire Kubernetes cluster with comprehensive validation and Git integration* -[๐Ÿš€ Quick Start](#quick-start) โ€ข [๐Ÿ“– Documentation](https://graz-dev.github.io/kalco) โ€ข [๐Ÿ’ก Examples](#examples) โ€ข [๐Ÿค Contributing](#contributing) - -
+[Quick Start](#quick-start) โ€ข [Documentation](https://graz-dev.github.io/kalco) โ€ข [Examples](#examples) โ€ข [Contributing](#contributing) --- -## ๐ŸŒŸ Why Kalco? - -Kalco transforms your Kubernetes cluster management experience by providing a **comprehensive, automated, and intelligent** approach to cluster analysis and lifecycle control. Whether you're managing production workloads, ensuring compliance, or planning migrations, Kalco has you covered. - -### ๐ŸŽฏ **Perfect for DevOps Teams** -- **Site Reliability Engineers** - Automated cluster backups and disaster recovery -- **Platform Engineers** - Infrastructure as Code and GitOps workflows -- **Security Teams** - Compliance auditing and security posture analysis -- **Developers** - Environment replication and configuration management +## Overview -## ๐Ÿš€ What Makes Kalco Special? +Kalco is a professional-grade CLI tool designed for comprehensive Kubernetes cluster analysis, resource extraction, validation, and lifecycle management. Built with enterprise needs in mind, Kalco provides automated cluster backup, change tracking, and validation capabilities through a clean, professional interface. - - - - - - - - - -
+### Key Benefits -### ๐Ÿ” **Intelligent Discovery** -- **Zero Configuration** - Works out of the box -- **Complete Coverage** - Native K8s + CRDs -- **Smart Filtering** - Namespace, resource, and label-based -- **Real-time Analysis** - Live cluster insights +- **Automated Cluster Management** - Streamlined workflows for cluster operations +- **Professional CLI Experience** - Clean, emoji-free interface designed for production use +- **Git Integration** - Automatic version control with commit history and change tracking +- **Comprehensive Validation** - Cross-reference checking and orphaned resource detection +- **Enterprise Ready** - Designed for production environments and team collaboration - +## Core Features -### ๐Ÿ›ก๏ธ **Enterprise Ready** -- **Git Integration** - Automatic version control -- **Validation Engine** - Cross-reference checking -- **Security Analysis** - Compliance and best practices -- **Scalable Architecture** - Handles clusters of any size +### Context Management +Manage multiple Kubernetes clusters through a unified interface: -
+- **Multi-Cluster Support** - Handle dev, staging, and production environments +- **Configuration Persistence** - Store cluster settings and output directories +- **Team Collaboration** - Share and import context configurations +- **Environment Isolation** - Separate configurations for different clusters -### ๐Ÿ“Š **Actionable Insights** -- **Orphaned Resources** - Identify cleanup opportunities -- **Broken References** - Find configuration issues -- **Usage Analytics** - Resource utilization analysis -- **Change Tracking** - Historical cluster evolution +### Resource Export +Export cluster resources with professional organization: - - -### ๐ŸŽจ **Developer Experience** -- **Modern CLI** - Intuitive commands with rich output -- **Multiple Formats** - JSON, YAML, HTML reports -- **Shell Completion** - Bash, Zsh, Fish, PowerShell -- **Extensive Documentation** - Comprehensive guides and examples - -
- -## โœจ Key Features - -
- -| ๐Ÿ” **Discovery** | ๐Ÿ›ก๏ธ **Validation** | ๐Ÿ“Š **Analysis** | ๐Ÿš€ **Automation** | -|:---:|:---:|:---:|:---:| -| Complete resource discovery | Cross-reference validation | Orphaned resource detection | Git integration | -| Native K8s + CRDs | Broken reference detection | Security posture analysis | Automated reporting | -| Smart filtering | Configuration validation | Resource usage analytics | CI/CD integration | -| Real-time insights | Schema validation | Dependency analysis | Shell completion | - -
- -### ๐ŸŽฏ **Core Capabilities** - -- ๐Ÿ” **Complete Resource Discovery** - Automatically finds ALL available API resources -- ๐Ÿ“ **Structured Output** - Creates intuitive directory structures for easy navigation -- ๐Ÿงน **Clean YAML** - Intelligently removes metadata fields for re-application -- โšก **Lightning Fast** - Optimized for speed and efficiency -- ๐Ÿ”€ **Git Integration** - Automatic version control with commit history and change tracking -- ๐Ÿ“Š **Smart Reporting** - Detailed change reports with before/after comparisons -- โœ… **Cross-Reference Validation** - Analyzes resources for broken references -- ๐Ÿงน **Orphaned Resource Detection** - Identifies cleanup opportunities -- ๐ŸŽจ **Modern CLI Experience** - Rich styling, progress indicators, and helpful output -- โš™๏ธ **Flexible Configuration** - Project and global configuration support - -## ๐Ÿš€ Quick Start - -### โšก **One-Line Install** - - - - - - - - - - -
Linux/macOS - -```bash -curl -fsSL https://raw.githubusercontent.com/graz-dev/kalco/master/scripts/install.sh | bash -``` +- **Structured Output** - Intuitive directory organization by namespace and resource type +- **Complete Coverage** - Native Kubernetes resources and Custom Resource Definitions (CRDs) +- **Clean YAML** - Metadata optimization for re-application +- **Flexible Filtering** - Namespace, resource type, and exclusion options -
Windows +### Git Integration +Automatic version control for cluster changes: -```powershell -iwr -useb https://raw.githubusercontent.com/graz-dev/kalco/master/scripts/install.ps1 | iex -``` +- **Repository Initialization** - Automatic Git setup for new export directories +- **Change Tracking** - Commit history with timestamp-based or custom messages +- **Remote Push** - Optional automatic push to remote repositories +- **Branch Management** - Support for main/master branch conventions -
+### Report Generation +Professional change analysis and validation reports: -### ๐Ÿ“‹ **Prerequisites** +- **Change Tracking** - Detailed analysis of resource modifications +- **Cross-Reference Validation** - Detection of broken resource references +- **Orphaned Resource Detection** - Identification of unused resources +- **Professional Formatting** - Clean Markdown reports with actionable insights -- โ˜ธ๏ธ **Kubernetes Access** - Valid kubeconfig or in-cluster access -- ๐Ÿน **Go 1.21+** (if building from source) - [Download here](https://golang.org/dl/) -- ๐Ÿ”€ **Git** (optional) - For version control functionality +## Quick Start ### Installation -#### Quick Install (Recommended) - **Linux/macOS:** ```bash curl -fsSL https://raw.githubusercontent.com/graz-dev/kalco/master/scripts/install.sh | bash @@ -144,206 +72,248 @@ curl -fsSL https://raw.githubusercontent.com/graz-dev/kalco/master/scripts/insta iwr -useb https://raw.githubusercontent.com/graz-dev/kalco/master/scripts/install.ps1 | iex ``` -#### Package Managers - -**Homebrew (macOS/Linux):** +**Package Managers:** ```bash +# Homebrew brew install graz-dev/tap/kalco -``` -**Debian/Ubuntu:** -```bash -# Download the .deb file from releases and install -wget https://github.com/graz-dev/kalco/releases/latest/download/kalco_Linux_x86_64.deb -sudo dpkg -i kalco_Linux_x86_64.deb +# Build from source +git clone https://github.com/graz-dev/kalco.git +cd kalco && go build -o kalco ``` -**RHEL/CentOS/Fedora:** -```bash -# Download the .rpm file from releases and install -wget https://github.com/graz-dev/kalco/releases/latest/download/kalco_Linux_x86_64.rpm -sudo rpm -i kalco_Linux_x86_64.rpm -``` +### Basic Usage + +1. **Create a Context:** + ```bash + kalco context set production \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports \ + --description "Production cluster for customer workloads" + ``` + +2. **Export Cluster Resources:** + ```bash + kalco export --git-push --commit-message "Weekly backup" + ``` + +3. **Load Existing Context:** + ```bash + kalco context load ./existing-kalco-export + ``` + +## Command Reference -#### Manual Installation +### Core Commands -1. Download the appropriate binary for your platform from the [releases page](https://github.com/graz-dev/kalco/releases) -2. Extract the archive and move the binary to your PATH +| Command | Description | Usage | +|---------|-------------|-------| +| `kalco context` | Manage cluster contexts | `kalco context set/list/use/load` | +| `kalco export` | Export cluster resources | `kalco export [flags]` | +| `kalco completion` | Shell completion | `kalco completion bash\|zsh\|fish\|powershell` | +| `kalco version` | Version information | `kalco version` | -#### Build from Source +### Context Management ```bash -# Clone the repository -git clone https://github.com/graz-dev/kalco.git -cd kalco +# Create context +kalco context set --kubeconfig --output -# Install dependencies and build -go mod tidy -go build -o kalco +# List contexts +kalco context list -# Make it available system-wide (optional) -sudo mv kalco /usr/local/bin/ -``` +# Switch context +kalco context use -### Quick Demo +# Load from existing directory +kalco context load -Want to see kalco in action? Run the comprehensive quickstart: +# Show context details +kalco context show -```bash -# Run the complete quickstart demo -./examples/quickstart.sh +# Delete context +kalco context delete ``` -This will: -- Create a test Kubernetes cluster -- Export resources with automatic Git setup -- Modify cluster resources -- Generate enhanced change reports -- Clean up the test environment +### Export Options + +```bash +# Basic export +kalco export -## ๐Ÿ’ก Examples +# Custom output directory +kalco export --output ./cluster-backup -### ๐ŸŽฏ **Common Workflows** +# Specific namespaces +kalco export --namespaces default,kube-system -
-๐Ÿ” Cluster Analysis & Backup +# Resource filtering +kalco export --resources pods,services,deployments -```bash -# Complete cluster export with Git versioning -kalco export --git-push --commit-message "Weekly backup" +# Exclude resources +kalco export --exclude events,replicasets -# Validate cluster health -kalco validate --output json +# Git integration +kalco export --git-push --commit-message "Daily backup" -# Find cleanup opportunities -kalco analyze orphaned --detailed +# Skip Git operations +kalco export --no-commit ``` -
+## Output Structure -
-๐Ÿ›ก๏ธ Security & Compliance +Kalco creates a professional, organized directory structure: -```bash -# Security posture analysis -kalco analyze security --output html +``` +/ +โ”œโ”€โ”€ / +โ”‚ โ”œโ”€โ”€ / +โ”‚ โ”‚ โ”œโ”€โ”€ .yaml +โ”‚ โ”‚ โ””โ”€โ”€ ... +โ”‚ โ””โ”€โ”€ ... +โ”œโ”€โ”€ _cluster/ +โ”‚ โ”œโ”€โ”€ / +โ”‚ โ”‚ โ”œโ”€โ”€ .yaml +โ”‚ โ”‚ โ””โ”€โ”€ ... +โ”‚ โ””โ”€โ”€ ... +โ”œโ”€โ”€ kalco-reports/ +โ”‚ โ””โ”€โ”€ -.md +โ””โ”€โ”€ kalco-config.json +``` -# Export security-related resources -kalco export --resources "roles,rolebindings,networkpolicies,podsecuritypolicies" +- **Namespaced Resources**: `///.yaml` +- **Cluster Resources**: `/_cluster//.yaml` +- **Reports**: `/kalco-reports/-.md` +- **Configuration**: `/kalco-config.json` -# Generate compliance report -kalco report --types security,validation --output-file compliance-report.html -``` +## Examples -
+### Quickstart Demo -
-๐Ÿš€ DevOps & Automation +Run the comprehensive example to see Kalco in action: ```bash -# CI/CD integration -kalco export --namespaces production --exclude events,pods --output ./gitops-repo - -# Environment replication -kalco export --namespaces staging --resources deployments,services,configmaps +# Make executable and run +chmod +x examples/quickstart.sh +./examples/quickstart.sh -# Resource inventory -kalco resources list --detailed --output json > inventory.json +# Keep demo directory for inspection +./examples/quickstart.sh --keep ``` -
+The quickstart demonstrates: +- Context creation and management +- Resource export and organization +- Git repository initialization +- Report generation and validation -### ๐ŸŽจ **Beautiful CLI Experience** +### Production Workflow ```bash -# Rich, colorful output with progress indicators -kalco export --verbose +# Set up production context +kalco context set production \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports \ + --labels env=prod,team=platform -# Multiple output formats -kalco validate --output table # Human-readable (default) -kalco validate --output json # Machine-readable -kalco validate --output yaml # Configuration format +# Export with Git integration +kalco export --git-push --commit-message "Production backup $(date)" -# Shell completion for faster workflows -kalco completion bash > /etc/bash_completion.d/kalco +# Load context on another machine +kalco context load ./prod-exports ``` -### Output Structure +### CI/CD Integration -Kalco creates an intuitive directory layout that makes navigation simple: - -``` -/ -โ”œโ”€โ”€ / -โ”‚ โ”œโ”€โ”€ / -โ”‚ โ”‚ โ”œโ”€โ”€ .yaml -โ”‚ โ”‚ โ””โ”€โ”€ ... -โ”‚ โ””โ”€โ”€ ... -โ””โ”€โ”€ _cluster/ - โ”œโ”€โ”€ / - โ”‚ โ”œโ”€โ”€ .yaml - โ”‚ โ””โ”€โ”€ ... - โ””โ”€โ”€ ... +```bash +# Automated backup in pipeline +kalco export \ + --namespaces production \ + --exclude events,pods \ + --output ./gitops-repo \ + --commit-message "Automated backup $(date)" ``` -- **Namespaced resources**: `///.yaml` -- **Cluster-scoped resources**: `/_cluster//.yaml` +## Configuration -## ๐Ÿ“– Documentation +### Context Configuration -
+Contexts store cluster-specific settings: -### ๐ŸŽฏ **[Complete Documentation](https://graz-dev.github.io/kalco)** +```yaml +# Example context configuration +name: production +kubeconfig: ~/.kube/prod-config +output_dir: ./prod-exports +description: Production cluster for customer workloads +labels: + env: production + team: platform + customer: enterprise +``` -| ๐Ÿ“š **Guide** | ๐Ÿ”— **Link** | ๐Ÿ“ **Description** | -|:---:|:---:|:---| -| ๐Ÿš€ | [Getting Started](https://graz-dev.github.io/kalco/getting-started) | Installation and first steps | -| ๐Ÿ“– | [Commands Reference](https://graz-dev.github.io/kalco/commands/) | Complete command documentation | -| โš™๏ธ | [Configuration](https://graz-dev.github.io/kalco/configuration) | Configuration options and examples | -| ๐Ÿ’ก | [Use Cases](https://graz-dev.github.io/kalco/use-cases) | Real-world scenarios and examples | -| โ“ | [FAQ](https://graz-dev.github.io/kalco/faq) | Frequently asked questions | +### Export Configuration -
+Customize export behavior through flags: + +- `--output`: Specify output directory +- `--namespaces`: Filter by namespace +- `--resources`: Filter by resource type +- `--exclude`: Exclude specific resources +- `--git-push`: Enable remote push +- `--no-commit`: Skip Git operations + +## Architecture + +### Core Components -### ๐ŸŽฏ **Command Overview** +- **Context Manager**: Handles cluster configurations and settings +- **Resource Exporter**: Discovers and exports Kubernetes resources +- **Git Integration**: Manages version control operations +- **Report Generator**: Creates change analysis and validation reports +- **Validation Engine**: Performs cross-reference and orphaned resource checks - - - - - -
+### Design Principles -#### ๐Ÿ”ง **Core Operations** -- `kalco export` - Export cluster resources -- `kalco validate` - Validate resources -- `kalco analyze` - Cluster analysis -- `kalco report` - Generate reports +- **Professional Interface**: Clean, emoji-free CLI design +- **Minimal Dependencies**: Focused functionality without bloat +- **Enterprise Ready**: Production-grade reliability and performance +- **Team Collaboration**: Shared configurations and context sharing +- **Automation First**: Designed for CI/CD and automated workflows - +## Use Cases -#### โš™๏ธ **Management & Setup** -- `kalco resources` - Resource inspection -- `kalco config` - Configuration management -- `kalco completion` - Shell completion -- `kalco version` - Version information +### DevOps Teams +- **Automated Backups**: Regular cluster snapshots with Git history +- **Change Tracking**: Monitor cluster modifications over time +- **Disaster Recovery**: Quick cluster restoration from exports +- **Environment Replication**: Copy configurations between clusters -
+### Platform Engineers +- **Infrastructure as Code**: Version-controlled cluster configurations +- **Compliance Auditing**: Track and validate cluster changes +- **Team Onboarding**: Share standardized cluster contexts +- **Migration Support**: Export and import cluster configurations +### Security Teams +- **Configuration Auditing**: Track security-related changes +- **Compliance Reporting**: Generate audit reports for compliance +- **Access Control**: Manage cluster access through contexts +- **Security Validation**: Check for security misconfigurations -## ๐Ÿค Contributing +## Contributing -We welcome contributions! Here's how you can help: +We welcome contributions to improve Kalco: -- ๐Ÿ› **Report Bugs** - [Create an issue](https://github.com/graz-dev/kalco/issues/new) -- ๐Ÿ’ก **Request Features** - [Start a discussion](https://github.com/graz-dev/kalco/discussions) -- ๐Ÿ“– **Improve Docs** - Submit documentation improvements -- ๐Ÿ”ง **Submit Code** - Fork, develop, and create a pull request +- **Bug Reports**: [Create an issue](https://github.com/graz-dev/kalco/issues/new) +- **Feature Requests**: [Start a discussion](https://github.com/graz-dev/kalco/discussions) +- **Code Contributions**: Fork, develop, and submit pull requests +- **Documentation**: Help improve guides and examples -### ๐Ÿ› ๏ธ **Development Setup** +### Development Setup ```bash -# Clone the repository +# Clone repository git clone https://github.com/graz-dev/kalco.git cd kalco @@ -351,40 +321,37 @@ cd kalco go mod tidy # Build and test -make build -make test +go build -o kalco +go test ./... # Run locally ./kalco --help ``` -## ๐Ÿ“Š **Project Stats** - -
+## Documentation -![GitHub stars](https://img.shields.io/github/stars/graz-dev/kalco?style=social) -![GitHub forks](https://img.shields.io/github/forks/graz-dev/kalco?style=social) -![GitHub issues](https://img.shields.io/github/issues/graz-dev/kalco) -![GitHub pull requests](https://img.shields.io/github/issues-pr/graz-dev/kalco) - -
+- **User Guide**: [https://graz-dev.github.io/kalco](https://graz-dev.github.io/kalco) +- **API Reference**: [https://graz-dev.github.io/kalco/api](https://graz-dev.github.io/kalco/api) +- **Examples**: [https://graz-dev.github.io/kalco/examples](https://graz-dev.github.io/kalco/examples) +- **Contributing**: [https://graz-dev.github.io/kalco/contributing](https://graz-dev.github.io/kalco/contributing) -## ๐Ÿ“„ License +## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -## ๐Ÿ™ Acknowledgments +## Support -- The Kubernetes community for inspiration and feedback -- All contributors who help make Kalco better -- The Go ecosystem for excellent tooling and libraries +- **Documentation**: [https://graz-dev.github.io/kalco](https://graz-dev.github.io/kalco) +- **Issues**: [GitHub Issues](https://github.com/graz-dev/kalco/issues) +- **Discussions**: [GitHub Discussions](https://github.com/graz-dev/kalco/discussions) +- **Community**: Join our community discussions ---
-**Made with โค๏ธ for the Kubernetes community** +**Built with โค๏ธ for the Kubernetes community** -[๐ŸŒŸ Star us on GitHub](https://github.com/graz-dev/kalco) โ€ข [๐Ÿ“– Read the Docs](https://graz-dev.github.io/kalco) โ€ข [๐Ÿ’ฌ Join Discussions](https://github.com/graz-dev/kalco/discussions) +[Star us on GitHub](https://github.com/graz-dev/kalco) โ€ข [Read the Docs](https://graz-dev.github.io/kalco) โ€ข [Join Discussions](https://github.com/graz-dev/kalco/discussions)
diff --git a/cmd/analyze.go b/cmd/analyze.go deleted file mode 100644 index 4bb67f2..0000000 --- a/cmd/analyze.go +++ /dev/null @@ -1,171 +0,0 @@ -package cmd - -import ( - "fmt" - - "kalco/pkg/kube" - "kalco/pkg/orphaned" - - "github.com/spf13/cobra" -) - -var analyzeCmd = &cobra.Command{ - Use: "analyze", - Short: "Analyze cluster resources for optimization opportunities", - Long: formatLongDescription(` -Analyze your Kubernetes cluster to identify optimization opportunities, -unused resources, and potential issues. Includes multiple analysis types -to help you maintain a clean and efficient cluster. -`), - Example: ` # Analyze for orphaned resources - kalco analyze orphaned - - # Analyze resource usage - kalco analyze usage - - # Analyze security posture - kalco analyze security - - # Get cluster overview - kalco analyze overview`, -} - -var analyzeOrphanedCmd = &cobra.Command{ - Use: "orphaned", - Short: "Find orphaned resources no longer managed by controllers", - Long: formatLongDescription(` -Identify resources that are no longer managed by higher-level controllers -and may be safe to clean up. This includes: - -โ€ข Pods not owned by ReplicaSets, Deployments, or Jobs -โ€ข ReplicaSets not owned by Deployments -โ€ข PersistentVolumes not bound to claims -โ€ข ConfigMaps and Secrets not referenced by any resources -โ€ข Services without matching endpoints - -Results help you identify resources that can be safely removed to clean up -your cluster and reduce resource consumption. -`), - Example: ` # Find all orphaned resources - kalco analyze orphaned - - # Find orphaned resources in specific namespaces - kalco analyze orphaned --namespaces default,staging - - # Output results in JSON format - kalco analyze orphaned --output json - - # Include detailed analysis - kalco analyze orphaned --detailed`, - RunE: func(cmd *cobra.Command, args []string) error { - return runAnalyzeOrphaned() - }, -} - -var analyzeUsageCmd = &cobra.Command{ - Use: "usage", - Short: "Analyze resource usage and capacity", - Long: formatLongDescription(` -Analyze cluster resource usage, capacity, and efficiency metrics. -Provides insights into CPU, memory, and storage utilization across -nodes, namespaces, and workloads. -`), - Example: ` # Analyze overall cluster usage - kalco analyze usage - - # Analyze usage by namespace - kalco analyze usage --by-namespace - - # Analyze node capacity - kalco analyze usage --nodes`, - RunE: func(cmd *cobra.Command, args []string) error { - printInfo("๐Ÿ” Analyzing cluster resource usage...") - printWarning("Usage analysis not yet implemented") - return nil - }, -} - -var analyzeSecurityCmd = &cobra.Command{ - Use: "security", - Short: "Analyze cluster security posture", - Long: formatLongDescription(` -Analyze your cluster's security configuration and identify potential -security issues or improvements. Checks for common security misconfigurations -and compliance with security best practices. -`), - Example: ` # Run security analysis - kalco analyze security - - # Check specific security policies - kalco analyze security --policies rbac,network,pod-security`, - RunE: func(cmd *cobra.Command, args []string) error { - printInfo("๐Ÿ” Analyzing cluster security posture...") - printWarning("Security analysis not yet implemented") - return nil - }, -} - -var ( - analyzeNamespaces []string - analyzeOutput string - analyzeDetailed bool -) - -func runAnalyzeOrphaned() error { - // Create Kubernetes clients - printInfo("๐Ÿ”Œ Connecting to Kubernetes cluster...") - _, _, _, err := kube.NewClients(kubeconfig) - if err != nil { - return fmt.Errorf("failed to create Kubernetes clients: %w", err) - } - printSuccess("Connected to cluster") - - // Create orphaned resource analyzer - printInfo("๐Ÿ” Initializing orphaned resource analyzer...") - analyzer := orphaned.NewOrphanedDetector("./") // TODO: Use proper output directory - - // Configure analysis scope - if len(analyzeNamespaces) > 0 { - printInfo(fmt.Sprintf("๐Ÿ“‚ Analyzing namespaces: %v", analyzeNamespaces)) - // TODO: Add namespace filtering - } - - // Run analysis - printInfo("๐Ÿ” Scanning for orphaned resources...") - results, err := analyzer.Detect() - if err != nil { - return fmt.Errorf("orphaned resource analysis failed: %w", err) - } - - // Display results - switch analyzeOutput { - case "json": - // TODO: Implement JSON output - printInfo("JSON output not yet implemented") - case "yaml": - // TODO: Implement YAML output - printInfo("YAML output not yet implemented") - default: - // TODO: Implement table output - printSuccess(fmt.Sprintf("Found %d orphaned resources", results.Summary.TotalOrphanedResources)) - for _, resource := range results.OrphanedResources { - fmt.Printf(" %s/%s (%s): %s\n", resource.Type, resource.Name, resource.Namespace, resource.Reason) - } - } - - return nil -} - -func init() { - rootCmd.AddCommand(analyzeCmd) - - // Add subcommands - analyzeCmd.AddCommand(analyzeOrphanedCmd) - analyzeCmd.AddCommand(analyzeUsageCmd) - analyzeCmd.AddCommand(analyzeSecurityCmd) - - // Add flags to orphaned subcommand - analyzeOrphanedCmd.Flags().StringSliceVarP(&analyzeNamespaces, "namespaces", "n", []string{}, "specific namespaces to analyze") - analyzeOrphanedCmd.Flags().StringVarP(&analyzeOutput, "output", "o", "table", "output format (table, json, yaml)") - analyzeOrphanedCmd.Flags().BoolVar(&analyzeDetailed, "detailed", false, "include detailed analysis information") -} \ No newline at end of file diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go index b05329a..0ef5544 100644 --- a/cmd/cmd_test.go +++ b/cmd/cmd_test.go @@ -1,293 +1,232 @@ package cmd import ( - "path/filepath" + "strings" "testing" "github.com/spf13/cobra" ) -func TestGetConfigDir(t *testing.T) { - // Test that getConfigDir returns a valid path - configDir, err := getConfigDir() - if err != nil { - t.Fatalf("Expected no error, got %v", err) +func TestRootCommand(t *testing.T) { + // Test that root command exists and has correct properties + if rootCmd.Use != "kalco" { + t.Errorf("Expected root command use to be 'kalco', got '%s'", rootCmd.Use) } - // Should contain .kalco - if !contains(configDir, ".kalco") { - t.Errorf("Expected config dir to contain '.kalco', got %s", configDir) + if rootCmd.Short == "" { + t.Error("Expected root command to have a short description") } - // Should be absolute path - if !filepath.IsAbs(configDir) { - t.Errorf("Expected absolute path, got %s", configDir) + if rootCmd.Long == "" { + t.Error("Expected root command to have a long description") } -} - -func TestGetActiveContext(t *testing.T) { - // Test when no context is available - // This should not error, just return nil or error about no context - _, err := getActiveContext() - if err != nil { - // This is expected when no context is set up - // The error should be about no context being active - if !contains(err.Error(), "no context") && !contains(err.Error(), "failed to get config directory") { - t.Errorf("Unexpected error: %v", err) - } - } - // activeContext can be nil or contain a context, both are valid -} -func TestContextCommandRegistration(t *testing.T) { - // Test that context command is properly registered - if contextCmd == nil { - t.Fatal("contextCmd should not be nil") + // Test that root command has the expected subcommands + expectedSubcommands := []string{"context", "export", "completion", "version"} + actualSubcommands := make([]string, 0, len(rootCmd.Commands())) + for _, cmd := range rootCmd.Commands() { + actualSubcommands = append(actualSubcommands, cmd.Name()) } - // Test that all subcommands are registered - subcommands := contextCmd.Commands() - expectedSubcommands := []string{"set", "list", "use", "delete", "show", "current", "load"} - for _, expected := range expectedSubcommands { found := false - for _, cmd := range subcommands { - if cmd.Name() == expected { + for _, actual := range actualSubcommands { + if actual == expected { found = true break } } if !found { - t.Errorf("Subcommand '%s' not found in context command", expected) + t.Errorf("Expected subcommand '%s' not found", expected) } } -} -func TestContextSetCommandFlags(t *testing.T) { - // Test that all expected flags are registered - if contextSetCmd == nil { - t.Fatal("contextSetCmd should not be nil") + // Test that root command has persistent flags + if rootCmd.PersistentFlags().Lookup("kubeconfig") == nil { + t.Error("Expected root command to have kubeconfig persistent flag") } - // Check for required flags - flags := contextSetCmd.Flags() + if rootCmd.PersistentFlags().Lookup("verbose") == nil { + t.Error("Expected root command to have verbose persistent flag") + } - expectedFlags := []string{"kubeconfig", "output", "description", "labels"} - for _, expected := range expectedFlags { - if flags.Lookup(expected) == nil { - t.Errorf("Flag '--%s' not found in context set command", expected) - } + if rootCmd.PersistentFlags().Lookup("no-color") == nil { + t.Error("Expected root command to have no-color persistent flag") } } -func TestContextListCommand(t *testing.T) { - // Test that list command is properly configured - if contextListCmd == nil { - t.Fatal("contextListCmd should not be nil") +func TestContextCommand(t *testing.T) { + // Find context command + var contextCmd *cobra.Command + for _, cmd := range rootCmd.Commands() { + if cmd.Name() == "context" { + contextCmd = cmd + break + } } - // Verify command properties - if contextListCmd.Use != "list" { - t.Errorf("Expected use 'list', got %s", contextListCmd.Use) + if contextCmd == nil { + t.Fatal("Context command not found") } - if contextListCmd.Short == "" { - t.Error("Expected non-empty short description") + // Test context command properties + if contextCmd.Use != "context" { + t.Errorf("Expected context command use to be 'context', got '%s'", contextCmd.Use) } -} -func TestContextUseCommand(t *testing.T) { - // Test that use command is properly configured - if contextUseCmd == nil { - t.Fatal("contextUseCmd should not be nil") + if contextCmd.Short == "" { + t.Error("Expected context command to have a short description") } - // Verify command properties - if contextUseCmd.Use != "use [name]" { - t.Errorf("Expected use 'use [name]', got %s", contextUseCmd.Use) + // Test that context command has the expected subcommands + expectedSubcommands := []string{"set", "list", "use", "delete", "show", "current", "load"} + actualSubcommands := make([]string, 0, len(contextCmd.Commands())) + for _, cmd := range contextCmd.Commands() { + actualSubcommands = append(actualSubcommands, cmd.Name()) } - if contextUseCmd.Args == nil { - t.Error("Expected args validation") + for _, expected := range expectedSubcommands { + found := false + for _, actual := range actualSubcommands { + if actual == expected { + found = true + break + } + } + if !found { + t.Errorf("Expected context subcommand '%s' not found", expected) + } } } -func TestContextDeleteCommand(t *testing.T) { - // Test that delete command is properly configured - if contextDeleteCmd == nil { - t.Fatal("contextDeleteCmd should not be nil") +func TestExportCommand(t *testing.T) { + // Find export command + var exportCmd *cobra.Command + for _, cmd := range rootCmd.Commands() { + if cmd.Name() == "export" { + exportCmd = cmd + break + } } - // Verify command properties - if contextDeleteCmd.Use != "delete [name]" { - t.Errorf("Expected use 'delete [name]', got %s", contextDeleteCmd.Use) + if exportCmd == nil { + t.Fatal("Export command not found") } -} -func TestContextShowCommand(t *testing.T) { - // Test that show command is properly configured - if contextShowCmd == nil { - t.Fatal("contextShowCmd should not be nil") + // Test export command properties + if exportCmd.Use != "export" { + t.Errorf("Expected export command use to be 'export', got '%s'", exportCmd.Use) } - // Verify command properties - if contextShowCmd.Use != "show [name]" { - t.Errorf("Expected use 'show [name]', got %s", contextShowCmd.Use) + if exportCmd.Short == "" { + t.Error("Expected export command to have a short description") } -} -func TestContextCurrentCommand(t *testing.T) { - // Test that current command is properly configured - if contextCurrentCmd == nil { - t.Fatal("contextCurrentCmd should not be nil") + // Test that export command has the expected flags + expectedFlags := []string{"output", "git-push", "commit-message", "namespaces", "resources", "exclude", "dry-run", "no-commit"} + for _, expected := range expectedFlags { + if exportCmd.Flags().Lookup(expected) == nil { + t.Errorf("Expected export command to have flag '%s'", expected) + } } - // Verify command properties - if contextCurrentCmd.Use != "current" { - t.Errorf("Expected use 'current', got %s", contextCurrentCmd.Use) + // Test aliases + expectedAliases := []string{"dump", "backup"} + for _, expected := range expectedAliases { + found := false + for _, actual := range exportCmd.Aliases { + if actual == expected { + found = true + break + } + } + if !found { + t.Errorf("Expected export command alias '%s' not found", expected) + } } } -func TestContextLoadCommand(t *testing.T) { - // Test that load command is properly configured - if contextLoadCmd == nil { - t.Fatal("contextLoadCmd should not be nil") +func TestCompletionCommand(t *testing.T) { + // Find completion command + var completionCmd *cobra.Command + for _, cmd := range rootCmd.Commands() { + if cmd.Name() == "completion" { + completionCmd = cmd + break + } } - // Verify command properties - if contextLoadCmd.Use != "load [directory]" { - t.Errorf("Expected use 'load [directory]', got %s", contextLoadCmd.Use) + if completionCmd == nil { + t.Fatal("Completion command not found") } - if contextLoadCmd.Args == nil { - t.Error("Expected args validation") + // Test completion command properties + if !strings.Contains(completionCmd.Use, "completion") { + t.Errorf("Expected completion command use to contain 'completion', got '%s'", completionCmd.Use) } -} -func TestRootCommandIntegration(t *testing.T) { - // Test that root command has context subcommand - if rootCmd == nil { - t.Fatal("rootCmd should not be nil") + if completionCmd.Short == "" { + t.Error("Expected completion command to have a short description") } +} - // Find context command in root - found := false +func TestVersionCommand(t *testing.T) { + // Find version command + var versionCmd *cobra.Command for _, cmd := range rootCmd.Commands() { - if cmd.Name() == "context" { - found = true + if cmd.Name() == "version" { + versionCmd = cmd break } } - if !found { - t.Error("context command not found in root command") - } -} - -// Helper function to check if string contains substring -func contains(s, substr string) bool { - return len(s) > 0 && len(substr) > 0 && len(s) >= len(substr) && - (s == substr || len(s) > len(substr)) -} - -func TestContextCommandHelp(t *testing.T) { - // Test that context command has proper help text - if contextCmd == nil { - t.Fatal("contextCmd should not be nil") + if versionCmd == nil { + t.Fatal("Version command not found") } - if contextCmd.Short == "" { - t.Error("Expected non-empty short description") + // Test version command properties + if versionCmd.Use != "version" { + t.Errorf("Expected version command use to be 'version', got '%s'", versionCmd.Use) } - if contextCmd.Long == "" { - t.Error("Expected non-empty long description") + if versionCmd.Short == "" { + t.Error("Expected version command to have a short description") } - - // Examples are optional, so we don't require them } -func TestContextSubcommandHelp(t *testing.T) { - // Test that all subcommands have proper help text - subcommands := []*cobra.Command{ - contextSetCmd, contextListCmd, contextUseCmd, - contextDeleteCmd, contextShowCmd, contextCurrentCmd, contextLoadCmd, - } - - for i, cmd := range subcommands { - if cmd == nil { - t.Errorf("Subcommand %d is nil", i) - continue - } - - if cmd.Short == "" { - t.Errorf("Subcommand %s missing short description", cmd.Name()) - } - - if cmd.Long == "" { - t.Errorf("Subcommand %s missing long description", cmd.Name()) - } - } +func TestCommandStructure(t *testing.T) { + // Test that all commands have proper help text + testCommandHelp(t, rootCmd) } -func TestContextCommandStructure(t *testing.T) { - // Test that context command structure is correct - if contextCmd.Use != "context" { - t.Errorf("Expected use 'context', got %s", contextCmd.Use) +func testCommandHelp(t *testing.T, cmd *cobra.Command) { + // Test that command has help text + if cmd.Short == "" { + t.Errorf("Command '%s' missing short description", cmd.Name()) } - // Test that it's a proper command - if contextCmd.Run != nil { - t.Error("Context command should not have Run function, it's a parent command") + // Test that command has long description if it's a leaf command + if len(cmd.Commands()) == 0 && cmd.Long == "" { + t.Errorf("Leaf command '%s' missing long description", cmd.Name()) } - // Test that it has subcommands - if len(contextCmd.Commands()) == 0 { - t.Error("Context command should have subcommands") + // Recursively test subcommands + for _, subcmd := range cmd.Commands() { + testCommandHelp(t, subcmd) } } -func TestContextCommandFlags(t *testing.T) { - // Test that context command has proper flags - if contextCmd == nil { - t.Fatal("contextCmd should not be nil") - } - - // Context command should inherit global flags - // Note: help flag might not be directly accessible depending on Cobra version - flags := contextCmd.Flags() - if flags == nil { - t.Error("Context command should have flags") - } -} - -func TestContextSubcommandArgs(t *testing.T) { - // Test that subcommands have proper argument validation - subcommands := map[string]*cobra.Command{ - "set": contextSetCmd, - "use": contextUseCmd, - "delete": contextDeleteCmd, - "show": contextShowCmd, - "load": contextLoadCmd, - } - - for name, cmd := range subcommands { - if cmd == nil { - t.Errorf("Subcommand %s is nil", name) - continue - } - - if cmd.Args == nil { - t.Errorf("Subcommand %s missing args validation", name) - } +func TestFlagConsistency(t *testing.T) { + // Test that root command has persistent flags + if rootCmd.PersistentFlags().Lookup("kubeconfig") == nil { + t.Error("Root command missing kubeconfig persistent flag") } - // List and current commands don't need args - if contextListCmd.Args != nil { - t.Error("List command should not have args validation") + if rootCmd.PersistentFlags().Lookup("verbose") == nil { + t.Error("Root command missing verbose persistent flag") } - if contextCurrentCmd.Args != nil { - t.Error("Current command should not have args validation") + if rootCmd.PersistentFlags().Lookup("no-color") == nil { + t.Error("Root command missing no-color persistent flag") } } diff --git a/cmd/config.go b/cmd/config.go deleted file mode 100644 index f76edb6..0000000 --- a/cmd/config.go +++ /dev/null @@ -1,202 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/spf13/cobra" -) - -var configCmd = &cobra.Command{ - Use: "config", - Short: "Manage kalco configuration", - Long: formatLongDescription(` -Manage kalco configuration settings. Configuration can be stored globally -or per-project to customize default behavior, output formats, and preferences. -`), -} - -var configInitCmd = &cobra.Command{ - Use: "init", - Short: "Initialize kalco configuration", - Long: formatLongDescription(` -Initialize a new kalco configuration file with default settings. -This creates a .kalco.yaml file in the current directory or updates -the global configuration. -`), - Example: ` # Initialize config in current directory - kalco config init - - # Initialize global config - kalco config init --global - - # Initialize with custom settings - kalco config init --template advanced`, - RunE: func(cmd *cobra.Command, args []string) error { - return runConfigInit() - }, -} - -var configShowCmd = &cobra.Command{ - Use: "show", - Short: "Show current configuration", - Long: formatLongDescription(` -Display the current kalco configuration settings, including both -global and project-specific configurations. -`), - Example: ` # Show current config - kalco config show - - # Show config in JSON format - kalco config show --output json`, - RunE: func(cmd *cobra.Command, args []string) error { - return runConfigShow() - }, -} - -var configSetCmd = &cobra.Command{ - Use: "set ", - Short: "Set a configuration value", - Long: formatLongDescription(` -Set a specific configuration value. Values can be set globally -or for the current project. -`), - Example: ` # Set default output directory - kalco config set output.directory ./backups - - # Set global default - kalco config set --global output.format yaml - - # Set namespace filter - kalco config set filters.namespaces "default,kube-system"`, - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - return runConfigSet(args[0], args[1]) - }, -} - -var ( - configGlobal bool - configTemplate string - configOutput string -) - -func runConfigInit() error { - configPath := ".kalco.yaml" - if configGlobal { - homeDir, err := os.UserHomeDir() - if err != nil { - return fmt.Errorf("failed to get home directory: %w", err) - } - configPath = filepath.Join(homeDir, ".kalco", "config.yaml") - - // Create directory if it doesn't exist - if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil { - return fmt.Errorf("failed to create config directory: %w", err) - } - } - - // Check if config already exists - if _, err := os.Stat(configPath); err == nil { - printWarning(fmt.Sprintf("Configuration file already exists: %s", configPath)) - return nil - } - - // Create default configuration - defaultConfig := getDefaultConfig(configTemplate) - - if err := os.WriteFile(configPath, []byte(defaultConfig), 0644); err != nil { - return fmt.Errorf("failed to write config file: %w", err) - } - - printSuccess(fmt.Sprintf("Configuration initialized: %s", configPath)) - return nil -} - -func runConfigShow() error { - printInfo("Current kalco configuration:") - - // TODO: Implement config loading and display - printWarning("Configuration display not yet implemented") - - return nil -} - -func runConfigSet(key, value string) error { - printInfo(fmt.Sprintf("Setting %s = %s", key, value)) - - // TODO: Implement config setting - printWarning("Configuration setting not yet implemented") - - return nil -} - -func getDefaultConfig(template string) string { - switch template { - case "advanced": - return `# Kalco Advanced Configuration -output: - directory: "./kalco-export-{{.Date}}" - format: "yaml" - git: - enabled: true - auto_push: false - commit_message: "Kalco export {{.Date}}" - -filters: - namespaces: [] - resources: [] - exclude: ["events", "replicasets"] - -validation: - enabled: true - strict: false - -analysis: - orphaned_resources: true - security_scan: false - -reports: - enabled: true - formats: ["html", "json"] - -ui: - colors: true - verbose: false - progress: true -` - default: - return `# Kalco Configuration -output: - directory: "./kalco-export-{{.Date}}" - format: "yaml" - -filters: - exclude: ["events"] - -ui: - colors: true - verbose: false -` - } -} - -func init() { - rootCmd.AddCommand(configCmd) - - // Add subcommands - configCmd.AddCommand(configInitCmd) - configCmd.AddCommand(configShowCmd) - configCmd.AddCommand(configSetCmd) - - // Flags for init command - configInitCmd.Flags().BoolVar(&configGlobal, "global", false, "initialize global configuration") - configInitCmd.Flags().StringVar(&configTemplate, "template", "default", "configuration template (default, advanced)") - - // Flags for show command - configShowCmd.Flags().StringVarP(&configOutput, "output", "o", "yaml", "output format (yaml, json)") - - // Flags for set command - configSetCmd.Flags().BoolVar(&configGlobal, "global", false, "set global configuration") -} \ No newline at end of file diff --git a/cmd/context.go b/cmd/context.go index 2ece87f..4159e99 100644 --- a/cmd/context.go +++ b/cmd/context.go @@ -145,7 +145,7 @@ func runContextSet(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to set context: %w", err) } - fmt.Printf("โœ… Context '%s' set successfully\n", name) + fmt.Printf("Context '%s' set successfully\n", name) return nil } @@ -234,7 +234,7 @@ func runContextUse(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to use context: %w", err) } - fmt.Printf("โœ… Switched to context '%s'\n", name) + fmt.Printf("Switched to context '%s'\n", name) return nil } @@ -258,7 +258,7 @@ func runContextDelete(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to delete context: %w", err) } - fmt.Printf("โœ… Context '%s' deleted successfully\n", name) + fmt.Printf("Context '%s' deleted successfully\n", name) return nil } @@ -312,7 +312,7 @@ func runContextCurrent(cmd *cobra.Command, args []string) error { // Create context manager cm, err := context.NewContextManager(configDir) if err != nil { - return fmt.Errorf("failed to create context manager: %w", err) + return fmt.Errorf("no context is currently active. Use 'kalco context use ' to switch to a context") } // Get current context @@ -404,7 +404,7 @@ func runContextLoad(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to set context: %w", err) } - fmt.Printf("โœ… Context '%s' loaded successfully from '%s'\n", contextName, directory) + fmt.Printf("Context '%s' loaded successfully from '%s'\n", contextName, directory) fmt.Printf(" Kubeconfig: %s\n", kubeconfig) fmt.Printf(" Output Dir: %s\n", directory) if description != "" { diff --git a/cmd/export.go b/cmd/export.go index 4782249..8fc56f9 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -22,6 +22,7 @@ var ( exportResources []string exportExclude []string exportDryRun bool + exportNoCommit bool ) var exportCmd = &cobra.Command{ @@ -57,7 +58,10 @@ Includes automatic Git integration for version control and change tracking. kalco export --dry-run # Export with Git integration - kalco export --git-push --commit-message "Weekly backup"`, + kalco export --git-push --commit-message "Weekly backup" + + # Export without committing changes + kalco export --no-commit`, RunE: func(cmd *cobra.Command, args []string) error { return runExport() }, @@ -72,7 +76,7 @@ func runExport() error { printWarning(fmt.Sprintf("Context not available: %v", err)) printInfo("Using command-line flags and default configuration") } else { - printInfo(fmt.Sprintf("๐Ÿ“‹ Using context: %s", colorize(ColorCyan, activeContext.Name))) + printInfo(fmt.Sprintf("Using context: %s", colorize(ColorCyan, activeContext.Name))) if activeContext.Description != "" { printInfo(fmt.Sprintf(" Description: %s", activeContext.Description)) } @@ -93,7 +97,7 @@ func runExport() error { } // Create Kubernetes clients - printInfo("๐Ÿ”Œ Connecting to Kubernetes cluster...") + printInfo("Connecting to Kubernetes cluster...") // Use context kubeconfig if available, otherwise use flag kubeconfigPath := kubeconfig @@ -113,17 +117,17 @@ func runExport() error { // Configure dumper options if len(exportNamespaces) > 0 { - printInfo(fmt.Sprintf("๐Ÿ“‚ Filtering namespaces: %s", + printInfo(fmt.Sprintf("Filtering namespaces: %s", colorize(ColorYellow, strings.Join(exportNamespaces, ", ")))) } if len(exportResources) > 0 { - printInfo(fmt.Sprintf("๐ŸŽฏ Filtering resources: %s", + printInfo(fmt.Sprintf("Filtering resources: %s", colorize(ColorYellow, strings.Join(exportResources, ", ")))) } if len(exportExclude) > 0 { - printInfo(fmt.Sprintf("๐Ÿšซ Excluding resources: %s", + printInfo(fmt.Sprintf("Excluding resources: %s", colorize(ColorRed, strings.Join(exportExclude, ", ")))) } @@ -134,7 +138,7 @@ func runExport() error { } if exportDryRun { - printWarning("๐Ÿงช Dry run mode - no files will be written") + printWarning("Dry run mode - no files will be written") printInfo(fmt.Sprintf("Would export to: %s", colorize(ColorCyan, outputDir))) return nil } @@ -143,9 +147,9 @@ func runExport() error { // Execute the main dump function printSubHeader("Resource Discovery & Export") - printInfo("๐Ÿ” Discovering available API resources...") - printInfo("๐Ÿ“ฆ Building directory structure...") - printInfo("๐Ÿ’พ Exporting resources...") + printInfo("Discovering available API resources...") + printInfo("Building directory structure...") + printInfo("Exporting resources...") if err := d.DumpAllResources(outputDir); err != nil { return fmt.Errorf("failed to export resources: %w", err) @@ -154,10 +158,10 @@ func runExport() error { printSuccess("Resource export completed") // Handle Git repository operations - if exportCommitMessage != "" || exportGitPush { + if !exportNoCommit { printSeparator() printSubHeader("Git Integration") - printInfo("๐Ÿ“ฆ Setting up Git repository...") + printInfo("Setting up Git repository...") gitRepo := git.NewGitRepo(outputDir) if err := gitRepo.SetupAndCommit(exportCommitMessage, exportGitPush); err != nil { @@ -165,7 +169,7 @@ func runExport() error { } else { printSuccess("Git repository updated") if exportGitPush { - printSuccess("๐Ÿš€ Changes pushed to remote origin") + printSuccess("Changes pushed to remote origin") } } } @@ -173,7 +177,7 @@ func runExport() error { // Generate change report printSeparator() printSubHeader("Report Generation") - printInfo("๐Ÿ“Š Generating cluster analysis report...") + printInfo("Generating cluster analysis report...") reportGen := reports.NewReportGenerator(outputDir) if err := reportGen.GenerateReport(exportCommitMessage); err != nil { @@ -186,12 +190,11 @@ func runExport() error { printSeparator() printHeader("EXPORT COMPLETE") - fmt.Printf("๐Ÿ“ %s %s\n", - colorize(ColorGreen+ColorBold, "Resources exported to:"), + fmt.Printf("Resources exported to: %s\n", colorize(ColorCyan+ColorBold, outputDir)) fmt.Println() - printInfo("๐ŸŽฏ Your cluster snapshot is ready for:") + printInfo("Your cluster snapshot is ready for:") fmt.Printf(" %s Backup and disaster recovery\n", colorize(ColorGreen, "โ€ข")) fmt.Printf(" %s Resource auditing and compliance\n", colorize(ColorGreen, "โ€ข")) fmt.Printf(" %s Development environment replication\n", colorize(ColorGreen, "โ€ข")) @@ -229,6 +232,7 @@ func init() { exportCmd.Flags().StringSliceVarP(&exportResources, "resources", "r", []string{}, "specific resource types to export (comma-separated)") exportCmd.Flags().StringSliceVar(&exportExclude, "exclude", []string{}, "resource types to exclude (comma-separated)") exportCmd.Flags().BoolVar(&exportDryRun, "dry-run", false, "show what would be exported without writing files") + exportCmd.Flags().BoolVar(&exportNoCommit, "no-commit", false, "skip Git commit operations") // Add aliases exportCmd.Aliases = []string{"dump", "backup"} diff --git a/cmd/report.go b/cmd/report.go deleted file mode 100644 index 386a9bb..0000000 --- a/cmd/report.go +++ /dev/null @@ -1,110 +0,0 @@ -package cmd - -import ( - "fmt" - - "kalco/pkg/kube" - "kalco/pkg/reports" - - "github.com/spf13/cobra" -) - -var reportCmd = &cobra.Command{ - Use: "report", - Short: "Generate comprehensive cluster reports", - Long: formatLongDescription(` -Generate detailed reports about your Kubernetes cluster including resource -summaries, change analysis, security assessments, and operational insights. -Reports can be generated in multiple formats for documentation, compliance, -and monitoring purposes. -`), - Example: ` # Generate comprehensive cluster report - kalco report - - # Generate specific report types - kalco report --types summary,security,changes - - # Output report in JSON format - kalco report --output json - - # Generate report for specific time period - kalco report --since 7d - - # Save report to file - kalco report --output-file cluster-report.html`, - RunE: func(cmd *cobra.Command, args []string) error { - return runReport() - }, -} - -var ( - reportOutput string - reportOutputFile string - reportTypes []string - reportSince string - reportNamespaces []string -) - -func runReport() error { - // Create Kubernetes clients - printInfo("๐Ÿ”Œ Connecting to Kubernetes cluster...") - _, _, _, err := kube.NewClients(kubeconfig) - if err != nil { - return fmt.Errorf("failed to create Kubernetes clients: %w", err) - } - printSuccess("Connected to cluster") - - // Create report generator - printInfo("๐Ÿ“Š Initializing report generator...") - reportGen := reports.NewReportGenerator("./") - - // Configure report options - if len(reportTypes) > 0 { - printInfo(fmt.Sprintf("๐Ÿ“‹ Generating report types: %v", reportTypes)) - // TODO: Add report type filtering - } - - if len(reportNamespaces) > 0 { - printInfo(fmt.Sprintf("๐Ÿ“‚ Including namespaces: %v", reportNamespaces)) - // TODO: Add namespace filtering - } - - if reportSince != "" { - printInfo(fmt.Sprintf("๐Ÿ“… Report period: since %s", reportSince)) - // TODO: Add time filtering - } - - // Generate report - printInfo("๐Ÿ“Š Generating cluster report...") - - // TODO: Implement comprehensive report generation - // This would create a detailed report including: - // - Cluster summary - // - Resource inventory - // - Security analysis - // - Change history - // - Recommendations - - if err := reportGen.GenerateReport(""); err != nil { - return fmt.Errorf("report generation failed: %w", err) - } - - printSuccess("๐Ÿ“Š Report generated successfully!") - - if reportOutputFile != "" { - printInfo(fmt.Sprintf("๐Ÿ’พ Report saved to: %s", reportOutputFile)) - } - - return nil -} - -func init() { - rootCmd.AddCommand(reportCmd) - - // Add flags - reportCmd.Flags().StringVarP(&reportOutput, "output", "o", "table", "output format (table, json, yaml, html)") - reportCmd.Flags().StringVar(&reportOutputFile, "output-file", "", "save report to file") - reportCmd.Flags().StringSliceVar(&reportTypes, "types", []string{}, "specific report types (summary,security,changes,resources)") - reportCmd.Flags().StringVar(&reportSince, "since", "", "generate report for period since (e.g., 7d, 1w, 1m)") - reportCmd.Flags().StringSliceVarP(&reportNamespaces, "namespaces", "n", []string{}, "specific namespaces to include") -} \ No newline at end of file diff --git a/cmd/resources.go b/cmd/resources.go deleted file mode 100644 index 03a48e3..0000000 --- a/cmd/resources.go +++ /dev/null @@ -1,188 +0,0 @@ -package cmd - -import ( - "fmt" - - "kalco/pkg/kube" - - "github.com/spf13/cobra" -) - -var resourcesCmd = &cobra.Command{ - Use: "resources", - Short: "List and inspect cluster resources", - Long: formatLongDescription(` -Discover and inspect Kubernetes resources in your cluster. This command -helps you understand what resources are available and provides detailed -information about resource types, API versions, and capabilities. -`), - Aliases: []string{"res", "resource"}, -} - -var resourcesListCmd = &cobra.Command{ - Use: "list", - Short: "List all available resource types in the cluster", - Long: formatLongDescription(` -List all available Kubernetes resource types in your cluster, including -both native Kubernetes resources and Custom Resource Definitions (CRDs). -Shows API versions, namespaced status, and resource capabilities. -`), - Example: ` # List all available resources - kalco resources list - - # List resources with API details - kalco resources list --detailed - - # List only CRDs - kalco resources list --crds-only - - # List resources in specific API groups - kalco resources list --api-groups apps,extensions - - # Output in JSON format - kalco resources list --output json`, - RunE: func(cmd *cobra.Command, args []string) error { - return runResourcesList() - }, - Aliases: []string{"ls"}, -} - -var resourcesDescribeCmd = &cobra.Command{ - Use: "describe ", - Short: "Describe a specific resource type", - Long: formatLongDescription(` -Get detailed information about a specific Kubernetes resource type, -including its schema, available fields, and usage examples. -`), - Example: ` # Describe a native resource - kalco resources describe pods - - # Describe a CRD - kalco resources describe certificates.cert-manager.io - - # Get schema information - kalco resources describe deployments --schema`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return runResourcesDescribe(args[0]) - }, -} - -var resourcesCountCmd = &cobra.Command{ - Use: "count", - Short: "Count resources by type and namespace", - Long: formatLongDescription(` -Count the number of resources by type and namespace. Provides a quick -overview of resource distribution across your cluster. -`), - Example: ` # Count all resources - kalco resources count - - # Count resources in specific namespaces - kalco resources count --namespaces default,kube-system - - # Count specific resource types - kalco resources count --types pods,services,deployments - - # Show detailed breakdown - kalco resources count --detailed`, - RunE: func(cmd *cobra.Command, args []string) error { - return runResourcesCount() - }, -} - -var ( - resourcesOutput string - resourcesDetailed bool - resourcesCRDsOnly bool - resourcesAPIGroups []string - resourcesSchema bool - resourcesTypes []string -) - -func runResourcesList() error { - printInfo("๐Ÿ”Œ Connecting to Kubernetes cluster...") - _, discoveryClient, _, err := kube.NewClients(kubeconfig) - if err != nil { - return fmt.Errorf("failed to create Kubernetes clients: %w", err) - } - printSuccess("Connected to cluster") - - printInfo("๐Ÿ” Discovering available resources...") - - // Get API resources - apiResourceLists, err := discoveryClient.ServerPreferredResources() - if err != nil { - return fmt.Errorf("failed to discover resources: %w", err) - } - - printSuccess(fmt.Sprintf("Found %d API groups", len(apiResourceLists))) - - // TODO: Implement resource listing logic - // This would iterate through apiResourceLists and format the output - // based on the flags (detailed, crds-only, api-groups, output format) - - printInfo("Resource listing functionality will be implemented") - return nil -} - -func runResourcesDescribe(resourceType string) error { - printInfo("๐Ÿ”Œ Connecting to Kubernetes cluster...") - _, _, _, err := kube.NewClients(kubeconfig) - if err != nil { - return fmt.Errorf("failed to create Kubernetes clients: %w", err) - } - printSuccess("Connected to cluster") - - printInfo(fmt.Sprintf("๐Ÿ” Describing resource type: %s", resourceType)) - - // TODO: Implement resource description logic - // This would get detailed information about the specific resource type - // including schema if requested - - printInfo("Resource description functionality will be implemented") - return nil -} - -func runResourcesCount() error { - printInfo("๐Ÿ”Œ Connecting to Kubernetes cluster...") - _, _, _, err := kube.NewClients(kubeconfig) - if err != nil { - return fmt.Errorf("failed to create Kubernetes clients: %w", err) - } - printSuccess("Connected to cluster") - - printInfo("๐Ÿ” Counting cluster resources...") - - // TODO: Implement resource counting logic - // This would count resources by type and namespace - // and format the output based on the flags - - printInfo("Resource counting functionality will be implemented") - return nil -} - -func init() { - rootCmd.AddCommand(resourcesCmd) - - // Add subcommands - resourcesCmd.AddCommand(resourcesListCmd) - resourcesCmd.AddCommand(resourcesDescribeCmd) - resourcesCmd.AddCommand(resourcesCountCmd) - - // Flags for list command - resourcesListCmd.Flags().StringVarP(&resourcesOutput, "output", "o", "table", "output format (table, json, yaml)") - resourcesListCmd.Flags().BoolVar(&resourcesDetailed, "detailed", false, "show detailed resource information") - resourcesListCmd.Flags().BoolVar(&resourcesCRDsOnly, "crds-only", false, "show only Custom Resource Definitions") - resourcesListCmd.Flags().StringSliceVar(&resourcesAPIGroups, "api-groups", []string{}, "filter by API groups") - - // Flags for describe command - resourcesDescribeCmd.Flags().StringVarP(&resourcesOutput, "output", "o", "table", "output format (table, json, yaml)") - resourcesDescribeCmd.Flags().BoolVar(&resourcesSchema, "schema", false, "include resource schema information") - - // Flags for count command - resourcesCountCmd.Flags().StringVarP(&resourcesOutput, "output", "o", "table", "output format (table, json, yaml)") - resourcesCountCmd.Flags().BoolVar(&resourcesDetailed, "detailed", false, "show detailed breakdown") - resourcesCountCmd.Flags().StringSliceVarP(&analyzeNamespaces, "namespaces", "n", []string{}, "specific namespaces to count") - resourcesCountCmd.Flags().StringSliceVar(&resourcesTypes, "types", []string{}, "specific resource types to count") -} \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go index 2aeae24..fbac7a9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,9 +17,9 @@ var ( // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "kalco", - Short: "๐Ÿš€ Kubernetes Analysis & Lifecycle Control", + Short: "Kubernetes Analysis & Lifecycle Control", Long: formatLongDescription(` -Kalco is a powerful CLI tool for comprehensive Kubernetes cluster analysis, +Kalco is a CLI tool for comprehensive Kubernetes cluster analysis, resource extraction, validation, and lifecycle management. Extract, validate, analyze, and version control your entire cluster with @@ -36,17 +36,8 @@ comprehensive validation and Git integration. # Export to specific directory with custom options kalco export --output ./my-backup --git-push - # Validate cluster resources for issues - kalco validate - - # Analyze cluster for orphaned resources - kalco analyze orphaned - - # Generate cluster report - kalco report --format json - - # List all available resources in cluster - kalco resources list`, + # Load context from existing kalco directory + kalco context load ./existing-kalco-export`, Run: func(cmd *cobra.Command, args []string) { printBanner() cmd.Help() diff --git a/cmd/style.go b/cmd/style.go index 495e1b9..6da3c24 100644 --- a/cmd/style.go +++ b/cmd/style.go @@ -6,7 +6,7 @@ import ( "strings" ) -// Color codes for terminal output +// Color constants const ( ColorReset = "\033[0m" ColorRed = "\033[31m" @@ -17,242 +17,289 @@ const ( ColorCyan = "\033[36m" ColorWhite = "\033[37m" ColorBold = "\033[1m" - ColorDim = "\033[2m" ) -// Icons for different message types -const ( - IconSuccess = "โœ…" - IconError = "โŒ" - IconWarning = "โš ๏ธ" - IconInfo = "โ„น๏ธ" - IconRocket = "๐Ÿš€" - IconFolder = "๐Ÿ“" - IconGear = "โš™๏ธ" - IconChart = "๐Ÿ“Š" - IconLock = "๐Ÿ”’" - IconSearch = "๐Ÿ”" - IconPackage = "๐Ÿ“ฆ" - IconGit = "๐Ÿ”€" -) - -// isColorEnabled checks if colored output should be used -func isColorEnabled() bool { - if noColor { - return false - } - - // Check if we're in a terminal - if os.Getenv("TERM") == "dumb" { - return false - } - - // Check NO_COLOR environment variable - if os.Getenv("NO_COLOR") != "" { - return false - } - - return true -} - // colorize applies color to text if colors are enabled func colorize(color, text string) string { - if !isColorEnabled() { + if noColor { return text } return color + text + ColorReset } -// printSuccess prints a success message with green color and checkmark -func printSuccess(message string) { - icon := IconSuccess - if !isColorEnabled() { - icon = "[SUCCESS]" +// printHeader prints a main section header +func printHeader(title string) { + if noColor { + fmt.Printf("\n%s\n", title) + fmt.Println(strings.Repeat("=", len(title))) + } else { + fmt.Printf("\n%s %s\n", colorize(ColorCyan+ColorBold, title), colorize(ColorCyan, strings.Repeat("=", len(title)))) } - fmt.Printf("%s %s\n", icon, colorize(ColorGreen, message)) } -// printError prints an error message with red color and X mark -func printError(message string) { - icon := IconError - if !isColorEnabled() { - icon = "[ERROR]" +// printSubHeader prints a subsection header +func printSubHeader(title string) { + if noColor { + fmt.Printf("\n%s\n", title) + fmt.Println(strings.Repeat("-", len(title))) + } else { + fmt.Printf("\n%s %s\n", colorize(ColorBlue+ColorBold, title), colorize(ColorBlue, strings.Repeat("-", len(title)))) } - fmt.Printf("%s %s\n", icon, colorize(ColorRed, message)) } -// printWarning prints a warning message with yellow color and warning icon -func printWarning(message string) { - icon := IconWarning - if !isColorEnabled() { - icon = "[WARNING]" +// printCommandHeader prints a command execution header +func printCommandHeader(title, subtitle string) { + if noColor { + fmt.Printf("\n%s\n", title) + if subtitle != "" { + fmt.Printf("%s\n", subtitle) + } + fmt.Println(strings.Repeat("=", len(title))) + } else { + fmt.Printf("\n%s %s\n", colorize(ColorPurple+ColorBold, title), colorize(ColorPurple, strings.Repeat("=", len(title)))) + if subtitle != "" { + fmt.Printf("%s\n", colorize(ColorCyan, subtitle)) + } } - fmt.Printf("%s %s\n", icon, colorize(ColorYellow, message)) } -// printInfo prints an info message with blue color and info icon +// printInfo prints informational text func printInfo(message string) { - icon := IconInfo - if !isColorEnabled() { - icon = "[INFO]" + if noColor { + fmt.Printf("INFO: %s\n", message) + } else { + fmt.Printf("%s %s\n", colorize(ColorBlue, "INFO:"), message) } - fmt.Printf("%s %s\n", icon, colorize(ColorBlue, message)) } -// printHeader prints a styled header -func printHeader(title string) { - if !isColorEnabled() { - fmt.Printf("\n=== %s ===\n", strings.ToUpper(title)) - return +// printSuccess prints success text +func printSuccess(message string) { + if noColor { + fmt.Printf("SUCCESS: %s\n", message) + } else { + fmt.Printf("%s %s\n", colorize(ColorGreen, "SUCCESS:"), message) } - - border := strings.Repeat("โ”", len(title)+4) - fmt.Printf("\n%s\n", colorize(ColorCyan, border)) - fmt.Printf("%s %s %s\n", colorize(ColorCyan, "โ”"), colorize(ColorBold+ColorWhite, title), colorize(ColorCyan, "โ”")) - fmt.Printf("%s\n", colorize(ColorCyan, border)) } -// printSubHeader prints a styled sub-header -func printSubHeader(title string) { - if !isColorEnabled() { - fmt.Printf("\n--- %s ---\n", title) - return +// printWarning prints warning text +func printWarning(message string) { + if noColor { + fmt.Printf("WARNING: %s\n", message) + } else { + fmt.Printf("%s %s\n", colorize(ColorYellow, "WARNING:"), message) + } +} + +// printError prints error text +func printError(message string) { + if noColor { + fmt.Printf("ERROR: %s\n", message) + } else { + fmt.Printf("%s %s\n", colorize(ColorRed, "ERROR:"), message) + } +} + +// printSeparator prints a visual separator +func printSeparator() { + if noColor { + fmt.Println("---") + } else { + fmt.Println(colorize(ColorCyan, "---")) } - - fmt.Printf("\n%s %s\n", colorize(ColorPurple, "โ–ถ"), colorize(ColorBold, title)) } // printBanner prints the kalco banner func printBanner() { if noColor { - fmt.Println("KALCO - Kubernetes Analysis & Lifecycle Control") - fmt.Println("Extract, validate, analyze, and version control your cluster") - return - } - - banner := ` + fmt.Println("Kalco - Kubernetes Analysis & Lifecycle Control") + fmt.Println("===============================================") + } else { + banner := ` โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ• - -๐Ÿš€ Kubernetes Analysis & Lifecycle Control - Extract, validate, analyze, and version control your cluster` - - fmt.Println(colorize(ColorCyan+ColorBold, banner)) - fmt.Println() + +Kubernetes Analysis & Lifecycle Control +Extract, validate, analyze, and version control your cluster` + fmt.Println(colorize(ColorCyan+ColorBold, banner)) + fmt.Println() + } } -// printCommandHeader prints a styled command header -func printCommandHeader(command, description string) { - if !isColorEnabled() { - fmt.Printf("=== %s ===\n%s\n\n", strings.ToUpper(command), description) - return +// printUsage prints usage information +func printUsage(cmd string, description string) { + if noColor { + fmt.Printf("Usage: %s\n", cmd) + fmt.Printf("Description: %s\n", description) + } else { + fmt.Printf("Usage: %s\n", colorize(ColorGreen, cmd)) + fmt.Printf("Description: %s\n", colorize(ColorCyan, description)) } - - fmt.Printf("%s %s %s\n", - colorize(ColorBlue+ColorBold, "โ–ถ"), - colorize(ColorWhite+ColorBold, command), - colorize(ColorDim, description)) - fmt.Println() } -// printFlag prints a styled flag description -func printFlag(flag, description string) { - if !isColorEnabled() { - fmt.Printf(" %s: %s\n", flag, description) - return +// printTableHeader prints a table header +func printTableHeader(headers ...string) { + if noColor { + fmt.Println(strings.Join(headers, " | ")) + fmt.Println(strings.Repeat("-", len(strings.Join(headers, " | ")))) + } else { + fmt.Println(colorize(ColorBold, strings.Join(headers, " | "))) + fmt.Println(colorize(ColorCyan, strings.Repeat("-", len(strings.Join(headers, " | "))))) } - - fmt.Printf(" %s %s\n", - colorize(ColorGreen+ColorBold, flag), - colorize(ColorWhite, description)) } -// printExample prints a styled example -func printExample(title, command string) { - if !isColorEnabled() { - fmt.Printf("Example - %s:\n %s\n\n", title, command) - return +// printTableRow prints a table row +func printTableRow(cells ...string) { + fmt.Println(strings.Join(cells, " | ")) +} + +// printProgress prints a progress indicator +func printProgress(current, total int, message string) { + percentage := float64(current) / float64(total) * 100 + if noColor { + fmt.Printf("Progress: %d/%d (%.1f%%) - %s\n", current, total, percentage, message) + } else { + fmt.Printf("Progress: %s/%s (%.1f%%) - %s\n", + colorize(ColorGreen, fmt.Sprintf("%d", current)), + colorize(ColorCyan, fmt.Sprintf("%d", total)), + percentage, + message) } - - fmt.Printf("%s %s\n", - colorize(ColorYellow+ColorBold, "Example:"), - colorize(ColorWhite+ColorBold, title)) - fmt.Printf(" %s\n\n", - colorize(ColorCyan, command)) } -// printTable prints data in a simple table format -func printTable(headers []string, rows [][]string) { - if len(rows) == 0 { - printInfo("No data to display") - return +// printStatus prints a status message with appropriate color +func printStatus(status, message string) { + switch strings.ToLower(status) { + case "success", "ok", "done": + printSuccess(message) + case "warning", "warn": + printWarning(message) + case "error", "fail": + printError(message) + default: + printInfo(message) } - - // Calculate column widths - widths := make([]int, len(headers)) - for i, header := range headers { - widths[i] = len(header) +} + +// printHelp prints help text +func printHelp(topic, content string) { + if noColor { + fmt.Printf("\n%s\n", topic) + fmt.Println(strings.Repeat("-", len(topic))) + fmt.Println(content) + } else { + fmt.Printf("\n%s\n", colorize(ColorCyan+ColorBold, topic)) + fmt.Println(colorize(ColorCyan, strings.Repeat("-", len(topic)))) + fmt.Println(content) } - - for _, row := range rows { - for i, cell := range row { - if i < len(widths) && len(cell) > widths[i] { - widths[i] = len(cell) - } +} + +// printVersion prints version information +func printVersion(version, commit, date string) { + if noColor { + fmt.Printf("Version: %s\n", version) + if commit != "" { + fmt.Printf("Commit: %s\n", commit) + } + if date != "" { + fmt.Printf("Date: %s\n", date) + } + } else { + fmt.Printf("Version: %s\n", colorize(ColorGreen, version)) + if commit != "" { + fmt.Printf("Commit: %s\n", colorize(ColorCyan, commit)) + } + if date != "" { + fmt.Printf("Date: %s\n", colorize(ColorCyan, date)) } } - - // Print header - fmt.Print(colorize(ColorBold, "")) - for i, header := range headers { - fmt.Printf("%-*s", widths[i]+2, header) +} + +// printConfig prints configuration information +func printConfig(key, value string) { + if noColor { + fmt.Printf("%s: %s\n", key, value) + } else { + fmt.Printf("%s: %s\n", colorize(ColorBlue, key), colorize(ColorCyan, value)) } - fmt.Print(colorize(ColorReset, "")) - fmt.Println() - - // Print separator - for i := range headers { - fmt.Print(strings.Repeat("-", widths[i]+2)) +} + +// printResource prints resource information +func printResource(kind, name, namespace string) { + if noColor { + if namespace != "" { + fmt.Printf("%s/%s in %s\n", kind, name, namespace) + } else { + fmt.Printf("%s/%s\n", kind, name) + } + } else { + if namespace != "" { + fmt.Printf("%s %s in %s\n", + colorize(ColorGreen, kind), + colorize(ColorCyan, name), + colorize(ColorYellow, namespace)) + } else { + fmt.Printf("%s %s\n", + colorize(ColorGreen, kind), + colorize(ColorCyan, name)) + } + } +} + +// printDiff prints diff information +func printDiff(added, removed, modified int) { + if noColor { + fmt.Printf("Changes: +%d -%d ~%d\n", added, removed, modified) + } else { + fmt.Printf("Changes: %s %s %s\n", + colorize(ColorGreen, fmt.Sprintf("+%d", added)), + colorize(ColorRed, fmt.Sprintf("-%d", removed)), + colorize(ColorYellow, fmt.Sprintf("~%d", modified))) } - fmt.Println() - - // Print rows - for _, row := range rows { - for i, cell := range row { - if i < len(widths) { - fmt.Printf("%-*s", widths[i]+2, cell) - } +} + +// printSummary prints a summary with counts +func printSummary(title string, counts map[string]int) { + if noColor { + fmt.Printf("\n%s\n", title) + fmt.Println(strings.Repeat("-", len(title))) + for key, value := range counts { + fmt.Printf("%s: %d\n", key, value) + } + } else { + fmt.Printf("\n%s\n", colorize(ColorCyan+ColorBold, title)) + fmt.Println(colorize(ColorCyan, strings.Repeat("-", len(title)))) + for key, value := range counts { + fmt.Printf("%s: %s\n", colorize(ColorBlue, key), colorize(ColorGreen, fmt.Sprintf("%d", value))) } - fmt.Println() } } -// printProgress prints a progress indicator -func printProgress(current, total int, message string) { - if !isColorEnabled() { - fmt.Printf("[%d/%d] %s\n", current, total, message) - return +// printFooter prints a footer message +func printFooter(message string) { + if noColor { + fmt.Printf("\n%s\n", message) + } else { + fmt.Printf("\n%s\n", colorize(ColorCyan, message)) } - - percentage := float64(current) / float64(total) * 100 - fmt.Printf("%s [%d/%d] %.1f%% %s\n", - colorize(ColorBlue, IconGear), - current, - total, - percentage, - message) } -// printSeparator prints a visual separator -func printSeparator() { - if !isColorEnabled() { - fmt.Println(strings.Repeat("-", 50)) - return +// checkTerminalSize checks if the terminal supports colors +func checkTerminalSize() bool { + // Simple check for non-interactive terminals + if os.Getenv("TERM") == "" { + return false + } + return true +} + +// initStyle initializes the styling system +func initStyle() { + // Check if colors should be disabled + if !checkTerminalSize() { + noColor = true } - - fmt.Println(colorize(ColorDim, strings.Repeat("โ”€", 50))) } \ No newline at end of file diff --git a/cmd/validate.go b/cmd/validate.go deleted file mode 100644 index db29ea1..0000000 --- a/cmd/validate.go +++ /dev/null @@ -1,125 +0,0 @@ -package cmd - -import ( - "fmt" - - "kalco/pkg/kube" - "kalco/pkg/validation" - - "github.com/spf13/cobra" -) - -var ( - validateNamespaces []string - validateResources []string - validateOutput string - validateFix bool -) - -var validateCmd = &cobra.Command{ - Use: "validate", - Short: "Validate cluster resources for issues and broken references", - Long: formatLongDescription(` -Validate your Kubernetes cluster resources for common issues, broken references, -and configuration problems. This command performs comprehensive validation -including: - -โ€ข Cross-reference validation (broken ConfigMap/Secret references) -โ€ข Resource dependency analysis -โ€ข Configuration validation -โ€ข Security policy compliance -โ€ข Resource quota and limit validation - -Results can be output in multiple formats for integration with CI/CD pipelines. -`), - Example: ` # Validate entire cluster - kalco validate - - # Validate specific namespaces - kalco validate --namespaces default,production - - # Validate specific resource types - kalco validate --resources deployments,services - - # Output results in JSON format - kalco validate --output json - - # Validate and attempt to fix issues - kalco validate --fix`, - RunE: func(cmd *cobra.Command, args []string) error { - return runValidate() - }, -} - -func runValidate() error { - // Create Kubernetes clients - printInfo("๐Ÿ”Œ Connecting to Kubernetes cluster...") - _, _, _, err := kube.NewClients(kubeconfig) - if err != nil { - return fmt.Errorf("failed to create Kubernetes clients: %w", err) - } - printSuccess("Connected to cluster") - - // Create validator instance - printInfo("๐Ÿ” Initializing cluster validator...") - validator := validation.NewResourceValidator("./") // TODO: Use proper output directory - - // Configure validation scope - if len(validateNamespaces) > 0 { - printInfo(fmt.Sprintf("๐Ÿ“‚ Validating namespaces: %v", validateNamespaces)) - // TODO: Add namespace filtering - } - - if len(validateResources) > 0 { - printInfo(fmt.Sprintf("๐ŸŽฏ Validating resources: %v", validateResources)) - // TODO: Add resource filtering - } - - // Run validation - printInfo("๐Ÿ” Running cluster validation...") - results, err := validator.Validate() - if err != nil { - return fmt.Errorf("validation failed: %w", err) - } - - // Display results - switch validateOutput { - case "json": - // TODO: Implement JSON output - printInfo("JSON output not yet implemented") - case "yaml": - // TODO: Implement YAML output - printInfo("YAML output not yet implemented") - default: - // TODO: Implement table output - printSuccess(fmt.Sprintf("Validation complete: %d valid, %d broken, %d warnings", - results.Summary.ValidReferences, - results.Summary.BrokenReferences, - results.Summary.WarningReferences)) - - if len(results.BrokenReferences) > 0 { - printWarning("Broken references found:") - for _, ref := range results.BrokenReferences { - fmt.Printf(" %s/%s -> %s/%s (%s)\n", - ref.SourceType, ref.SourceName, - ref.TargetType, ref.TargetName, - ref.Field) - } - } - } - - return nil -} - -func init() { - rootCmd.AddCommand(validateCmd) - - // Add flags - validateCmd.Flags().StringSliceVarP(&validateNamespaces, "namespaces", "n", []string{}, "specific namespaces to validate") - validateCmd.Flags().StringSliceVarP(&validateResources, "resources", "r", []string{}, "specific resource types to validate") - validateCmd.Flags().StringVarP(&validateOutput, "output", "o", "table", "output format (table, json, yaml)") - validateCmd.Flags().BoolVar(&validateFix, "fix", false, "attempt to fix validation issues where possible") - - // Add aliases - validateCmd.Aliases = []string{"check", "lint"} -} \ No newline at end of file diff --git a/docs/commands/context.md b/docs/commands/context.md new file mode 100644 index 0000000..f8ac2a9 --- /dev/null +++ b/docs/commands/context.md @@ -0,0 +1,453 @@ +# Context Management + +The `kalco context` command manages cluster configurations and settings, allowing you to work with multiple Kubernetes clusters through a unified interface. + +## Overview + +Contexts in Kalco store cluster-specific information including: +- **Kubeconfig path** - Connection details for the cluster +- **Output directory** - Where exported resources are stored +- **Description** - Human-readable context description +- **Labels** - Key-value pairs for organization and filtering +- **Metadata** - Creation and modification timestamps + +## Subcommands + +### `kalco context set` + +Create or update a context with the specified configuration. + +#### Syntax + +```bash +kalco context set [flags] +``` + +#### Arguments + +- **``** - Unique name for the context (required) + +#### Flags + +| Flag | Description | Required | Default | +|------|-------------|----------|---------| +| `--kubeconfig` | Path to kubeconfig file | No | Current kubeconfig | +| `--output` | Output directory for exports | No | None | +| `--description` | Human-readable description | No | Empty | +| `--labels` | Labels in key=value format | No | Empty | + +#### Examples + +```bash +# Create a production context +kalco context set production \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports \ + --description "Production cluster for customer workloads" \ + --labels env=prod,team=platform + +# Create a staging context with minimal configuration +kalco context set staging \ + --kubeconfig ~/.kube/staging-config \ + --output ./staging-exports + +# Update existing context +kalco context set production \ + --description "Updated production cluster description" +``` + +### `kalco context list` + +Display all available contexts with their configuration details. + +#### Syntax + +```bash +kalco context list +``` + +#### Output + +The command displays: +- Context names with current context indicator (*) +- Description and configuration details +- Labels and metadata +- Creation and modification timestamps + +#### Example Output + +``` +Available contexts: + +* production + Description: Production cluster for customer workloads + Kubeconfig: ~/.kube/prod-config + Output Dir: ./prod-exports + Labels: env=prod, team=platform + Created: 2024-01-15 10:30:00 + Updated: 2024-01-15 14:45:00 + + staging + Description: Staging cluster for testing + Kubeconfig: ~/.kube/staging-config + Output Dir: ./staging-exports + Labels: env=staging, team=qa + Created: 2024-01-10 09:15:00 + Updated: 2024-01-10 09:15:00 + +* = current context +``` + +### `kalco context use` + +Switch to the specified context. This context will be used for future operations. + +#### Syntax + +```bash +kalco context use +``` + +#### Arguments + +- **``** - Name of the context to switch to (required) + +#### Examples + +```bash +# Switch to production context +kalco context use production + +# Switch to staging context +kalco context use staging +``` + +#### Output + +``` +Switched to context 'production' +``` + +### `kalco context show` + +Display detailed information about a specific context. + +#### Syntax + +```bash +kalco context show +``` + +#### Arguments + +- **``** - Name of the context to display (required) + +#### Examples + +```bash +# Show production context details +kalco context show production + +# Show staging context details +kalco context show staging +``` + +#### Example Output + +``` +Context: production +Description: Production cluster for customer workloads +Kubeconfig: ~/.kube/prod-config +Output Directory: ./prod-exports +Labels: + env: prod + team: platform +Created: 2024-01-15 10:30:00 +Updated: 2024-01-15 14:45:00 +``` + +### `kalco context current` + +Display information about the currently active context. + +#### Syntax + +```bash +kalco context current +``` + +#### Examples + +```bash +# Show current context +kalco context current +``` + +#### Example Output + +``` +Current context: production +Description: Production cluster for customer workloads +Kubeconfig: ~/.kube/prod-config +Output Directory: ./prod-exports +Labels: + env: prod + team: platform +Created: 2024-01-15 10:30:00 +Updated: 2024-01-15 14:45:00 +``` + +### `kalco context delete` + +Remove the specified context. Cannot delete the currently active context. + +#### Syntax + +```bash +kalco context delete +``` + +#### Arguments + +- **``** - Name of the context to delete (required) + +#### Examples + +```bash +# Delete staging context +kalco context delete staging + +# Delete production context (must switch first) +kalco context use staging +kalco context delete production +``` + +#### Output + +``` +Context 'staging' deleted successfully +``` + +### `kalco context load` + +Load a context configuration from an existing kalco directory by reading the `kalco-config.json` file. + +#### Syntax + +```bash +kalco context load +``` + +#### Arguments + +- **``** - Path to existing kalco export directory (required) + +#### Examples + +```bash +# Load context from existing export +kalco context load ./existing-kalco-export + +# Load context from team member's export +kalco context load ~/team-exports/prod-cluster +``` + +#### Example Output + +``` +Context 'prod-cluster' loaded successfully from './existing-kalco-export' + Kubeconfig: ~/.kube/prod-config + Output Dir: ./existing-kalco-export + Description: Production cluster export + Labels: env=prod, team=platform +``` + +## Context Configuration + +### Context File Structure + +Contexts are stored in `~/.kalco/contexts.yaml`: + +```yaml +production: + name: production + kubeconfig: ~/.kube/prod-config + output_dir: ./prod-exports + description: Production cluster for customer workloads + labels: + env: prod + team: platform + created_at: 2024-01-15T10:30:00Z + updated_at: 2024-01-15T14:45:00Z + +staging: + name: staging + kubeconfig: ~/.kube/staging-config + output_dir: ./staging-exports + description: Staging cluster for testing + labels: + env: staging + team: qa + created_at: 2024-01-10T09:15:00Z + updated_at: 2024-01-10T09:15:00Z +``` + +### Current Context + +The currently active context is stored in `~/.kalco/current-context`: + +``` +production +``` + +## Best Practices + +### Naming Conventions + +- **Use descriptive names** that reflect the cluster purpose +- **Include environment information** (e.g., `prod-eu-west`, `staging-us-east`) +- **Use consistent naming patterns** across your organization + +### Label Organization + +- **Environment labels**: `env=prod`, `env=staging`, `env=dev` +- **Team labels**: `team=platform`, `team=qa`, `team=developers` +- **Region labels**: `region=eu-west`, `region=us-east` +- **Customer labels**: `customer=enterprise`, `customer=internal` + +### Output Directory Structure + +- **Use meaningful paths** that reflect the cluster purpose +- **Include timestamps** for historical tracking +- **Use consistent naming** across contexts + +### Context Management + +- **Regularly review** and clean up unused contexts +- **Document context purposes** with clear descriptions +- **Share context configurations** with team members +- **Use context switching** for multi-cluster operations + +## Integration with Export + +Contexts automatically configure export operations: + +```bash +# Set production context +kalco context set production \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports + +# Use production context +kalco context use production + +# Export automatically uses context settings +kalco export --git-push --commit-message "Production backup" +``` + +The export command will: +- Connect using the context's kubeconfig +- Save resources to the context's output directory +- Use context metadata in generated reports + +## Troubleshooting + +### Common Issues + +#### Context Not Found + +``` +Error: context 'production' not found +``` + +**Solution**: Use `kalco context list` to see available contexts. + +#### Cannot Delete Current Context + +``` +Error: cannot delete the currently active context +``` + +**Solution**: Switch to another context first, then delete. + +#### Invalid Directory for Load + +``` +Error: directory './invalid-path' is not a valid kalco directory +``` + +**Solution**: Ensure the directory contains a `kalco-config.json` file. + +#### Permission Denied + +``` +Error: failed to create output directory +``` + +**Solution**: Ensure write permissions for the output directory. + +### Getting Help + +- **Context help**: `kalco context --help` +- **Subcommand help**: `kalco context --help` +- **Verbose output**: Use `--verbose` flag for detailed information + +## Examples + +### Multi-Environment Setup + +```bash +# Production environment +kalco context set production \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports \ + --description "Production cluster for customer workloads" \ + --labels env=prod,team=platform,region=eu-west + +# Staging environment +kalco context set staging \ + --kubeconfig ~/.kube/staging-config \ + --output ./staging-exports \ + --description "Staging cluster for testing and validation" \ + --labels env=staging,team=qa,region=eu-west + +# Development environment +kalco context set development \ + --kubeconfig ~/.kube/dev-config \ + --output ./dev-exports \ + --description "Development cluster for local development" \ + --labels env=dev,team=developers,region=eu-west +``` + +### Team Collaboration + +```bash +# Load team member's context +kalco context load ~/team-exports/prod-cluster + +# Use shared context +kalco context use prod-cluster + +# Export with team context +kalco export --git-push --commit-message "Team backup $(date)" +``` + +### Context Switching Workflow + +```bash +# Work with production +kalco context use production +kalco export --commit-message "Production backup" + +# Switch to staging +kalco context use staging +kalco export --commit-message "Staging backup" + +# Switch back to production +kalco context use production +kalco export --commit-message "Production update" +``` + +--- + +*For more information about context management, see the [Commands Reference](index.md) or run `kalco context --help`.* diff --git a/docs/commands/export.md b/docs/commands/export.md index d766e91..220f978 100644 --- a/docs/commands/export.md +++ b/docs/commands/export.md @@ -5,205 +5,319 @@ nav_order: 1 parent: Commands Reference --- -# kalco export +# Export Command -Export cluster resources to organized YAML files. +The `kalco export` command exports Kubernetes cluster resources to organized YAML files with automatic Git integration and professional report generation. -## Synopsis +## Overview -The `export` command is Kalco's primary functionality. It discovers all available API resources (including CRDs) and exports them with clean metadata suitable for re-application. +The export command is the core functionality of Kalco, providing: + +- **Complete Resource Discovery** - Automatically finds all available API resources including CRDs +- **Structured Organization** - Creates intuitive directory structures by namespace and resource type +- **Clean YAML Output** - Optimizes metadata for re-application +- **Git Integration** - Automatic version control with commit history +- **Professional Reporting** - Comprehensive change analysis and validation reports + +## Syntax ```bash kalco export [flags] ``` -## Description +## Flags -The export command creates an intuitive directory structure: -- **Namespaced resources**: `///.yaml` -- **Cluster resources**: `/_cluster//.yaml` +### Output Configuration -Features: -- Automatic resource discovery (native K8s + CRDs) -- Clean metadata removal for re-application -- Git integration for version control -- Flexible filtering options -- Progress tracking and detailed output +| Flag | Description | Default | Required | +|------|-------------|---------|----------| +| `--output, -o` | Output directory path | `./kalco-export-` | No | +| `--namespaces, -n` | Specific namespaces to export | All namespaces | No | +| `--resources, -r` | Specific resource types to export | All resources | No | +| `--exclude` | Resource types to exclude | None | No | -## Flags +### Git Integration -| Flag | Short | Type | Description | -|------|-------|------|-------------| -| `--output` | `-o` | string | Output directory path (default: `./kalco-export-`) | -| `--namespaces` | `-n` | []string | Specific namespaces to export (comma-separated) | -| `--resources` | `-r` | []string | Specific resource types to export (comma-separated) | -| `--exclude` | | []string | Resource types to exclude (comma-separated) | -| `--git-push` | | bool | Automatically push changes to remote origin | -| `--commit-message` | `-m` | string | Custom Git commit message | -| `--dry-run` | | bool | Show what would be exported without writing files | +| Flag | Description | Default | Required | +|------|-------------|---------|----------| +| `--git-push` | Automatically push to remote origin | `false` | No | +| `--commit-message, -m` | Custom Git commit message | Timestamp-based | No | +| `--no-commit` | Skip Git commit operations | `false` | No | -## Examples +### Execution Control -### Basic Export +| Flag | Description | Default | Required | +|------|-------------|---------|----------| +| `--dry-run` | Show what would be exported | `false` | No | +| `--verbose, -v` | Enable verbose output | `false` | No | + +## Basic Usage + +### Simple Export + +Export all cluster resources to a timestamped directory: ```bash -# Export entire cluster to timestamped directory kalco export +``` + +This creates a directory like `./kalco-export-20240819-145542/` and exports all resources. -# Export to specific directory -kalco export --output ./cluster-backup +### Custom Output Directory + +Specify a custom output directory: + +```bash +kalco export --output ./my-cluster-backup ``` -### Filtered Export +### Namespace Filtering + +Export resources from specific namespaces: ```bash -# Export specific namespaces only -kalco export --namespaces default,kube-system,production +# Single namespace +kalco export --namespaces default + +# Multiple namespaces +kalco export --namespaces default,kube-system,monitoring -# Export specific resource types -kalco export --resources pods,services,deployments,configmaps +# Exclude system namespaces +kalco export --namespaces default,monitoring --exclude kube-system +``` + +### Resource Type Filtering + +Export specific resource types: + +```bash +# Core resources +kalco export --resources pods,services,deployments + +# All resources in a category +kalco export --resources "*.apps/v1" # Exclude noisy resources kalco export --exclude events,replicasets,endpoints ``` -### Git Integration +## Git Integration + +### Automatic Git Setup + +Kalco automatically initializes Git repositories for new export directories: ```bash -# Export with Git commit -kalco export --commit-message "Weekly cluster backup" +kalco export --output ./new-cluster-export +``` + +The command will: +1. Create the output directory +2. Initialize a Git repository +3. Export cluster resources +4. Create initial commit +5. Generate change report + +### Custom Commit Messages -# Export and push to remote -kalco export --git-push --commit-message "Production snapshot $(date)" +Use meaningful commit messages for better Git history: -# Export to existing Git repository -kalco export --output ./existing-repo/cluster-state --git-push +```bash +kalco export --commit-message "Production backup - $(date)" +kalco export --commit-message "Weekly maintenance backup" +kalco export --commit-message "Before deployment v2.1.0" ``` -### Advanced Usage +### Remote Push + +Automatically push changes to remote repositories: ```bash -# Dry run to see what would be exported -kalco export --dry-run --verbose +kalco export --git-push --commit-message "Automated backup" +``` -# Export production namespace with Git integration -kalco export \ - --namespaces production \ - --exclude events,replicasets \ - --git-push \ - --commit-message "Production backup - $(date +%Y-%m-%d)" +**Note**: This requires the directory to have a remote origin configured. -# Export for disaster recovery -kalco export \ - --output ./disaster-recovery/cluster-$(date +%Y%m%d) \ - --exclude events,endpoints,replicasets \ - --commit-message "DR backup" +### Skip Git Operations + +Export without Git integration: + +```bash +kalco export --no-commit ``` +This is useful for: +- One-time exports +- CI/CD pipelines where Git is handled separately +- Testing export functionality + ## Output Structure +Kalco creates a professional, organized directory structure: + ``` -output-directory/ -โ”œโ”€โ”€ _cluster/ # Cluster-scoped resources -โ”‚ โ”œโ”€โ”€ ClusterRole/ -โ”‚ โ”‚ โ”œโ”€โ”€ admin.yaml -โ”‚ โ”‚ โ””โ”€โ”€ view.yaml -โ”‚ โ”œโ”€โ”€ ClusterRoleBinding/ -โ”‚ โ””โ”€โ”€ StorageClass/ -โ”œโ”€โ”€ default/ # Default namespace -โ”‚ โ”œโ”€โ”€ ConfigMap/ -โ”‚ โ”œโ”€โ”€ Service/ -โ”‚ โ””โ”€โ”€ ServiceAccount/ -โ”œโ”€โ”€ kube-system/ # System namespace -โ”‚ โ”œโ”€โ”€ Deployment/ -โ”‚ โ”œโ”€โ”€ Service/ -โ”‚ โ””โ”€โ”€ ConfigMap/ -โ””โ”€โ”€ kalco-reports/ # Analysis reports - โ”œโ”€โ”€ Cluster-snapshot-2025-08-16-14-55-34.md - โ””โ”€โ”€ Changes-and-validation-demo--2025-08-16-14-55-34.md +/ +โ”œโ”€โ”€ / +โ”‚ โ”œโ”€โ”€ / +โ”‚ โ”‚ โ”œโ”€โ”€ .yaml +โ”‚ โ”‚ โ””โ”€โ”€ ... +โ”‚ โ””โ”€โ”€ ... +โ”œโ”€โ”€ _cluster/ +โ”‚ โ”œโ”€โ”€ / +โ”‚ โ”‚ โ”œโ”€โ”€ .yaml +โ”‚ โ”‚ โ””โ”€โ”€ ... +โ”‚ โ””โ”€โ”€ ... +โ”œโ”€โ”€ kalco-reports/ +โ”‚ โ””โ”€โ”€ -.md +โ””โ”€โ”€ kalco-config.json ``` -## Resource Filtering +### Directory Organization -### Include Specific Resources +- **Namespaced Resources**: `///.yaml` +- **Cluster Resources**: `/_cluster//.yaml` +- **Reports**: `/kalco-reports/-.md` +- **Configuration**: `/kalco-config.json` -```bash -# Export only core resources -kalco export --resources pods,services,deployments,configmaps +### File Naming -# Export with CRDs -kalco export --resources pods,services,mycustomresource -``` +Resources are saved with descriptive filenames: +- `nginx-deployment.yaml` +- `mysql-service.yaml` +- `redis-configmap.yaml` + +## Context Integration -### Exclude Resources +When using contexts, export automatically uses context settings: ```bash -# Exclude noisy resources -kalco export --exclude events,replicasets,endpoints +# Set production context +kalco context set production \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports -# Exclude specific CRDs -kalco export --exclude events,mycustomresource +# Use production context +kalco context use production + +# Export uses context settings automatically +kalco export --git-push --commit-message "Production backup" ``` -## Git Integration +The export command will: +- Connect using the context's kubeconfig +- Save resources to the context's output directory +- Use context metadata in generated reports -### Automatic Git Operations +## Report Generation -```bash -# Initialize Git and commit -kalco export --git-push +Kalco automatically generates comprehensive reports for each export: -# Custom commit message -kalco export --git-push --commit-message "Weekly backup" +### Report Location -# Push to remote origin -kalco export --git-push --commit-message "Production snapshot" -``` +Reports are saved in `kalco-reports/` directory with descriptive names: +- `20240819-145542-Production-backup.md` +- `20240819-160000-Weekly-maintenance.md` -### Git Repository Setup +### Report Content -The export command will: -1. Initialize Git repository if not present -2. Add all exported files -3. Commit with timestamp or custom message -4. Push to remote origin if `--git-push` is specified +Each report includes: +- **Change Summary** - Overview of modifications +- **Resource Details** - Specific changes with diffs +- **Validation Results** - Cross-reference and orphaned resource checks +- **Git Information** - Commit details and history +- **Actionable Insights** - Recommendations and next steps + +### Report Types + +- **Initial Snapshot** - First export with complete resource inventory +- **Change Reports** - Incremental updates with modification details +- **Validation Reports** - Cross-reference and orphaned resource analysis + +## Advanced Usage + +### Dry Run Mode + +Preview what would be exported without making changes: + +```bash +kalco export --dry-run --output ./preview-export +``` -## Progress and Output +Output shows: +- Resources that would be exported +- Directory structure that would be created +- Git operations that would be performed + +### Verbose Output -### Verbose Mode +Enable detailed logging for debugging: ```bash -kalco export --verbose +kalco export --verbose --output ./debug-export ``` Shows: - Resource discovery progress -- File writing operations -- Git operations +- Export operations in detail +- Git operation details - Validation results -### Dry Run +### Resource Filtering Examples ```bash -kalco export --dry-run +# Export only application resources +kalco export --resources deployments,services,configmaps,secrets + +# Exclude system and temporary resources +kalco export --exclude events,replicasets,endpoints,pods + +# Focus on specific application namespace +kalco export --namespaces myapp --resources deployments,services ``` -Shows what would be exported without writing files: -- Resource counts by namespace -- File paths that would be created -- Git operations that would be performed +## Use Cases -## Exit Codes +### Production Backups -- `0` - Success -- `1` - General error -- `2` - Configuration error -- `3` - Kubernetes connection error -- `4` - File system error -- `5` - Git operation error +```bash +# Daily automated backup +kalco export \ + --output ./prod-backups/$(date +%Y%m%d) \ + --git-push \ + --commit-message "Daily production backup - $(date)" -## Related Commands +# Pre-deployment backup +kalco export \ + --output ./prod-backups/pre-deploy-v2.1.0 \ + --commit-message "Pre-deployment backup v2.1.0" +``` + +### CI/CD Integration + +```bash +#!/bin/bash +# Automated backup script + +CLUSTER_NAME=$1 +BACKUP_DIR="./backups/${CLUSTER_NAME}/$(date +%Y%m%d-%H%M%S)" + +# Export cluster +kalco export \ + --output "$BACKUP_DIR" \ + --exclude events,replicasets,pods \ + --commit-message "Automated backup - $CLUSTER_NAME - $(date)" + +# Push to remote if available +if [ -d "$BACKUP_DIR/.git" ]; then + cd "$BACKUP_DIR" + if git remote get-url origin >/dev/null 2>&1; then + git push origin main + fi +fi + +echo "Backup completed: $BACKUP_DIR" +``` + +--- -- **[validate]({{ site.baseurl }}/docs/commands/validate)** - Validate exported resources -- **[analyze]({{ site.baseurl }}/docs/commands/analyze)** - Analyze cluster state -- **[config]({{ site.baseurl }}/docs/commands/config)** - Manage configuration +*For more information about the export command, run `kalco export --help` or see the [Commands Reference](index.md).* diff --git a/docs/commands/index.md b/docs/commands/index.md index 6f36fd8..823f5e4 100644 --- a/docs/commands/index.md +++ b/docs/commands/index.md @@ -7,95 +7,208 @@ has_children: true # Commands Reference -Complete reference for all Kalco commands and options. +This section provides comprehensive documentation for all Kalco commands and their options. -## ๐Ÿ“‹ Available Commands +## Command Overview -- **[export]({{ site.baseurl }}/docs/commands/export)** - Export cluster resources to organized YAML files -- **[validate]({{ site.baseurl }}/docs/commands/validate)** - Validate cluster resources and cross-references -- **[analyze]({{ site.baseurl }}/docs/commands/analyze)** - Analyze cluster state and generate reports -- **[config]({{ site.baseurl }}/docs/commands/config)** - Manage Kalco configuration +Kalco provides a focused set of commands designed for professional Kubernetes cluster management: -## ๐Ÿšฉ Global Options +| Command | Description | Usage | +|---------|-------------|-------| +| `kalco context` | Manage cluster contexts | `kalco context set/list/use/load` | +| `kalco export` | Export cluster resources | `kalco export [flags]` | +| `kalco completion` | Shell completion | `kalco completion bash\|zsh\|fish\|powershell` | +| `kalco version` | Version information | `kalco version` | -All commands support these global options: +## Global Flags -```bash ---help, -h Show help for the command ---version, -v Show version information ---verbose Enable verbose output ---quiet Suppress non-error messages ---config Path to configuration file -``` +All Kalco commands support these global flags: + +| Flag | Description | Default | +|------|-------------|---------| +| `--kubeconfig` | Path to kubeconfig file | `~/.kube/config` | +| `--verbose, -v` | Enable verbose output | `false` | +| `--no-color` | Disable colored output | `false` | +| `--help, -h` | Show help information | - | + +## Context Management + +The `kalco context` command manages cluster configurations and settings. -## ๐Ÿ”ง Command Structure +### Subcommands + +- **`set`** - Create or update a context +- **`list`** - List all available contexts +- **`use`** - Switch to a specific context +- **`show`** - Display context details +- **`current`** - Show current active context +- **`delete`** - Remove a context +- **`load`** - Import context from existing directory + +### Usage Examples ```bash -kalco [subcommand] [flags] [arguments] +# Create a production context +kalco context set production \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports \ + --description "Production cluster for customer workloads" \ + --labels env=prod,team=platform + +# List all contexts +kalco context list + +# Switch to production context +kalco context use production + +# Show current context +kalco context current + +# Load context from existing export +kalco context load ./existing-kalco-export ``` -### Examples +## Resource Export + +The `kalco export` command exports Kubernetes cluster resources with Git integration. + +### Flags + +| Flag | Description | Default | +|------|-------------|---------| +| `--output, -o` | Output directory path | `./kalco-export-` | +| `--namespaces, -n` | Specific namespaces to export | All namespaces | +| `--resources, -r` | Specific resource types to export | All resources | +| `--exclude` | Resource types to exclude | None | +| `--git-push` | Automatically push to remote origin | `false` | +| `--commit-message, -m` | Custom Git commit message | Timestamp-based | +| `--dry-run` | Show what would be exported | `false` | +| `--no-commit` | Skip Git commit operations | `false` | + +### Usage Examples ```bash # Basic export kalco export -# Export with options -kalco export --output ./backup --namespaces default,kube-system +# Export to specific directory +kalco export --output ./cluster-backup -# Validate cluster -kalco validate --cross-references +# Export specific namespaces +kalco export --namespaces default,kube-system -# Show configuration -kalco config show +# Export specific resource types +kalco export --resources pods,services,deployments + +# Exclude noisy resources +kalco export --exclude events,replicasets + +# Export with Git integration +kalco export --git-push --commit-message "Weekly backup" + +# Export without committing +kalco export --no-commit + +# Dry run to see what would be exported +kalco export --dry-run ``` -## ๐Ÿ“š Command Categories +## Shell Completion -### Core Operations -- **export** - Primary functionality for cluster resource extraction -- **validate** - Resource validation and health checks -- **analyze** - Cluster analysis and reporting +The `kalco completion` command generates shell completion scripts. -### Configuration & Management -- **config** - Configuration management and validation -- **completion** - Shell completion generation +### Supported Shells -## ๐ŸŽฏ Getting Help +- **Bash** - `kalco completion bash` +- **Zsh** - `kalco completion zsh` +- **Fish** - `kalco completion fish` +- **PowerShell** - `kalco completion powershell` -### Command Help +### Usage Examples ```bash -# General help -kalco --help +# Generate bash completion +kalco completion bash > /etc/bash_completion.d/kalco + +# Generate zsh completion +kalco completion zsh > ~/.zsh/completion/_kalco -# Command-specific help -kalco export --help -kalco validate --help +# Source completion in current shell +source <(kalco completion bash) ``` -### Examples +## Version Information -```bash -# Show examples for export command -kalco export --help | grep -A 10 "Examples" +The `kalco version` command displays version and build information. -# Show all available flags -kalco export --help | grep -E "^ --" +```bash +kalco version ``` -## ๐Ÿ” Command Discovery +Output includes: +- Version number +- Git commit hash +- Build timestamp +- Go version used -Explore available commands: +## Command Aliases -```bash -# List all commands -kalco --help +Some commands provide convenient aliases: -# Show command tree -kalco --help | grep -E "^ [a-z]" -``` +| Command | Aliases | +|---------|---------| +| `kalco export` | `dump`, `backup` | +| `kalco context list` | `ls` | +| `kalco context show` | `info` | + +## Error Handling + +Kalco provides clear error messages and exit codes: + +- **Exit Code 0** - Success +- **Exit Code 1** - General error +- **Exit Code 2** - Configuration error +- **Exit Code 3** - Kubernetes connection error + +## Best Practices + +### Context Management + +1. **Use descriptive names** for contexts (e.g., `prod-eu-west`, `staging-us-east`) +2. **Include labels** for better organization and filtering +3. **Set output directories** that reflect the cluster purpose +4. **Regularly clean up** unused contexts -## ๐Ÿ“– Next Steps +### Resource Export + +1. **Use meaningful commit messages** for better Git history +2. **Exclude noisy resources** like events and replicasets +3. **Enable Git push** for team collaboration +4. **Use dry-run** to verify export configuration + +### Automation + +1. **Integrate with CI/CD** pipelines for automated backups +2. **Use context switching** for multi-cluster operations +3. **Leverage shell completion** for faster command entry +4. **Set up regular exports** for change tracking + +## Troubleshooting + +### Common Issues + +- **Permission denied**: Ensure write access to output directory +- **Git not found**: Install Git for version control functionality +- **Kubernetes connection failed**: Verify kubeconfig and cluster access +- **Context not found**: Use `kalco context list` to see available contexts + +### Getting Help + +- **Command help**: `kalco --help` +- **Global help**: `kalco --help` +- **Verbose output**: Use `--verbose` flag for detailed information +- **GitHub issues**: Report bugs and request features + +--- -Explore the specific command categories to learn more about each command's options and usage patterns. +*For more detailed information about specific commands, see the individual command documentation pages.* diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index 894c9ef..9a70a35 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -7,198 +7,497 @@ parent: Getting Started # Configuration -Customize Kalco to fit your workflow and requirements. +This guide covers configuring Kalco for your environment, including context management, output customization, and advanced settings. -## โš™๏ธ Configuration Methods +## Overview -Kalco supports multiple configuration methods in order of precedence: +Kalco uses a hierarchical configuration system: -1. **Command-line flags** (highest priority) -2. **Environment variables** -3. **Configuration files** -4. **Default values** (lowest priority) +- **Global Configuration** - Application-wide settings +- **Context Configuration** - Cluster-specific settings +- **Environment Variables** - System-level overrides +- **Command Line Flags** - Runtime overrides -## ๐Ÿšฉ Command-Line Options +## Configuration Directory -### Basic Options +Kalco stores configuration in `~/.kalco/`: + +``` +~/.kalco/ +โ”œโ”€โ”€ contexts.yaml # Context configurations +โ”œโ”€โ”€ current-context # Currently active context +โ””โ”€โ”€ config.json # Global configuration +``` + +### Initial Setup + +The configuration directory is created automatically on first run: ```bash -# Output directory -kalco export --output ./cluster-backup +# First command creates ~/.kalco/ +kalco context list +``` + +## Context Configuration -# Specific namespaces -kalco export --namespaces default,kube-system,production +### Context Structure -# Resource filtering -kalco export --resources pods,services,deployments -kalco export --exclude events,replicasets,endpoints +Each context stores cluster-specific information: -# Verbose output -kalco export --verbose +```yaml +production: + name: production + kubeconfig: ~/.kube/prod-config + output_dir: ./prod-exports + description: Production cluster for customer workloads + labels: + env: prod + team: platform + region: eu-west + created_at: 2024-01-15T10:30:00Z + updated_at: 2024-01-15T14:45:00Z + +staging: + name: staging + kubeconfig: ~/.kube/staging-config + output_dir: ./staging-exports + description: Staging cluster for testing + labels: + env: staging + team: qa + region: eu-west + created_at: 2024-01-10T09:15:00Z + updated_at: 2024-01-10T09:15:00Z ``` -### Git Integration +### Context Fields + +| Field | Description | Required | Default | +|-------|-------------|----------|---------| +| `name` | Unique context identifier | Yes | - | +| `kubeconfig` | Path to kubeconfig file | No | Current kubeconfig | +| `output_dir` | Export output directory | No | None | +| `description` | Human-readable description | No | Empty | +| `labels` | Key-value pairs for organization | No | Empty | +| `created_at` | Creation timestamp | Auto | Current time | +| `updated_at` | Last modification timestamp | Auto | Current time | + +### Managing Contexts + +#### Create Context ```bash -# Enable Git operations -kalco export --git-push +# Basic context +kalco context set my-cluster \ + --kubeconfig ~/.kube/config \ + --output ./my-exports + +# With metadata +kalco context set production \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports \ + --description "Production cluster for customer workloads" \ + --labels env=prod,team=platform,region=eu-west +``` -# Custom commit message -kalco export --commit-message "Weekly backup - $(date)" +#### Update Context -# Dry run (no actual export) -kalco export --dry-run +```bash +# Update description +kalco context set production \ + --description "Updated production cluster description" + +# Update labels +kalco context set production \ + --labels env=prod,team=platform,region=eu-west,customer=enterprise + +# Update output directory +kalco context set production \ + --output ./new-prod-exports +``` + +#### Delete Context + +```bash +# Delete context (must not be current) +kalco context delete staging ``` -## ๐ŸŒ Environment Variables +### Context Best Practices + +#### Naming Conventions + +- **Environment-based**: `prod`, `staging`, `dev` +- **Region-based**: `prod-eu-west`, `prod-us-east` +- **Team-based**: `prod-platform`, `prod-data` +- **Customer-based**: `prod-enterprise`, `prod-startup` + +#### Label Organization + +```yaml +# Environment labels +env: prod|staging|dev|testing + +# Team labels +team: platform|qa|developers|data -Set these environment variables for persistent configuration: +# Region labels +region: eu-west|us-east|ap-southeast + +# Customer labels +customer: enterprise|startup|internal + +# Project labels +project: website|api|analytics +``` + +#### Output Directory Strategy ```bash -# Output directory -export KALCO_OUTPUT_DIR="./cluster-exports" +# Environment-based +./exports/prod/ +./exports/staging/ +./exports/dev/ + +# Date-based +./exports/prod/2024-01-15/ +./exports/prod/2024-01-16/ + +# Project-based +./exports/prod/website/ +./exports/prod/api/ +``` + +## Global Configuration -# Default namespaces -export KALCO_NAMESPACES="default,kube-system,production" +### Global Settings -# Git integration -export KALCO_GIT_PUSH="true" -export KALCO_COMMIT_MESSAGE="Cluster snapshot" +Global configuration in `~/.kalco/config.json`: -# Resource filtering -export KALCO_RESOURCES="pods,services,deployments,configmaps" -export KALCO_EXCLUDE="events,replicasets,endpoints" +```json +{ + "default_kubeconfig": "~/.kube/config", + "default_output_dir": "./kalco-exports", + "git_auto_push": false, + "git_auto_commit": true, + "report_format": "markdown", + "exclude_resources": ["events", "replicasets"], + "include_resources": [], + "verbose_output": false, + "color_output": true +} ``` -## ๐Ÿ“ Configuration Files +### Configuration Options -Create a configuration file for complex setups: +| Option | Description | Default | Type | +|--------|-------------|---------|------| +| `default_kubeconfig` | Default kubeconfig path | `~/.kube/config` | string | +| `default_output_dir` | Default output directory | `./kalco-exports` | string | +| `git_auto_push` | Automatically push Git changes | `false` | boolean | +| `git_auto_commit` | Automatically commit changes | `true` | boolean | +| `report_format` | Report output format | `markdown` | string | +| `exclude_resources` | Resources to exclude by default | `["events"]` | array | +| `include_resources` | Resources to include by default | `[]` | array | +| `verbose_output` | Enable verbose output by default | `false` | boolean | +| `color_output` | Enable colored output by default | `true` | boolean | -```yaml -# ~/.kalco/config.yaml -output: - directory: "./cluster-backups" - format: "yaml" - compress: false +## Environment Variables -filtering: - namespaces: - - "default" - - "kube-system" - - "production" - resources: - include: - - "pods" - - "services" - - "deployments" - - "configmaps" - exclude: - - "events" - - "replicasets" - - "endpoints" +### Supported Variables -git: - enabled: true - auto_push: false - commit_message: "Cluster export - {timestamp}" - remote_origin: "origin" +Kalco respects these environment variables: -validation: - cross_references: true - orphaned_resources: true - detailed_reporting: true +| Variable | Description | Default | +|----------|-------------|---------| +| `KUBECONFIG` | Path to kubeconfig file | `~/.kube/config` | +| `KALCO_CONFIG_DIR` | Configuration directory | `~/.kalco` | +| `KALCO_DEFAULT_OUTPUT` | Default output directory | `./kalco-exports` | +| `KALCO_GIT_AUTO_PUSH` | Enable auto Git push | `false` | +| `KALCO_VERBOSE` | Enable verbose output | `false` | +| `NO_COLOR` | Disable colored output | `false` | -output: - reports: - enabled: true - format: "markdown" - include_changes: true - include_validation: true +### Environment Variable Usage + +```bash +# Set environment variables +export KUBECONFIG=~/.kube/prod-config +export KALCO_DEFAULT_OUTPUT=./prod-exports +export KALCO_GIT_AUTO_PUSH=true + +# Run kalco with environment settings +kalco export ``` -## ๐Ÿ”ง Advanced Configuration +## Export Configuration + +### Default Export Settings -### Custom Resource Types +Configure default export behavior: ```bash -# Include Custom Resource Definitions -kalco export --resources pods,services,mycustomresource +# Set default exclude resources +kalco context set production \ + --exclude events,replicasets,endpoints -# Exclude specific CRDs -kalco export --exclude events,replicasets,mycustomresource +# Set default include resources +kalco context set production \ + --resources deployments,services,configmaps,secrets ``` -### Output Formatting +### Export Flags + +Override configuration with command-line flags: ```bash -# Compressed output -kalco export --compress +# Override output directory +kalco export --output ./custom-backup + +# Override namespace filtering +kalco export --namespaces default,kube-system + +# Override resource filtering +kalco export --resources pods,services -# Custom file naming -kalco export --output "./backups/cluster-{date}-{time}" +# Override exclusions +kalco export --exclude events,replicasets + +# Override Git behavior +kalco export --no-commit +kalco export --git-push ``` -### Validation Options +## Git Configuration + +### Repository Settings + +Kalco automatically configures Git repositories: ```bash -# Skip validation for faster export -kalco export --skip-validation +# Initialize Git repository +kalco export --output ./new-export -# Custom validation rules -kalco export --validation-rules ./rules.yaml +# Configure remote origin +cd ./new-export +git remote add origin + +# Enable auto-push +kalco export --git-push ``` -## ๐Ÿ“‹ Configuration Examples +### Git Integration Options -### Development Environment +| Option | Description | Default | +|--------|-------------|---------| +| `--git-push` | Automatically push to remote | `false` | +| `--commit-message` | Custom commit message | Timestamp-based | +| `--no-commit` | Skip Git operations | `false` | + +### Git Best Practices + +1. **Use meaningful commit messages** +2. **Configure remote origins for collaboration** +3. **Use branches for different environments** +4. **Regularly clean up old export directories** + +## Output Configuration + +### Directory Structure + +Customize export output organization: ```bash -# Quick export for development +# Default structure +/ +โ”œโ”€โ”€ / +โ”‚ โ”œโ”€โ”€ / +โ”‚ โ”‚ โ””โ”€โ”€ .yaml +โ”‚ โ””โ”€โ”€ ... +โ”œโ”€โ”€ _cluster/ +โ”‚ โ”œโ”€โ”€ / +โ”‚ โ”‚ โ””โ”€โ”€ .yaml +โ”‚ โ””โ”€โ”€ ... +โ”œโ”€โ”€ kalco-reports/ +โ”‚ โ””โ”€โ”€ -.md +โ””โ”€โ”€ kalco-config.json +``` + +### Resource Organization + +- **Namespaced Resources**: `//.yaml` +- **Cluster Resources**: `_cluster//.yaml` +- **Reports**: `kalco-reports/-.md` +- **Configuration**: `kalco-config.json` + +## Advanced Configuration + +### Resource Filtering + +Configure resource inclusion and exclusion: + +```bash +# Exclude noisy resources +kalco context set production \ + --exclude events,replicasets,endpoints,pods + +# Include only specific resources +kalco context set production \ + --resources deployments,services,configmaps,secrets + +# Combine filters kalco export \ - --output "./dev-cluster" \ - --namespaces "default,development" \ - --exclude "events,replicasets" \ - --verbose + --namespaces default,monitoring \ + --resources deployments,services \ + --exclude events ``` -### Production Backup +### Namespace Filtering ```bash -# Comprehensive production backup +# Export specific namespaces +kalco export --namespaces default,kube-system + +# Exclude system namespaces +kalco export --namespaces default,monitoring,applications + +# Export all except system +kalco export --exclude-namespaces kube-system,kube-public +``` + +### Custom Output Formats + +Kalco supports multiple output formats: + +```bash +# Markdown reports (default) +kalco export --report-format markdown + +# JSON reports +kalco export --report-format json + +# HTML reports +kalco export --report-format html +``` + +## Configuration Examples + +### Development Environment + +```bash +# Development context +kalco context set dev \ + --kubeconfig ~/.kube/dev-config \ + --output ./dev-exports \ + --description "Development cluster for testing" \ + --labels env=dev,team=developers + +# Development export settings kalco export \ - --output "./production-backup-$(date +%Y%m%d)" \ - --namespaces "production,monitoring,security" \ - --git-push \ - --commit-message "Production backup - $(date)" + --namespaces default,dev-apps \ + --exclude events,replicasets \ + --commit-message "Development snapshot" ``` -### CI/CD Pipeline +### Production Environment ```bash -# Automated export in CI/CD +# Production context +kalco context set prod \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports \ + --description "Production cluster for customer workloads" \ + --labels env=prod,team=platform,region=eu-west + +# Production export settings kalco export \ - --output "./cluster-state" \ + --namespaces production,monitoring \ + --exclude events,replicasets,pods \ --git-push \ - --commit-message "Automated backup - Build ${BUILD_NUMBER}" + --commit-message "Production backup $(date)" ``` -## ๐Ÿ” Configuration Validation +### Multi-Cluster Setup + +```bash +# Staging context +kalco context set staging \ + --kubeconfig ~/.kube/staging-config \ + --output ./staging-exports \ + --labels env=staging,team=qa + +# Production context +kalco context set production \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports \ + --labels env=prod,team=platform + +# Export all environments +for env in staging production; do + kalco context use $env + kalco export --git-push --commit-message "$env backup $(date)" +done +``` + +## Troubleshooting + +### Configuration Issues + +#### Context Not Found + +```bash +Error: context 'production' not found +``` + +**Solution**: Use `kalco context list` to see available contexts. + +#### Invalid Configuration + +```bash +Error: invalid context configuration +``` + +**Solution**: Check context file format and syntax. + +#### Permission Denied + +```bash +Error: failed to create configuration directory +``` + +**Solution**: Ensure write permissions for `~/.kalco/`. + +### Configuration Validation Validate your configuration: ```bash -# Check configuration -kalco config validate +# Check context configuration +kalco context show production -# Show current configuration -kalco config show +# Verify current context +kalco context current -# Test configuration -kalco export --dry-run --verbose +# Test export with current configuration +kalco export --dry-run ``` -## ๐Ÿ“š Next Steps +### Getting Help + +- **Configuration help**: `kalco context --help` +- **Export help**: `kalco export --help` +- **Verbose output**: Use `--verbose` flag +- **Dry run**: Use `--dry-run` to preview configuration + +## Next Steps + +After configuring Kalco: + +1. **Test your configuration** with `--dry-run` +2. **Set up multiple contexts** for different environments +3. **Configure automated exports** for regular backups +4. **Customize resource filtering** for your needs +5. **Read the [Commands Reference](../commands/index.md)** for advanced usage + +--- -1. **[Commands Reference]({{ site.baseurl }}/docs/commands/)** - Complete command documentation -2. **[Use Cases]({{ site.baseurl }}/docs/use-cases/)** - Common scenarios and workflows -3. **[Troubleshooting]({{ site.baseurl }}/docs/getting-started/troubleshooting)** - Solve common issues +*For more configuration help, see the [Commands Reference](../commands/index.md) or run `kalco --help`.* diff --git a/docs/getting-started/first-run.md b/docs/getting-started/first-run.md index 166f010..3eda4d6 100644 --- a/docs/getting-started/first-run.md +++ b/docs/getting-started/first-run.md @@ -7,122 +7,366 @@ parent: Getting Started # First Run -Learn how to export your first Kubernetes cluster with Kalco. +This guide walks you through your first Kalco export, from setting up a context to generating your first cluster snapshot. -## ๐ŸŽฏ Prerequisites +## Prerequisites -Before you begin, ensure you have: +Before starting, ensure you have: -- โœ… Kalco installed and working -- โœ… `kubectl` configured and connected to a cluster -- โœ… Access to a Kubernetes cluster (local or remote) +- **Kalco installed** - See [Installation](installation.md) if needed +- **Kubernetes access** - Valid kubeconfig or in-cluster access +- **Git installed** (optional) - For version control functionality -## ๐Ÿ” Verify Cluster Access +## Quick Start -First, verify that you can access your cluster: +### 1. Verify Installation + +First, confirm Kalco is working: ```bash -kubectl cluster-info -kubectl get nodes +kalco version ``` -## ๐Ÿš€ Basic Export +You should see version information and build details. + +### 2. Check Available Commands -Start with a simple export to see Kalco in action: +Explore what Kalco can do: ```bash -# Export to a timestamped directory -kalco export +kalco --help +``` + +This shows the available commands: `context`, `export`, `completion`, and `version`. + +### 3. Set Up Your First Context + +Create a context for your cluster: + +```bash +kalco context set my-cluster \ + --kubeconfig ~/.kube/config \ + --output ./my-cluster-exports \ + --description "My first Kubernetes cluster" \ + --labels env=dev,team=personal +``` + +### 4. Use the Context + +Activate your context: + +```bash +kalco context use my-cluster +``` + +### 5. Export Your Cluster + +Perform your first export: + +```bash +kalco export --git-push --commit-message "Initial cluster snapshot" +``` + +## Detailed Walkthrough + +### Understanding Contexts + +Contexts in Kalco store cluster-specific information: -# Or specify a custom output directory -kalco export --output ./my-cluster-backup +- **Kubeconfig path** - How to connect to your cluster +- **Output directory** - Where exported resources are saved +- **Description** - Human-readable context description +- **Labels** - Key-value pairs for organization + +### Creating Your First Context + +```bash +# Basic context creation +kalco context set my-cluster \ + --kubeconfig ~/.kube/config \ + --output ./my-cluster-exports + +# With additional metadata +kalco context set my-cluster \ + --kubeconfig ~/.kube/config \ + --output ./my-cluster-exports \ + --description "Development cluster for testing" \ + --labels env=dev,team=developers,region=local ``` -## ๐Ÿ“ Understanding the Output +**Context Naming Tips:** +- Use descriptive names (e.g., `prod-eu-west`, `staging-us-east`) +- Include environment information +- Use consistent naming patterns + +### Managing Contexts + +View your contexts: + +```bash +# List all contexts +kalco context list -Kalco creates an organized directory structure: +# Show current context +kalco context current +# Show specific context details +kalco context show my-cluster ``` -my-cluster-backup/ -โ”œโ”€โ”€ _cluster/ # Cluster-scoped resources -โ”‚ โ”œโ”€โ”€ ClusterRole/ -โ”‚ โ”œโ”€โ”€ ClusterRoleBinding/ -โ”‚ โ”œโ”€โ”€ StorageClass/ -โ”‚ โ””โ”€โ”€ ... -โ”œโ”€โ”€ default/ # Default namespace -โ”‚ โ”œโ”€โ”€ ConfigMap/ -โ”‚ โ”œโ”€โ”€ Service/ -โ”‚ โ””โ”€โ”€ ... -โ”œโ”€โ”€ kube-system/ # System namespace -โ”‚ โ”œโ”€โ”€ Deployment/ -โ”‚ โ”œโ”€โ”€ Service/ -โ”‚ โ””โ”€โ”€ ... -โ””โ”€โ”€ kalco-reports/ # Analysis reports - โ”œโ”€โ”€ Cluster-snapshot-2025-08-16-14-55-34.md - โ””โ”€โ”€ ... + +Switch between contexts: + +```bash +# Switch to a different context +kalco context use another-cluster + +# Switch back +kalco context use my-cluster ``` -## ๐Ÿ” View the Report +### Your First Export -Check the generated report to understand your cluster: +The export command is Kalco's core functionality: ```bash -# View the latest report -ls -la ./my-cluster-backup/kalco-reports/ -cat ./my-cluster-backup/kalco-reports/*.md | head -50 +# Basic export +kalco export + +# Export with Git integration +kalco export --git-push --commit-message "Initial backup" + +# Export to specific directory +kalco export --output ./custom-backup + +# Export specific namespaces +kalco export --namespaces default,kube-system +``` + +### Understanding Export Output + +After export, you'll see: + +1. **Resource Discovery** - Kalco finds all available resources +2. **Directory Creation** - Organized structure is created +3. **Git Setup** - Repository is initialized (if new) +4. **Resource Export** - YAML files are created +5. **Report Generation** - Change analysis report is created +6. **Git Commit** - Changes are committed with timestamp + +### Output Structure + +Your export creates this directory structure: + +``` +my-cluster-exports/ +โ”œโ”€โ”€ default/ +โ”‚ โ”œโ”€โ”€ pods/ +โ”‚ โ”‚ โ”œโ”€โ”€ nginx-pod.yaml +โ”‚ โ”‚ โ””โ”€โ”€ mysql-pod.yaml +โ”‚ โ”œโ”€โ”€ services/ +โ”‚ โ”‚ โ”œโ”€โ”€ nginx-service.yaml +โ”‚ โ”‚ โ””โ”€โ”€ mysql-service.yaml +โ”‚ โ””โ”€โ”€ deployments/ +โ”‚ โ”œโ”€โ”€ nginx-deployment.yaml +โ”‚ โ””โ”€โ”€ mysql-deployment.yaml +โ”œโ”€โ”€ kube-system/ +โ”‚ โ”œโ”€โ”€ pods/ +โ”‚ โ””โ”€โ”€ services/ +โ”œโ”€โ”€ _cluster/ +โ”‚ โ”œโ”€โ”€ nodes/ +โ”‚ โ””โ”€โ”€ namespaces/ +โ”œโ”€โ”€ kalco-reports/ +โ”‚ โ””โ”€โ”€ 20240819-145542-Initial-cluster-snapshot.md +โ””โ”€โ”€ kalco-config.json ``` -## ๐Ÿ“Š What the Report Shows +### Git Integration -The report includes: +Kalco automatically handles Git operations: -- **Resource Summary** - Count of each resource type -- **Validation Results** - Cross-reference checks -- **Orphaned Resources** - Unmanaged resources -- **Detailed Changes** - Since previous snapshot (if any) +1. **Repository Initialization** - Creates Git repo if needed +2. **File Addition** - Adds all exported resources +3. **Commit Creation** - Commits with your message or timestamp +4. **Remote Push** - Pushes to origin if `--git-push` is used -## ๐Ÿ”„ Git Integration +### Generated Reports -Initialize Git tracking for version control: +Kalco creates comprehensive reports in `kalco-reports/`: + +- **Change Summary** - Overview of modifications +- **Resource Details** - Specific changes with diffs +- **Validation Results** - Cross-reference and orphaned resource checks +- **Git Information** - Commit details and history + +## Common Scenarios + +### Development Cluster + +```bash +# Set up development context +kalco context set dev-cluster \ + --kubeconfig ~/.kube/dev-config \ + --output ./dev-exports \ + --description "Local development cluster" \ + --labels env=dev,team=developers + +# Export development resources +kalco context use dev-cluster +kalco export --commit-message "Development snapshot" +``` + +### Production Cluster ```bash -cd ./my-cluster-backup +# Set up production context +kalco context set prod-cluster \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports \ + --description "Production cluster for customer workloads" \ + --labels env=prod,team=platform + +# Export with Git push +kalco context use prod-cluster +kalco export --git-push --commit-message "Production backup $(date)" +``` -# Initialize Git repository -git init -git add . -git commit -m "Initial cluster export - $(date)" +### Multi-Cluster Setup -# Add remote origin (optional) -git remote add origin -git push -u origin main +```bash +# Create contexts for different environments +kalco context set staging \ + --kubeconfig ~/.kube/staging-config \ + --output ./staging-exports \ + --labels env=staging,team=qa + +kalco context set production \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports \ + --labels env=prod,team=platform + +# Export all environments +kalco context use staging +kalco export --commit-message "Staging backup" + +kalco context use production +kalco export --commit-message "Production backup" ``` -## ๐ŸŽฏ Next Steps +## Best Practices + +### Context Management + +1. **Use descriptive names** for contexts +2. **Include labels** for better organization +3. **Set meaningful output directories** +4. **Document context purposes** with descriptions + +### Export Strategy -Now that you've completed your first export: +1. **Start with basic exports** to understand the process +2. **Use meaningful commit messages** for better Git history +3. **Enable Git push** for team collaboration +4. **Exclude noisy resources** like events and replicasets -1. **[Explore Commands]({{ site.baseurl }}/docs/commands/)** - Learn about all available options -2. **[Configure Kalco]({{ site.baseurl }}/docs/getting-started/configuration)** - Customize for your workflow -3. **[Use Cases]({{ site.baseurl }}/docs/use-cases/)** - Common scenarios and workflows +### Organization -## ๐Ÿ› Troubleshooting +1. **Group contexts by environment** (dev, staging, prod) +2. **Use consistent naming patterns** across your organization +3. **Include team and region information** in labels +4. **Regularly review and clean up** unused contexts + +## Troubleshooting ### Common Issues -**Permission denied**: Ensure you have cluster access +#### Context Not Found + +```bash +Error: context 'my-cluster' not found +``` + +**Solution**: Use `kalco context list` to see available contexts. + +#### Permission Denied + +```bash +Error: failed to create output directory +``` + +**Solution**: Ensure write permissions for the output directory. + +#### Git Not Found + ```bash -kubectl auth can-i get pods --all-namespaces +Error: failed to initialize Git repository ``` -**Empty export**: Check if resources exist +**Solution**: Install Git and ensure it's in your PATH. + +#### Kubernetes Connection Failed + ```bash -kubectl get all --all-namespaces +Error: failed to create Kubernetes clients ``` -**Report not generated**: Verify output directory permissions +**Solution**: Verify kubeconfig and cluster access. + +### Getting Help + +- **Command help**: `kalco --help` +- **Verbose output**: Use `--verbose` flag for detailed information +- **Context help**: `kalco context --help` +- **Export help**: `kalco export --help` + +## Next Steps + +After your first successful export: + +1. **Explore the output structure** to understand organization +2. **Review the generated report** for insights about your cluster +3. **Set up additional contexts** for other clusters +4. **Configure automated exports** for regular backups +5. **Read the [Commands Reference](../commands/index.md)** for advanced usage + +## Examples + +### Complete First Run + ```bash -ls -la ./my-cluster-backup/ +# 1. Verify installation +kalco version + +# 2. Create context +kalco context set my-cluster \ + --kubeconfig ~/.kube/config \ + --output ./my-cluster-exports \ + --description "My first Kubernetes cluster" \ + --labels env=dev,team=personal + +# 3. Use context +kalco context use my-cluster + +# 4. Export cluster +kalco export --git-push --commit-message "Initial snapshot" + +# 5. Verify results +ls -la ./my-cluster-exports/ +git log --oneline ./my-cluster-exports/ ``` -Congratulations! You've successfully exported your first Kubernetes cluster with Kalco. ๐ŸŽ‰ +### Team Collaboration + +```bash +# Load team member's context +kalco context load ~/team-exports/prod-cluster + +# Use shared context +kalco context use prod-cluster + +# Export with team context +kalco export --git-push --commit-message "Team backup $(date)" +``` + +--- + +*For more information about using Kalco, see the [Commands Reference](../commands/index.md) or run `kalco --help`.* diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 9d74c8b..f982f24 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -7,23 +7,47 @@ parent: Getting Started # Installation -Get Kalco up and running on your system with these simple installation methods. +This guide covers installing Kalco on various operating systems and platforms. -## ๐Ÿš€ Quick Install +## Prerequisites -The fastest way to get started is using Go's built-in installer: +Before installing Kalco, ensure you have: + +- **Kubernetes Access** - Valid kubeconfig or in-cluster access +- **Git** (optional) - For version control functionality +- **Go 1.21+** (if building from source) - [Download here](https://golang.org/dl/) + +## Quick Install + +### Linux/macOS + +Install Kalco with a single command: ```bash -go install github.com/graz-dev/kalco/cmd/kalco@latest +curl -fsSL https://raw.githubusercontent.com/graz-dev/kalco/master/scripts/install.sh | bash ``` -## ๐Ÿ“ฆ Package Managers +The script will: +- Detect your operating system and architecture +- Download the appropriate binary +- Install it to `/usr/local/bin/kalco` +- Make it executable + +### Windows (PowerShell) + +Install Kalco on Windows using PowerShell: + +```powershell +iwr -useb https://raw.githubusercontent.com/graz-dev/kalco/master/scripts/install.ps1 | iex +``` + +## Package Managers ### Homebrew (macOS/Linux) ```bash -# Add the tap (when available) -brew tap graz-dev/kalco +# Add the tap +brew tap graz-dev/tap # Install Kalco brew install kalco @@ -31,84 +55,266 @@ brew install kalco ### Manual Download -Download the latest release for your platform from the [releases page](https://github.com/graz-dev/kalco/releases). +Download the appropriate binary for your platform: + +1. Visit the [releases page](https://github.com/graz-dev/kalco/releases) +2. Download the binary for your OS and architecture +3. Extract and move to your PATH + +**Linux:** +```bash +# Download and extract +wget https://github.com/graz-dev/kalco/releases/latest/download/kalco_Linux_x86_64.tar.gz +tar -xzf kalco_Linux_x86_64.tar.gz + +# Move to PATH +sudo mv kalco /usr/local/bin/ +``` + +**macOS:** +```bash +# Download and extract +curl -L https://github.com/graz-dev/kalco/releases/latest/download/kalco_Darwin_x86_64.tar.gz | tar -xz + +# Move to PATH +sudo mv kalco /usr/local/bin/ +``` + +**Windows:** +```powershell +# Download and extract +Invoke-WebRequest -Uri "https://github.com/graz-dev/kalco/releases/latest/download/kalco_Windows_x86_64.zip" -OutFile "kalco.zip" +Expand-Archive -Path "kalco.zip" -DestinationPath "kalco" + +# Add to PATH (requires admin) +[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\kalco", [EnvironmentVariableTarget]::Machine) +``` + +## Build from Source + +### Prerequisites -## ๐Ÿ”จ Build from Source +- **Go 1.21+** - [Download and install](https://golang.org/dl/) +- **Git** - For cloning the repository -If you prefer to build from source: +### Build Steps ```bash # Clone the repository git clone https://github.com/graz-dev/kalco.git cd kalco -# Build the binary -go build -o kalco ./cmd +# Install dependencies +go mod tidy -# Make it executable -chmod +x kalco +# Build the binary +go build -o kalco -# Move to your PATH +# Make it available system-wide (optional) sudo mv kalco /usr/local/bin/ ``` -## โœ… Verify Installation +### Development Build + +For development and testing: + +```bash +# Clone and build +git clone https://github.com/graz-dev/kalco.git +cd kalco +go build -o kalco + +# Run locally +./kalco --help +``` + +## Verification + +After installation, verify Kalco is working correctly: + +```bash +# Check version +kalco version + +# Show help +kalco --help + +# Test context command +kalco context --help +``` + +Expected output should show: +- Version information +- Available commands (context, export, completion, version) +- Global flags and options + +## Configuration + +### Initial Setup -Check that Kalco is properly installed: +Kalco creates its configuration directory on first run: ```bash -kalco --version +# First run creates ~/.kalco/ +kalco context list ``` -You should see output similar to: +The configuration directory structure: ``` -kalco version v0.1.0 (commit: abc1234, date: 2025-08-16) +~/.kalco/ +โ”œโ”€โ”€ contexts.yaml # Context configurations +โ”œโ”€โ”€ current-context # Currently active context +โ””โ”€โ”€ config.json # Global configuration ``` -## ๐Ÿ”ง Shell Completion +### Environment Variables + +Kalco respects these environment variables: + +| Variable | Description | Default | +|----------|-------------|---------| +| `KUBECONFIG` | Path to kubeconfig file | `~/.kube/config` | +| `KALCO_CONFIG_DIR` | Configuration directory | `~/.kalco` | +| `NO_COLOR` | Disable colored output | `false` | + +## Shell Completion + +Enable shell completion for better user experience: ### Bash ```bash -# Add to your ~/.bashrc -echo 'source <(kalco completion bash)' >> ~/.bashrc -source ~/.bashrc +# Generate completion script +kalco completion bash > /etc/bash_completion.d/kalco + +# Or for current user +kalco completion bash > ~/.bash_completion.d/kalco + +# Source in current shell +source <(kalco completion bash) ``` ### Zsh ```bash -# Add to your ~/.zshrc -echo 'source <(kalco completion zsh)' >> ~/.zshrc -source ~/.zshrc +# Generate completion script +kalco completion zsh > ~/.zsh/completion/_kalco + +# Add to .zshrc +echo 'fpath=(~/.zsh/completion $fpath)' >> ~/.zshrc +echo 'autoload -U compinit && compinit' >> ~/.zshrc ``` ### Fish ```bash +# Generate completion script kalco completion fish > ~/.config/fish/completions/kalco.fish ``` -## ๐Ÿ› Troubleshooting +### PowerShell + +```powershell +# Generate completion script +kalco completion powershell > kalco-completion.ps1 + +# Source in current session +. .\kalco-completion.ps1 +``` + +## Troubleshooting + +### Common Installation Issues + +#### Permission Denied + +```bash +Error: permission denied +``` + +**Solutions:** +- Use `sudo` for system-wide installation +- Check file permissions on the binary +- Verify PATH configuration + +#### Binary Not Found + +```bash +kalco: command not found +``` + +**Solutions:** +- Verify the binary is in your PATH +- Check if the installation completed successfully +- Restart your terminal session -### Common Issues +#### Go Version Issues -**Command not found**: Ensure Kalco is in your PATH ```bash -echo $PATH -which kalco +go: version go1.20 is not supported ``` -**Permission denied**: Make the binary executable +**Solutions:** +- Update to Go 1.21 or later +- Use the pre-built binary instead of building from source + +#### Architecture Mismatch + +```bash +cannot execute binary file: Exec format error +``` + +**Solutions:** +- Download the correct binary for your architecture +- Verify you're using the right OS/architecture combination + +### Getting Help + +- **Installation issues**: [GitHub Issues](https://github.com/graz-dev/kalco/issues) +- **Build problems**: Check Go version and dependencies +- **Binary issues**: Try downloading the pre-built binary + +## Next Steps + +After successful installation: + +1. **Read the [First Run](first-run.md) guide** to get started +2. **Set up your first context** with `kalco context set` +3. **Export your cluster** with `kalco export` +4. **Explore the [Commands Reference](../commands/index.md)** + +## Uninstallation + +### Remove Binary + ```bash -chmod +x kalco +# Remove from system PATH +sudo rm /usr/local/bin/kalco + +# Or if installed in user directory +rm ~/bin/kalco ``` -**Go version issues**: Ensure you have Go 1.19+ installed +### Remove Configuration + ```bash -go version +# Remove configuration directory +rm -rf ~/.kalco ``` -## ๐Ÿ“š Next Steps +### Remove Shell Completion + +```bash +# Bash +sudo rm /etc/bash_completion.d/kalco + +# Zsh +rm ~/.zsh/completion/_kalco + +# Fish +rm ~/.config/fish/completions/kalco.fish +``` + +--- -Once Kalco is installed, proceed to [First Run]({{ site.baseurl }}/docs/getting-started/first-run) to export your first cluster. +*For more installation help, see [GitHub Issues](https://github.com/graz-dev/kalco/issues) or [Discussions](https://github.com/graz-dev/kalco/discussions).* diff --git a/docs/index.md b/docs/index.md index 0c28448..7147fc6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,64 +4,103 @@ title: Home nav_order: 1 --- -# โ˜ธ๏ธ Kalco - Kubernetes Analysis & Lifecycle Control +# Kalco Documentation -**Extract, validate, analyze, and version control your entire Kubernetes cluster with comprehensive validation and Git integration** +Welcome to the Kalco documentation. Kalco is a professional CLI tool for Kubernetes cluster analysis, resource extraction, validation, and lifecycle management. -{: .fs-6 .fw-300 } +## What is Kalco? ---- +Kalco transforms your Kubernetes cluster management experience by providing a comprehensive, automated, and intelligent approach to cluster analysis and lifecycle control. Whether you're managing production workloads, ensuring compliance, or planning migrations, Kalco has you covered. + +## Key Features + +- **Context Management** - Manage multiple Kubernetes clusters through unified contexts +- **Resource Export** - Export cluster resources with professional organization +- **Git Integration** - Automatic version control with commit history and change tracking +- **Report Generation** - Professional change analysis and validation reports +- **Enterprise Ready** - Designed for production environments and team collaboration + +## Quick Start + +1. **Install Kalco:** + ```bash + curl -fsSL https://raw.githubusercontent.com/graz-dev/kalco/master/scripts/install.sh | bash + ``` + +2. **Create a Context:** + ```bash + kalco context set production \ + --kubeconfig ~/.kube/prod-config \ + --output ./prod-exports + ``` + +3. **Export Cluster Resources:** + ```bash + kalco export --git-push --commit-message "Initial backup" + ``` + +## Documentation Sections + +### Getting Started +- [Installation](getting-started/installation.md) - Install Kalco on your system +- [First Run](getting-started/first-run.md) - Get started with your first export +- [Configuration](getting-started/configuration.md) - Configure Kalco for your environment + +### Commands Reference +- [Command Overview](commands/index.md) - Complete command reference +- [Export Command](commands/export.md) - Export cluster resources +- [Context Management](commands/context.md) - Manage cluster contexts -## ๐Ÿš€ What is Kalco? +### Examples +- [Quickstart Demo](../examples/quickstart.sh) - Comprehensive example script +- [Production Workflows](examples/production.md) - Real-world usage examples +- [CI/CD Integration](examples/cicd.md) - Automation examples -Kalco is a powerful Kubernetes cluster management tool that provides comprehensive resource extraction, validation, and version control capabilities. It's designed to help DevOps engineers, SREs, and platform teams maintain clean, validated, and version-controlled cluster configurations. +## Use Cases -## โœจ Key Features +### DevOps Teams +- Automated cluster backups and disaster recovery +- Change tracking and compliance auditing +- Environment replication and configuration management -- **๐Ÿ” Complete Resource Extraction** - Export all cluster resources including CRDs -- **โœ… Smart Validation** - Cross-reference validation and orphaned resource detection -- **๐Ÿ“ Git Integration** - Automatic version control with commit and push capabilities -- **๐ŸŽฏ Flexible Filtering** - Export specific namespaces, resources, or exclude noisy types -- **๐Ÿ“Š Detailed Reporting** - Comprehensive change analysis and resource summaries -- **๐Ÿ”„ Incremental Updates** - Track changes between cluster snapshots -- **๐ŸŒ Context Management** - Manage multiple clusters with easy switching +### Platform Engineers +- Infrastructure as Code and GitOps workflows +- Team collaboration and context sharing +- Migration support and configuration validation -## ๐Ÿš€ Quick Start +### Security Teams +- Configuration auditing and compliance reporting +- Access control through context management +- Security validation and change monitoring -```bash -# Install Kalco -go install github.com/graz-dev/kalco/cmd/kalco@latest +## Architecture -# Set up a cluster context -kalco context set production \ - --kubeconfig ~/.kube/prod-config \ - --output ./prod-exports \ - --labels env=prod,team=platform +Kalco is built with a modular architecture designed for enterprise use: -# Use the context -kalco context use production +- **Context Manager** - Handles cluster configurations and settings +- **Resource Exporter** - Discovers and exports Kubernetes resources +- **Git Integration** - Manages version control operations +- **Report Generator** - Creates change analysis and validation reports +- **Validation Engine** - Performs cross-reference and orphaned resource checks -# Export your cluster -kalco export +## Design Principles -# Export with Git integration -kalco export --git-push --commit-message "Cluster snapshot $(date)" -``` +- **Professional Interface** - Clean, emoji-free CLI design +- **Minimal Dependencies** - Focused functionality without bloat +- **Enterprise Ready** - Production-grade reliability and performance +- **Team Collaboration** - Shared configurations and context sharing +- **Automation First** - Designed for CI/CD and automated workflows -## ๐Ÿ“š Documentation +## Support -- **[Getting Started]({{ site.baseurl }}/getting-started/)** - Installation and first steps -- **[Commands Reference]({{ site.baseurl }}/commands/)** - Complete command documentation -- **[Use Cases]({{ site.baseurl }}/use-cases/)** - Common scenarios and workflows -- **[Configuration]({{ site.baseurl }}/configuration/)** - Customization options +- **GitHub Issues**: [Report bugs and request features](https://github.com/graz-dev/kalco/issues) +- **Discussions**: [Join community discussions](https://github.com/graz-dev/kalco/discussions) +- **Documentation**: This site and [GitHub repository](https://github.com/graz-dev/kalco) -## ๐Ÿ”— Useful Links +## Contributing -- [GitHub Repository](https://github.com/graz-dev/kalco) -- [Issues & Bug Reports](https://github.com/graz-dev/kalco/issues) -- [Discussions](https://github.com/graz-dev/kalco/discussions) -- [Releases](https://github.com/graz-dev/kalco/releases) +We welcome contributions to improve Kalco and its documentation. See our [contributing guidelines](https://github.com/graz-dev/kalco/blob/master/CONTRIBUTING.md) for details. --- -*Built with โค๏ธ for the Kubernetes community* +*Kalco - Built with โค๏ธ for the Kubernetes community* diff --git a/examples/README.md b/examples/README.md index 3ce6dab..1ed8060 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,116 +1,122 @@ # Kalco Examples -This directory contains practical examples and scripts demonstrating how to use Kalco effectively. +This directory contains practical examples demonstrating how to use Kalco for Kubernetes cluster management and analysis. -## ๐Ÿ“ Available Examples +## Available Examples -### ๐Ÿš€ Quick Start -- **[quickstart.sh](quickstart.sh)** - Complete workflow from cluster creation to export and analysis +### Quickstart Script (`quickstart.sh`) -### ๐ŸŒ Context Management -- **[context-example.sh](context-example.sh)** - Demonstrates context management for multiple clusters +A comprehensive demonstration of Kalco's core functionality: -## ๐ŸŽฏ Quick Start Example - -The `quickstart.sh` script provides a complete end-to-end example: +- **Context Management**: Creating and managing cluster contexts +- **Resource Export**: Simulating cluster resource export and organization +- **Git Integration**: Demonstrating version control capabilities +- **Report Generation**: Showing how change reports are structured +**Usage:** ```bash -# Make executable and run -chmod +x examples/quickstart.sh +# Run the complete quickstart demo ./examples/quickstart.sh + +# Keep the demo directory for inspection +./examples/quickstart.sh --keep ``` -This example: -1. Creates a Kind cluster -2. Deploys a sample application -3. Runs Kalco export -4. Makes changes to the cluster -5. Runs Kalco again to see changes -6. Cleans up the cluster +**What you'll learn:** +- How to create and manage contexts with `kalco context` +- How contexts store cluster configuration and output directories +- How Kalco organizes exported resources in a structured way +- How Git integration works for version control +- How `kalco-config.json` stores context information -## ๐Ÿ”„ Context Management Example +## Prerequisites -The `context-example.sh` script demonstrates context management: +Before running the example, ensure you have: -```bash -# Make executable and run -chmod +x examples/context-example.sh -./examples/context-example.sh -``` +1. **Kalco installed** - Download from [releases](https://github.com/graz-dev/kalco/releases) or build from source +2. **Git available** - For version control functionality +3. **Basic Kubernetes knowledge** - Understanding of clusters, namespaces, and resources -This example: -1. Creates multiple contexts for different environments -2. Shows context switching -3. Demonstrates automatic context usage in export -4. Shows context override with flags -5. Cleans up example contexts +## Running the Example -## ๐Ÿ› ๏ธ Prerequisites +### Quick Start -Before running the examples, ensure you have: +1. **Clone the repository:** + ```bash + git clone https://github.com/graz-dev/kalco.git + cd kalco + ``` -- **Kalco** installed and in your PATH -- **Docker** running (for Kind clusters) -- **Kind** installed (for local clusters) -- **kubectl** configured +2. **Install Kalco:** + ```bash + # Quick install + curl -fsSL https://raw.githubusercontent.com/graz-dev/kalco/master/scripts/install.sh | bash + + # Or build from source + go mod tidy + go build -o kalco + ``` -## ๐Ÿ“š Learning Path +3. **Run the example:** + ```bash + # Make script executable + chmod +x examples/quickstart.sh + + # Run quickstart + ./examples/quickstart.sh + ``` -1. **Start with quickstart.sh** - Learn basic Kalco operations -2. **Try context-example.sh** - Master context management -3. **Experiment with your own clusters** - Apply concepts to real scenarios +### Example Output -## ๐Ÿ”ง Customization +The example will show you: -Feel free to modify these examples: +- **Context Creation**: How to set up contexts for different environments +- **Resource Organization**: How Kalco structures exported resources +- **Git Integration**: How version control is automatically handled +- **Configuration Management**: How contexts store cluster settings +- **Report Generation**: How change tracking and validation work -- Change cluster names and configurations -- Add your own applications and resources -- Modify export parameters and filters -- Customize context labels and descriptions +## Customizing the Example -## ๐Ÿ› Troubleshooting +You can modify this example to: -### Common Issues +- **Use Real Clusters**: Replace simulated resources with actual Kubernetes clusters +- **Add Custom Resources**: Include your organization's specific resource types +- **Modify Contexts**: Adjust context configurations for your environment +- **Extend Workflows**: Add additional steps specific to your use case -**Permission denied** -```bash -chmod +x examples/*.sh -``` +## Next Steps -**Command not found** -```bash -# Ensure Kalco is in your PATH -which kalco -``` +After running the example: -**Cluster connection issues** -```bash -# Check cluster status -kubectl cluster-info -kubectl get nodes -``` +1. **Try with Real Clusters**: Use `kalco export` with your actual Kubernetes clusters +2. **Explore Reports**: Examine the generated reports in `kalco-reports/` directories +3. **Share Contexts**: Use `kalco context load` to import configurations from team members +4. **Read Documentation**: Visit [https://graz-dev.github.io/kalco](https://graz-dev.github.io/kalco) +5. **Join Community**: Participate in [GitHub discussions](https://github.com/graz-dev/kalco/discussions) + +## Troubleshooting -## ๐Ÿ“– Next Steps +### Common Issues -After running the examples: +- **Permission Denied**: Ensure script is executable with `chmod +x examples/quickstart.sh` +- **Kalco Not Found**: Verify Kalco is installed and in your PATH +- **Git Not Available**: Install Git for version control functionality +- **Directory Issues**: Ensure you have write permissions in the current directory -1. **Read the documentation** - Explore the full command reference -2. **Try different clusters** - Test with your own Kubernetes clusters -3. **Customize contexts** - Create contexts for your specific use cases -4. **Integrate with CI/CD** - Automate exports in your pipelines +### Getting Help -## ๐Ÿค Contributing +- **Documentation**: [https://graz-dev.github.io/kalco](https://graz-dev.github.io/kalco) +- **Issues**: [GitHub Issues](https://github.com/graz-dev/kalco/issues) +- **Discussions**: [GitHub Discussions](https://github.com/graz-dev/kalco/discussions) -Have a great example? Feel free to contribute: +## Contributing -1. Create a new script with descriptive name -2. Add clear comments and documentation -3. Include error handling and cleanup -4. Update this README with your example +We welcome contributions to improve this example: -## ๐Ÿ“š Resources +- **Bug Reports**: Report issues with the example +- **Enhancements**: Suggest improvements +- **Documentation**: Help improve example descriptions and usage +- **New Examples**: Submit examples for additional use cases -- [Kalco Documentation](../docs/) -- [Commands Reference](../docs/commands/) -- [Getting Started](../docs/getting-started/) +For more information, see the [main README](../README.md) and [contributing guidelines](../CONTRIBUTING.md). diff --git a/examples/context-example.sh b/examples/context-example.sh deleted file mode 100755 index 937d6f2..0000000 --- a/examples/context-example.sh +++ /dev/null @@ -1,154 +0,0 @@ -#!/bin/bash - -# Kalco Context Management Example -# This script demonstrates how to use Kalco contexts for managing multiple clusters - -set -e - -echo "๐Ÿš€ Kalco Context Management Example" -echo "==================================" -echo - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Function to print colored output -print_info() { - echo -e "${BLUE}โ„น๏ธ $1${NC}" -} - -print_success() { - echo -e "${GREEN}โœ… $1${NC}" -} - -print_warning() { - echo -e "${YELLOW}โš ๏ธ $1${NC}" -} - -print_error() { - echo -e "${RED}โŒ $1${NC}" -} - -# Check if kalco is available (use local binary if available, otherwise system) -KALCO_CMD="kalco" -if [ -f "./kalco" ]; then - KALCO_CMD="./kalco" - print_info "Using local kalco binary" -elif command -v kalco &> /dev/null; then - print_info "Using system kalco binary" -else - print_error "Kalco is not available. Please build it first with 'make build' or install it." - exit 1 -fi - -print_success "Kalco is available and ready to use" -echo - -# Create example contexts for different environments -print_info "Creating example contexts for different environments..." - -# Production context -print_info "Setting up production context..." -$KALCO_CMD context set production \ - --kubeconfig ~/.kube/config \ - --output ./exports/production \ - --description "Production cluster for live workloads" \ - --labels env=production \ - --labels team=platform \ - --labels region=eu-west - -# Staging context -print_info "Setting up staging context..." -$KALCO_CMD context set staging \ - --kubeconfig ~/.kube/config \ - --output ./exports/staging \ - --description "Staging cluster for testing" \ - --labels env=staging \ - --labels team=qa \ - --labels purpose=testing - -# Development context -print_info "Setting up development context..." -$KALCO_CMD context set development \ - --kubeconfig ~/.kube/config \ - --output ./exports/development \ - --description "Development cluster for engineers" \ - --labels env=development \ - --labels team=engineering \ - --labels purpose=development - -print_success "All contexts created successfully" -echo - -# List all contexts -print_info "Listing all available contexts..." -$KALCO_CMD context list -echo - -# Switch between contexts and show current -print_info "Demonstrating context switching..." - -print_info "Switching to production context..." -$KALCO_CMD context use production -$KALCO_CMD context current -echo - -print_info "Switching to staging context..." -$KALCO_CMD context use staging -$KALCO_CMD context current -echo - -print_info "Switching to development context..." -$KALCO_CMD context use development -$KALCO_CMD context current -echo - -# Show how export uses context automatically -print_info "Demonstrating automatic context usage in export command..." -print_info "Current context: development" -$KALCO_CMD export --dry-run -echo - -# Switch back to production and export -print_info "Switching to production and exporting..." -$KALCO_CMD context use production -$KALCO_CMD export --dry-run -echo - -# Show context override with flags -print_info "Demonstrating context override with command-line flags..." -$KALCO_CMD export --dry-run --output ./override-output -echo - -# Show context details -print_info "Showing detailed context information..." -$KALCO_CMD context show production -echo - -# Cleanup example contexts -print_warning "Cleaning up example contexts..." -$KALCO_CMD context use development -$KALCO_CMD context delete production -$KALCO_CMD context use staging -$KALCO_CMD context delete development -$KALCO_CMD context use temp -$KALCO_CMD context delete staging - -print_success "Example contexts cleaned up" -echo - -print_success "Context management example completed successfully!" -echo -print_info "Key takeaways:" -echo " โ€ข Use 'kalco context set' to create contexts" -echo " โ€ข Use 'kalco context use' to switch between contexts" -echo " โ€ข Contexts automatically configure kubeconfig and output directory" -echo " โ€ข Command-line flags can override context settings" -echo " โ€ข Use 'kalco context list' to see all available contexts" -echo " โ€ข Use 'kalco context current' to see active context" -echo -print_info "For more information, run: kalco context --help" diff --git a/examples/quickstart.sh b/examples/quickstart.sh index f5db8ff..e1f66e7 100755 --- a/examples/quickstart.sh +++ b/examples/quickstart.sh @@ -1,15 +1,14 @@ #!/bin/bash -# Kalco Simple Quickstart Script -# This script demonstrates a real, cohesive application with: -# - Echo server deployment -# - Service and Ingress -# - Real CRD from kube-green operator (SleepInfo) -# - Cross-reference validation -# - Orphaned resource detection +# Kalco Quickstart Script +# This script demonstrates the core functionality of Kalco set -e +echo "Kalco Quickstart - Kubernetes Analysis & Lifecycle Control" +echo "==========================================================" +echo + # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' @@ -17,503 +16,181 @@ YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color -# Helper functions +# Function to print colored output print_status() { - echo -e "${GREEN}โœ… $1${NC}" + echo -e "${BLUE}[INFO]${NC} $1" } -print_info() { - echo -e "${BLUE}โ„น๏ธ $1${NC}" +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { - echo -e "${YELLOW}โš ๏ธ $1${NC}" + echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { - echo -e "${RED}โŒ $1${NC}" -} - -print_feature() { - echo -e "${BLUE}๐Ÿ” $1${NC}" + echo -e "${RED}[ERROR]${NC} $1" } -# Script header -echo "๐Ÿš€ Kalco Simple Quickstart Demo" -echo "================================" -echo "" -echo "This demo shows a real, cohesive application with:" -echo "- ๐Ÿš€ HTTP Echo server deployment" -echo "- ๐ŸŒ Service and Ingress" -echo "- ๐Ÿ”ง Real kube-green operator with SleepInfo CRD" -echo "- ๐Ÿ” Cross-reference validation" -echo "- ๐Ÿ—‘๏ธ Orphaned resource detection" -echo "" - -# Check prerequisites -echo "๐Ÿ” Checking prerequisites..." -if ! command -v kubectl &> /dev/null; then - print_error "kubectl is required but not installed" - exit 1 -fi - -if ! command -v kind &> /dev/null; then - print_error "kind is required but not installed" +# Check if kalco is installed +if ! command -v kalco &> /dev/null; then + print_error "Kalco is not installed. Please install it first." + print_info "Run: curl -fsSL https://raw.githubusercontent.com/graz-dev/kalco/master/scripts/install.sh | bash" exit 1 fi -if ! command -v go &> /dev/null; then - print_error "go is required but not installed" - exit 1 -fi +print_success "Kalco is installed: $(kalco version)" +# Create a temporary directory for this demo +DEMO_DIR=$(mktemp -d) +print_status "Created demo directory: $DEMO_DIR" +cd "$DEMO_DIR" +# Create a test context +print_status "Creating test context..." +kalco context set demo \ + --kubeconfig ~/.kube/config \ + --output ./demo-exports \ + --description "Demo context for quickstart" \ + --labels env=demo,team=platform -print_status "All prerequisites are available" +print_success "Context 'demo' created successfully" -# Build kalco -echo "" -echo "โ„น๏ธ Building kalco..." -go build -o kalco -print_status "kalco built successfully" +# List contexts +print_status "Listing available contexts..." +kalco context list -# Create test cluster -echo "" -echo "๐Ÿ—๏ธ Creating test cluster..." -kind create cluster --name kalco-quickstart --wait 2m -print_status "Test cluster created" +# Use the demo context +print_status "Switching to demo context..." +kalco context use demo -# Wait for cluster to be ready -echo "" -echo "โณ Waiting for cluster to be ready..." -sleep 10 # Give the cluster a moment to fully initialize -kubectl wait --for=condition=Ready node/kalco-quickstart-control-plane --timeout=60s -print_status "Cluster is ready" +print_success "Now using context 'demo'" -# Create a simple, real application -echo "" -echo "๐Ÿ“ฆ Creating a simple, real application..." +# Show current context +print_status "Current context details:" +kalco context current -# Create namespace for our demo app -kubectl create namespace demo-app --dry-run=client -o yaml | kubectl apply -f - +# Create a simple test file to simulate cluster resources +print_status "Creating test cluster resources..." +mkdir -p demo-exports/default/pods +mkdir -p demo-exports/default/services +mkdir -p demo-exports/_cluster/namespaces -# Create ConfigMap for echo server configuration -kubectl apply -f - < demo-exports/default/pods/nginx.yaml << 'EOF' apiVersion: v1 -kind: ConfigMap -metadata: - name: echo-config - namespace: demo-app - labels: - app: echo-server - tier: backend -data: - environment: "development" - log-level: "info" - app-version: "1.0.0" - description: "Simple echo server for demo purposes" -EOF - -# Create Deployment for echo server -kubectl apply -f - < demo-exports/default/services/nginx-service.yaml << 'EOF' apiVersion: v1 kind: Service metadata: - name: echo-service - namespace: demo-app - labels: - app: echo-server - tier: backend + name: nginx-service + namespace: default spec: - type: ClusterIP + selector: + app: nginx ports: - port: 80 targetPort: 80 - protocol: TCP - name: http - selector: - app: echo-server - tier: backend -EOF - -# Create Ingress for external access -kubectl apply -f - < demo-exports/_cluster/namespaces/default.yaml << 'EOF' apiVersion: v1 -kind: ConfigMap +kind: Namespace metadata: - name: orphaned-config - namespace: demo-app - labels: - app: orphaned - tier: test -data: - orphaned: "true" - description: "This ConfigMap has no references and will be detected as orphaned" + name: default EOF -# Create orphaned Secret -kubectl apply -f - </dev/null || cat kalco-config.json +else + print_warning "kalco-config.json not found (this would be created by kalco export)" +fi cd .. # Cleanup -echo "" -echo "๐Ÿงน Cleaning up..." -kind delete cluster --name kalco-quickstart -print_status "Test cluster deleted" - -echo "" -echo "๐ŸŽ‰ Simple Quickstart Demo Completed!" -echo "====================================" -echo "" -echo "๐Ÿ“Š What was tested:" -echo "- โœ… Initial snapshot with Git repository creation" -echo "- โœ… Simple, cohesive application (HTTP echo server + service + ingress)" -echo "- โœ… Real kube-green operator with SleepInfo CRD" -echo "- โœ… Resource modification and change tracking" -echo "- โœ… Enhanced change report with validation" -echo "- โœ… Git history tracking" -echo "- ๐Ÿ” CROSS-REFERENCE VALIDATION" -echo " - โŒ Broken Service selectors (targeting non-existent deployments)" -echo " - โŒ Broken Ingress backends (non-existent services)" -echo "- ๐Ÿ—‘๏ธ ORPHANED RESOURCE DETECTION" -echo " - ๐Ÿ—‘๏ธ Orphaned ConfigMaps (unreferenced)" -echo " - ๐Ÿ—‘๏ธ Orphaned Secrets (unreferenced)" -echo "" -echo "๐Ÿ“ Your backup is preserved in: ./quickstart-demo/" -echo "๐Ÿ“‹ Enhanced reports with validation are in: ./quickstart-demo/kalco-reports/" -echo "" -echo "๐Ÿ” Key Features Demonstrated:" -echo "- ๐Ÿ†• New resources show complete YAML content" -echo "- โœ๏ธ Modified resources show Git diff with before/after" -echo "- ๐Ÿ“Š Change summaries with line counts and section tracking" -echo "- ๐Ÿ” Field-level change identification" -echo "- ๐Ÿ” CROSS-REFERENCE VALIDATION:" -echo " - โœ… Valid references tracking" -echo " - โŒ Broken references detection" -echo " - ๐Ÿ“‹ Actionable recommendations" -echo " - ๐Ÿ›ก๏ธ Reliability assurance for reapplying resources" -echo "- ๐Ÿ—‘๏ธ ORPHANED RESOURCE DETECTION:" -echo " - ๐Ÿ” Orphaned resource identification" -echo " - ๐Ÿ“Š Resource breakdown by type" -echo " - ๐Ÿ’ก Cleanup recommendations" -echo " - ๐Ÿงน Cluster cleanup guidance" -echo "- ๐Ÿ”ง REAL CRD SUPPORT:" -echo " - ๐ŸŒ kube-green operator installation" -echo " - ๐Ÿ“ฆ SleepInfo CRD resource creation" -echo " - ๐Ÿ” CRD validation and analysis" -echo "" -echo "๐Ÿ’ก Try viewing the reports to see kalco's functionality!" -echo "๐Ÿ” The Cross-Reference Validation section will show you exactly what's broken!" -echo "๐Ÿ—‘๏ธ The Orphaned Resource Detection will help you clean up your cluster!" -echo "๐Ÿ”ง CRD support ensures all your custom resources are properly handled!" +print_status "Cleaning up demo environment..." +if [ "$1" != "--keep" ]; then + rm -rf "$DEMO_DIR" + print_success "Demo directory cleaned up" +else + print_info "Demo directory kept at: $DEMO_DIR" + print_info "Run: rm -rf $DEMO_DIR to clean up manually" +fi + +echo +print_success "Quickstart completed successfully!" +echo +print_info "What you learned:" +print_info "โ€ข How to create and manage contexts with kalco context" +print_info "โ€ข How contexts store cluster configuration and output directories" +print_info "โ€ข How kalco organizes exported resources in a structured way" +print_info "โ€ข How Git integration works for version control" +print_info "โ€ข How kalco-config.json stores context information" +echo +print_info "Next steps:" +print_info "โ€ข Try kalco export with a real Kubernetes cluster" +print_info "โ€ข Explore the generated reports in kalco-reports/ directory" +print_info "โ€ข Use kalco context load to import existing exports" +print_info "โ€ข Read the documentation: https://graz-dev.github.io/kalco" +echo +print_info "For more examples and documentation, visit:" +print_info "https://github.com/graz-dev/kalco" diff --git a/pkg/git/git.go b/pkg/git/git.go index c3f890c..dafa728 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -23,15 +23,15 @@ func NewGitRepo(path string) *GitRepo { func (g *GitRepo) Init() error { // Check if .git directory already exists if g.IsGitRepo() { - fmt.Println(" ๐Ÿ“ฆ Using existing Git repository") + fmt.Println(" Using existing Git repository") return nil } // Check if directory is new (empty or doesn't exist) if g.isNewDirectory() { - fmt.Println(" ๐Ÿ†• New directory detected, initializing Git repository...") + fmt.Println(" New directory detected, initializing Git repository...") } else { - fmt.Println(" ๐Ÿ“ฆ Initializing Git repository in existing directory...") + fmt.Println(" Initializing Git repository in existing directory...") } // Initialize new Git repository @@ -46,7 +46,7 @@ func (g *GitRepo) Init() error { return fmt.Errorf("failed to create .gitignore: %w", err) } - fmt.Println(" โœ… Git repository initialized successfully") + fmt.Println(" Git repository initialized successfully") return nil } @@ -73,7 +73,7 @@ func (g *GitRepo) Commit(customMessage string) error { return fmt.Errorf("failed to commit changes: %w", err) } - fmt.Printf(" ๐Ÿ“ Committed changes: %s\n", message) + fmt.Printf(" Committed changes: %s\n", message) return nil } @@ -81,7 +81,7 @@ func (g *GitRepo) Commit(customMessage string) error { func (g *GitRepo) Push() error { // Check if remote origin exists if !g.HasRemoteOrigin() { - fmt.Println(" โ„น๏ธ No remote origin found, skipping push") + fmt.Println(" No remote origin found, skipping push") return nil } @@ -97,7 +97,7 @@ func (g *GitRepo) Push() error { } } - fmt.Println(" ๐Ÿš€ Pushed changes to remote origin") + fmt.Println(" Pushed changes to remote origin") return nil } @@ -115,7 +115,7 @@ func (g *GitRepo) SetupAndCommit(customMessage string, shouldPush bool) error { // Check if there are changes to commit if !g.hasChanges() { - fmt.Println(" โ„น๏ธ No changes detected, skipping commit") + fmt.Println(" No changes detected, skipping commit") return nil } @@ -126,17 +126,17 @@ func (g *GitRepo) SetupAndCommit(customMessage string, shouldPush bool) error { // Check for remote origin and ask user if they want to push if g.HasRemoteOrigin() { - fmt.Println(" ๐ŸŒ Remote origin detected!") + fmt.Println(" Remote origin detected!") if shouldPush { - fmt.Println(" ๐Ÿš€ Auto-push enabled, pushing to remote origin...") + fmt.Println(" Auto-push enabled, pushing to remote origin...") if err := g.Push(); err != nil { return err } } else { - fmt.Println(" ๐Ÿ’ก Use --git-push flag to automatically push changes to remote origin") + fmt.Println(" Use --git-push flag to automatically push changes to remote origin") } } else { - fmt.Println(" โ„น๏ธ No remote origin found") + fmt.Println(" No remote origin found") } return nil diff --git a/pkg/reports/reports.go b/pkg/reports/reports.go index db12487..3af76b1 100644 --- a/pkg/reports/reports.go +++ b/pkg/reports/reports.go @@ -48,7 +48,7 @@ func (r *ReportGenerator) GenerateReport(commitMessage string) error { // Generate validation report validationContent, err := r.generateValidationReport() if err != nil { - fmt.Printf(" โš ๏ธ Warning: Validation report generation failed: %v\n", err) + fmt.Printf(" Warning: Validation report generation failed: %v\n", err) } else { // Insert validation content before the footer content = strings.Replace(content, "\n\n---\n*Report generated automatically by kalco*", "\n\n"+validationContent+"\n\n---\n*Report generated automatically by kalco*", 1) @@ -59,7 +59,7 @@ func (r *ReportGenerator) GenerateReport(commitMessage string) error { return fmt.Errorf("failed to write report file: %w", err) } - fmt.Printf(" ๐Ÿ“Š Generated change report: %s\n", filename) + fmt.Printf(" Generated change report: %s\n", filename) return nil } @@ -152,7 +152,7 @@ func (r *ReportGenerator) generateReportContent(commitMessage string) (string, e changes := r.categorizeChanges(changedFiles) // Write resource type summary - content.WriteString("## ๐Ÿ“Š Resource Type Summary\n\n") + content.WriteString("## Resource Type Summary\n\n") for resourceType, count := range changes.ResourceTypes { content.WriteString("- **" + resourceType + "**: " + strconv.Itoa(count) + " changes\n") } @@ -178,9 +178,9 @@ func (r *ReportGenerator) generateReportContent(commitMessage string) (string, e // Group by namespace and show detailed changes directly for namespace, resources := range changes.ByNamespace { if namespace == "_cluster" { - content.WriteString("#### ๐ŸŒ Cluster-Scoped Resources\n\n") + content.WriteString("#### Cluster-Scoped Resources\n\n") } else { - content.WriteString("#### ๐Ÿ“ Namespace: `" + namespace + "`\n\n") + content.WriteString("#### Namespace: `" + namespace + "`\n\n") } for resourceType, files := range resources { @@ -196,7 +196,7 @@ func (r *ReportGenerator) generateReportContent(commitMessage string) (string, e // Get detailed diff information directly here diffInfo, err := r.getDetailedDiff(file, prevCommit, commitHash, status) if err != nil { - content.WriteString("โš ๏ธ Error getting diff: " + err.Error() + "\n\n") + content.WriteString("Warning: Error getting diff: " + err.Error() + "\n\n") continue } @@ -207,7 +207,7 @@ func (r *ReportGenerator) generateReportContent(commitMessage string) (string, e } // Write Git commands for reference - content.WriteString("## ๐Ÿ’ป Git Commands for Reference\n\n") + content.WriteString("## Git Commands for Reference\n\n") content.WriteString("```bash\n") content.WriteString("# View this commit\n") content.WriteString("git show " + commitHash + "\n\n") @@ -231,13 +231,13 @@ func (r *ReportGenerator) getDetailedDiff(file, prevCommit, currentCommit, statu var content strings.Builder switch status { - case "๐Ÿ†•": + case "New": // New file - show the complete content content.WriteString("**New Resource Created**\n\n") content.WriteString("```yaml\n") currentContent, err := r.getFileContent(file, currentCommit) if err != nil { - return "โš ๏ธ Error reading file content: " + err.Error(), nil + return "Warning: Error reading file content: " + err.Error(), nil } content.WriteString(currentContent) content.WriteString("\n```\n\n") @@ -248,13 +248,13 @@ func (r *ReportGenerator) getDetailedDiff(file, prevCommit, currentCommit, statu content.WriteString("- Status: Created in this snapshot\n") content.WriteString("- File: `" + file + "`\n\n") - case "๐Ÿ—‘๏ธ": + case "Deleted": // Deleted file - show what was removed content.WriteString("**Resource Deleted**\n\n") content.WriteString("```yaml\n") previousContent, err := r.getFileContent(file, prevCommit) if err != nil { - return "โš ๏ธ Error reading previous file content: " + err.Error(), nil + return "Warning: Error reading previous file content: " + err.Error(), nil } content.WriteString(previousContent) content.WriteString("\n```\n\n") @@ -265,14 +265,14 @@ func (r *ReportGenerator) getDetailedDiff(file, prevCommit, currentCommit, statu content.WriteString("- Status: Removed in this snapshot\n") content.WriteString("- File: `" + file + "`\n\n") - case "โœ๏ธ": + case "Modified": // Modified file - show the diff content.WriteString("**Resource Modified**\n\n") // Get the actual diff output diffOutput, err := r.getGitDiff(file, prevCommit, currentCommit) if err != nil { - content.WriteString("โš ๏ธ Error getting diff: " + err.Error() + "\n\n") + content.WriteString("Warning: Error getting diff: " + err.Error() + "\n\n") // Fallback to showing before/after content.WriteString("**Before (Previous Snapshot):**\n") content.WriteString("```yaml\n") @@ -448,17 +448,17 @@ func (r *ReportGenerator) getFileStatus(file, prevCommit, currentCommit string) cmd := exec.Command("git", "show", prevCommit+":"+file) cmd.Dir = r.repoPath if err := cmd.Run(); err != nil { - return "๐Ÿ†•" // New file + return "New" // New file } // Check if file exists in current commit cmd = exec.Command("git", "show", currentCommit+":"+file) cmd.Dir = r.repoPath if err := cmd.Run(); err != nil { - return "๐Ÿ—‘๏ธ" // Deleted file + return "Deleted" // Deleted file } - return "โœ๏ธ" // Modified file + return "Modified" // Modified file } // IsGitRepo checks if the directory is a Git repository @@ -487,6 +487,7 @@ func (r *ReportGenerator) getPreviousCommitHash() (string, error) { if err != nil { return "", err } + return strings.TrimSpace(string(output)), nil } @@ -525,22 +526,22 @@ func (r *ReportGenerator) generateValidationReport() (string, error) { return "", fmt.Errorf("failed to run validation: %w", err) } - content.WriteString("## ๐Ÿ” Cross-Reference Validation Report\n\n") + content.WriteString("## Cross-Reference Validation Report\n\n") content.WriteString("This section analyzes exported resources for broken references that could cause issues when reapplying.\n\n") // Validation Summary - content.WriteString("### ๐Ÿ“Š Validation Summary\n\n") + content.WriteString("### Validation Summary\n\n") content.WriteString(fmt.Sprintf("| Metric | Count |\n")) content.WriteString(fmt.Sprintf("|--------|-------|\n")) content.WriteString(fmt.Sprintf("| **Total References** | **%d** |\n", result.Summary.TotalReferences)) - content.WriteString(fmt.Sprintf("| **โœ… Valid References** | **%d** |\n", result.Summary.ValidReferences)) - content.WriteString(fmt.Sprintf("| **โŒ Broken References** | **%d** |\n", result.Summary.BrokenReferences)) - content.WriteString(fmt.Sprintf("| **โš ๏ธ Warning References** | **%d** |\n", result.Summary.WarningReferences)) + content.WriteString(fmt.Sprintf("| **Valid References** | **%d** |\n", result.Summary.ValidReferences)) + content.WriteString(fmt.Sprintf("| **Broken References** | **%d** |\n", result.Summary.BrokenReferences)) + content.WriteString(fmt.Sprintf("| **Warning References** | **%d** |\n", result.Summary.WarningReferences)) content.WriteString("\n") // Broken References (most important) if len(result.BrokenReferences) > 0 { - content.WriteString("### โŒ Broken References - ACTION REQUIRED\n\n") + content.WriteString("### Broken References - ACTION REQUIRED\n\n") // Group by source type grouped := make(map[string][]validation.ResourceReference) @@ -563,13 +564,13 @@ func (r *ReportGenerator) generateValidationReport() (string, error) { content.WriteString("\n") } } else { - content.WriteString("### โœ… No Broken References Found\n\n") - content.WriteString("๐ŸŽ‰ **Excellent!** All cross-references in your cluster are valid and safe to reapply.\n\n") + content.WriteString("### No Broken References Found\n\n") + content.WriteString("**Excellent!** All cross-references in your cluster are valid and safe to reapply.\n\n") } // Warning References if len(result.WarningReferences) > 0 { - content.WriteString("### โš ๏ธ Warning References - Manual Verification Needed\n\n") + content.WriteString("### Warning References - Manual Verification Needed\n\n") content.WriteString("**These references point to external resources that kalco cannot validate:**\n\n") // Group by source type @@ -592,8 +593,8 @@ func (r *ReportGenerator) generateValidationReport() (string, error) { // Valid References (summary only) if len(result.ValidReferences) > 0 { - content.WriteString("### โœ… Valid References - All Good!\n\n") - content.WriteString(fmt.Sprintf("**๐ŸŽ‰ %d references are properly configured and safe to reapply:**\n\n", len(result.ValidReferences))) + content.WriteString("### Valid References - All Good!\n\n") + content.WriteString(fmt.Sprintf("**%d references are properly configured and safe to reapply:**\n\n", len(result.ValidReferences))) // Group by source type grouped := make(map[string]int) @@ -604,17 +605,17 @@ func (r *ReportGenerator) generateValidationReport() (string, error) { content.WriteString(fmt.Sprintf("| Resource Type | Valid References | Status |\n")) content.WriteString(fmt.Sprintf("|---------------|------------------|---------|\n")) for sourceType, count := range grouped { - content.WriteString(fmt.Sprintf("| **%s** | **%d** | โœ… **Safe** |\n", sourceType, count)) + content.WriteString(fmt.Sprintf("| **%s** | **%d** | **Safe** |\n", sourceType, count)) } content.WriteString("\n") } - content.WriteString("**๐Ÿ“ Note**: This validation only checks for missing resources. It does not validate resource configurations, permissions, or runtime behavior.\n\n") + content.WriteString("**Note**: This validation only checks for missing resources. It does not validate resource configurations, permissions, or runtime behavior.\n\n") // Add orphaned resource detection orphanedContent, err := r.generateOrphanedResourceReport() if err != nil { - content.WriteString("\nโš ๏ธ **Warning**: Orphaned resource detection failed: " + err.Error() + "\n\n") + content.WriteString("\n**Warning**: Orphaned resource detection failed: " + err.Error() + "\n\n") } else { content.WriteString("\n" + orphanedContent) } @@ -633,11 +634,11 @@ func (r *ReportGenerator) generateOrphanedResourceReport() (string, error) { return "", fmt.Errorf("failed to run orphaned resource detection: %w", err) } - content.WriteString("## ๐Ÿ—‘๏ธ Orphaned Resource Detection Report\n\n") + content.WriteString("## Orphaned Resource Detection Report\n\n") content.WriteString("This section identifies resources that are no longer managed by higher-level controllers and may be consuming unnecessary resources.\n\n") // Orphaned Resource Summary - content.WriteString("### ๐Ÿ“Š Orphaned Resource Summary\n\n") + content.WriteString("### Orphaned Resource Summary\n\n") content.WriteString(fmt.Sprintf("| Metric | Count |\n")) content.WriteString(fmt.Sprintf("|--------|-------|\n")) content.WriteString(fmt.Sprintf("| **Total Orphaned Resources** | **%d** |\n", result.Summary.TotalOrphanedResources)) @@ -654,7 +655,7 @@ func (r *ReportGenerator) generateOrphanedResourceReport() (string, error) { // Orphaned Resources (most important) if len(result.OrphanedResources) > 0 { - content.WriteString("### ๐Ÿ—‘๏ธ Orphaned Resources Found - Cleanup Recommended\n\n") + content.WriteString("### Orphaned Resources Found - Cleanup Recommended\n\n") // Group by resource type grouped := make(map[string][]orphaned.OrphanedResource) @@ -673,11 +674,11 @@ func (r *ReportGenerator) generateOrphanedResourceReport() (string, error) { content.WriteString("\n") } } else { - content.WriteString("### โœ… No Orphaned Resources Found\n\n") - content.WriteString("๐ŸŽ‰ **Excellent!** All resources in your cluster are properly managed.\n\n") + content.WriteString("### No Orphaned Resources Found\n\n") + content.WriteString("**Excellent!** All resources in your cluster are properly managed.\n\n") } - content.WriteString("**๐Ÿ“ Note**: This detection identifies resources that appear to be unused based on ownership and reference analysis. Always verify before deletion.\n\n") + content.WriteString("**Note**: This detection identifies resources that appear to be unused based on ownership and reference analysis. Always verify before deletion.\n\n") return content.String(), nil } diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..439d4b9 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,243 @@ +# Scripts Directory + +This directory contains utility scripts for Kalco development and testing. + +## Available Scripts + +### `create-guestbook-cluster.sh` + +Creates a local Kubernetes cluster using Kind with the Guestbook application deployed. This script is perfect for testing Kalco's export functionality with a real application. + +#### What it does + +1. **Creates a Kind cluster** with 3 nodes (1 control-plane + 2 workers) +2. **Deploys Redis database** with leader-follower architecture +3. **Deploys Guestbook frontend** application +4. **Creates a Kalco context** for easy cluster management +5. **Keeps the cluster running** for ongoing testing + +#### Prerequisites + +- **Docker** - Running and accessible +- **Kind** - Kubernetes in Docker tool +- **kubectl** - Kubernetes command-line tool +- **Kalco** (optional) - For context management + +#### Installation of Prerequisites + +**Install Kind:** +```bash +# Linux/macOS +curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64 +chmod +x ./kind +sudo mv ./kind /usr/local/bin/kind + +# macOS with Homebrew +brew install kind +``` + +**Install kubectl:** +```bash +# Linux/macOS +curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" +chmod +x kubectl +sudo mv kubectl /usr/local/bin/kubectl + +# macOS with Homebrew +brew install kubectl +``` + +#### Usage + +**Basic usage:** +```bash +./scripts/create-guestbook-cluster.sh +``` + +**With custom cluster name:** +```bash +./scripts/create-guestbook-cluster.sh my-custom-cluster +``` + +#### What gets deployed + +The script deploys the complete Guestbook application as described in the [Kubernetes Guestbook Tutorial](https://kubernetes.io/docs/tutorials/stateless-application/guestbook/): + +- **Redis Leader**: Single Redis instance for write operations +- **Redis Followers**: 2 Redis instances for read operations +- **Frontend**: 3 PHP web servers serving the Guestbook application +- **Services**: Internal networking for all components + +#### Cluster Architecture + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Control Plane โ”‚ โ”‚ Worker Node 1 โ”‚ โ”‚ Worker Node 2 โ”‚ +โ”‚ (Port 8080) โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Guestbook App โ”‚ + โ”‚ (Frontend) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Redis Cluster โ”‚ + โ”‚ (Leader + 2 โ”‚ + โ”‚ Followers) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +#### Accessing the Application + +After the script completes successfully: + +1. **Port forward the frontend service:** + ```bash + kubectl port-forward svc/frontend 8080:80 + ``` + +2. **Open your browser** and navigate to `http://localhost:8080` + +3. **Test the application** by adding guestbook entries + +#### Using with Kalco + +The script automatically creates a Kalco context for easy management: + +```bash +# Switch to the guestbook cluster context +kalco context use guestbook-cluster + +# Export the cluster resources +kalco export --git-push --commit-message "Initial guestbook cluster export" + +# View the exported resources +ls -la ./guestbook-exports/ +``` + +#### Cluster Management + +**View cluster status:** +```bash +kubectl get pods +kubectl get services +kubectl get nodes +``` + +**Scale the frontend:** +```bash +kubectl scale deployment frontend --replicas=5 +``` + +**View logs:** +```bash +kubectl logs -l app=guestbook,tier=frontend +kubectl logs -l app=redis,tier=backend +``` + +#### Cleanup + +**The cluster is designed to persist** for ongoing testing. To manually remove it: + +```bash +kind delete cluster --name guestbook-cluster +``` + +**Remove Kalco context:** +```bash +kalco context delete guestbook-cluster +``` + +#### Troubleshooting + +**Common issues and solutions:** + +1. **Docker not running:** + ```bash + # Start Docker Desktop or Docker daemon + sudo systemctl start docker # Linux + # Or start Docker Desktop on macOS/Windows + ``` + +2. **Port 8080 already in use:** + ```bash + # Use a different port + kubectl port-forward svc/frontend 8081:80 + ``` + +3. **Pods not starting:** + ```bash + # Check pod status + kubectl describe pods + kubectl logs + ``` + +4. **Cluster creation fails:** + ```bash + # Check available resources + docker system df + # Ensure enough disk space and memory + ``` + +#### Customization + +You can modify the script to: + +- **Change cluster size** by modifying the node configuration +- **Use different Redis versions** by changing the image tag +- **Add more applications** by extending the deployment functions +- **Modify resource limits** for different testing scenarios + +#### Example Modifications + +**Add more worker nodes:** +```bash +# In the create_cluster function, add more worker nodes +- role: worker +- role: worker +- role: worker # Add this line +``` + +**Change Redis version:** +```bash +# In deploy_redis function, change the image +image: redis:7.0-alpine +``` + +**Modify resource limits:** +```bash +# Adjust CPU and memory limits as needed +resources: + requests: + cpu: 200m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi +``` + +#### Integration with CI/CD + +This script can be integrated into CI/CD pipelines for: + +- **Automated testing** of Kalco functionality +- **Development environment** setup +- **Demo environment** creation +- **Integration testing** with real Kubernetes clusters + +#### Support + +For issues with the script: + +1. Check the prerequisites are installed +2. Ensure Docker has sufficient resources +3. Check the troubleshooting section above +4. Review the script output for error messages +5. Verify your system meets the requirements + +--- + +*This script is based on the [Kubernetes Guestbook Tutorial](https://kubernetes.io/docs/tutorials/stateless-application/guestbook/) and adapted for Kalco testing purposes.* diff --git a/scripts/create-guestbook-cluster.sh b/scripts/create-guestbook-cluster.sh new file mode 100755 index 0000000..ddd9b5c --- /dev/null +++ b/scripts/create-guestbook-cluster.sh @@ -0,0 +1,443 @@ +#!/bin/bash + +# Kalco Guestbook Cluster Setup Script +# This script creates a Kind cluster with the Guestbook application +# Based on: https://kubernetes.io/docs/tutorials/stateless-application/guestbook/ + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Function to check if command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Function to wait for pods to be ready +wait_for_pods() { + local namespace=$1 + local label_selector=$2 + local expected_count=$3 + local timeout=300 + local elapsed=0 + + print_status "Waiting for pods in namespace '$namespace' with selector '$label_selector' to be ready..." + + while [ $elapsed -lt $timeout ]; do + local ready_count=$(kubectl get pods -n "$namespace" -l "$label_selector" -o jsonpath='{.items[*].status.containerStatuses[*].ready}' 2>/dev/null | tr ' ' '\n' | grep -c "true" 2>/dev/null || echo "0") + + # Ensure ready_count is a valid number + if [[ "$ready_count" =~ ^[0-9]+$ ]]; then + if [ "$ready_count" -eq "$expected_count" ]; then + print_success "All $expected_count pods are ready!" + return 0 + fi + + print_status "Ready: $ready_count/$expected_count pods (waiting...)" + else + print_status "Waiting for pods to initialize... (ready: 0/$expected_count)" + fi + + sleep 10 + elapsed=$((elapsed + 10)) + done + + print_error "Timeout waiting for pods to be ready after ${timeout}s" + return 1 +} + +# Function to check prerequisites +check_prerequisites() { + print_status "Checking prerequisites..." + + # Check if kind is installed + if ! command_exists kind; then + print_error "Kind is not installed. Please install it first:" + echo " curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64" + echo " chmod +x ./kind" + echo " sudo mv ./kind /usr/local/bin/kind" + exit 1 + fi + + # Check if kubectl is installed + if ! command_exists kubectl; then + print_error "kubectl is not installed. Please install it first:" + echo " curl -LO \"https://dl.k8s.io/release/\$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl\"" + echo " chmod +x kubectl" + echo " sudo mv kubectl /usr/local/bin/kubectl" + exit 1 + fi + + # Check if Docker is running + if ! docker info >/dev/null 2>&1; then + print_error "Docker is not running. Please start Docker first." + exit 1 + fi + + print_success "All prerequisites are satisfied!" +} + +# Function to create Kind cluster +create_cluster() { + local cluster_name=${1:-"guestbook-cluster"} + + print_status "Creating Kind cluster '$cluster_name'..." + + # Check if cluster already exists + if kind get clusters | grep -q "^$cluster_name$"; then + print_warning "Cluster '$cluster_name' already exists!" + read -p "Do you want to delete and recreate it? (y/N): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + print_status "Deleting existing cluster..." + kind delete cluster --name "$cluster_name" + else + print_status "Using existing cluster '$cluster_name'" + return 0 + fi + fi + + # Create cluster configuration + cat > /tmp/kind-config.yaml << EOF +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +name: $cluster_name +nodes: +- role: control-plane + extraPortMappings: + - containerPort: 80 + hostPort: 8080 + protocol: TCP +- role: worker +- role: worker +EOF + + # Create the cluster + kind create cluster --config /tmp/kind-config.yaml --name "$cluster_name" + + # Clean up config file + rm -f /tmp/kind-config.yaml + + print_success "Cluster '$cluster_name' created successfully!" +} + +# Function to deploy Redis +deploy_redis() { + print_status "Deploying Redis database..." + + # Create Redis leader deployment + cat </dev/null | grep 'Server Version' | awk '{print $3}' || echo 'Unknown')" + echo " Nodes: $(kubectl get nodes --no-headers | wc -l)" + + echo + print_status "Application Status:" + kubectl get pods -l app=redis + kubectl get pods -l app=guestbook + kubectl get services + + echo + print_status "Access Information:" + echo " Frontend Service: kubectl port-forward svc/frontend 8080:80" + echo " Redis Leader: kubectl port-forward svc/redis-leader 6379:6379" + echo " Redis Follower: kubectl port-forward svc/redis-follower 6380:6379" + echo + echo " Open http://localhost:8080 in your browser to access the Guestbook application" +} + +# Function to create Kalco context +create_kalco_context() { + # Try to find kalco in PATH or use local binary + local kalco_cmd="kalco" + if ! command_exists kalco; then + if [ -f "./kalco" ]; then + kalco_cmd="./kalco" + print_status "Using local Kalco binary" + else + print_warning "Kalco is not installed and no local binary found. Install it to manage this cluster with Kalco." + return 0 + fi + fi + + print_status "Creating Kalco context for the guestbook cluster..." + + # Create context + if $kalco_cmd context set guestbook-cluster \ + --kubeconfig ~/.kube/config \ + --output ./guestbook-exports \ + --description "Kind cluster with Guestbook application" \ + --labels env=dev,app=guestbook,cluster=kind; then + + print_success "Kalco context 'guestbook-cluster' created!" + echo " Use '$kalco_cmd context use guestbook-cluster' to switch to this context" + echo " Use '$kalco_cmd export' to export the cluster resources" + else + print_warning "Failed to create Kalco context. You can create it manually later." + fi +} + +# Main execution +main() { + echo "==========================================" + echo " Kalco Guestbook Cluster Setup Script" + echo "==========================================" + echo + + # Check prerequisites + check_prerequisites + + # Create cluster + create_cluster + + # Deploy Redis + deploy_redis + + # Deploy Guestbook frontend + deploy_guestbook + + # Wait for components + wait_for_components + + # Show cluster information + show_cluster_info + + # Create Kalco context + create_kalco_context + + echo + echo "==========================================" + print_success "Guestbook cluster setup completed!" + echo "==========================================" + echo + echo "The cluster will remain running. To stop it manually, run:" + echo " kind delete cluster --name guestbook-cluster" + echo + echo "To access the application:" + echo " kubectl port-forward svc/frontend 8080:80" + echo " Then open http://localhost:8080 in your browser" + echo + echo "To export the cluster with Kalco:" + echo " kalco context use guestbook-cluster" + echo " kalco export --git-push --commit-message 'Initial guestbook cluster export'" +} + +# Run main function +main "$@"