From 85a5fd8ce44540bb9b2d0344d4b62f0c8bd013b0 Mon Sep 17 00:00:00 2001 From: codeMonkeyCybersecurity Date: Sat, 21 Jun 2025 00:40:55 +0800 Subject: [PATCH] Fix lint issues --- cmd/create/hecate_terraform.go | 54 +++++++------ cmd/create/k3s_caddy_nginx.go | 138 +++++++++++++++++---------------- integration_scenarios_test.go | 18 +++-- integration_test.go | 58 +++++++------- pkg/alerts/smtp.go | 4 +- pkg/clean/clean.go | 2 - pkg/container/exec.go | 2 +- pkg/eos_cli/wrap_test.go | 62 ++++++++------- pkg/eos_io/context_test.go | 94 +++++++++++----------- pkg/eos_unix/filesystem.go | 4 +- pkg/hetzner/client_dns.go | 6 +- pkg/kvm/lifecycle.go | 2 +- pkg/kvm/provision_kickstart.go | 2 +- pkg/llm/helpers.go | 21 +++-- pkg/logger/lifecycle.go | 2 +- pkg/packer/lifecycle.go | 3 - pkg/parse/append.go | 2 +- pkg/stackstorm/emailer.go | 2 +- pkg/stackstorm/watcher.go | 34 ++++---- pkg/terraform/terraform.go | 92 +++++++++++----------- pkg/testutil/filesystem.go | 42 ++++++---- pkg/testutil/integration.go | 34 ++++---- pkg/vault/file_security.go | 56 ++++++------- pkg/verify/types.go | 4 - 24 files changed, 380 insertions(+), 358 deletions(-) diff --git a/cmd/create/hecate_terraform.go b/cmd/create/hecate_terraform.go index d5fcf2428..75ba464d5 100644 --- a/cmd/create/hecate_terraform.go +++ b/cmd/create/hecate_terraform.go @@ -404,33 +404,37 @@ type HecateConfig struct { func generateHecateTerraform(rc *eos_io.RuntimeContext, cmd *cobra.Command) error { logger := otelzap.Ctx(rc.Ctx) - + if err := terraform.CheckTerraformInstalled(); err != nil { return fmt.Errorf("terraform is required but not installed. Run 'eos create terraform' first: %w", err) } - + outputDir, _ := cmd.Flags().GetString("output-dir") useCloud, _ := cmd.Flags().GetBool("cloud") serverType, _ := cmd.Flags().GetString("server-type") location, _ := cmd.Flags().GetString("location") domain, _ := cmd.Flags().GetString("domain") - + // Interactive prompts for missing values if domain == "" { fmt.Print("Enter domain name for mail server: ") - fmt.Scanln(&domain) + if _, err := fmt.Scanln(&domain); err != nil { + return err + } } - + serverName := "hecate-mail" if useCloud { fmt.Printf("Enter server name [%s]: ", serverName) var input string - fmt.Scanln(&input) + if _, err := fmt.Scanln(&input); err != nil { + return err + } if input != "" { serverName = input } } - + config := HecateConfig{ UseHetzner: useCloud, ServerName: serverName, @@ -438,30 +442,30 @@ func generateHecateTerraform(rc *eos_io.RuntimeContext, cmd *cobra.Command) erro Location: location, Domain: domain, } - - logger.Info("Generating Hecate Terraform configuration", + + logger.Info("Generating Hecate Terraform configuration", zap.String("domain", domain), zap.Bool("cloud", useCloud), zap.String("output_dir", outputDir)) - + tfManager := terraform.NewManager(rc, outputDir) - + // Generate main.tf if err := tfManager.GenerateFromString(HecateTerraformTemplate, "main.tf", config); err != nil { return fmt.Errorf("failed to generate main.tf: %w", err) } - + // Generate cloud-init if using cloud if useCloud { if err := tfManager.GenerateFromString(HecateCloudInitTemplate, "hecate-cloud-init.yaml", config); err != nil { return fmt.Errorf("failed to generate cloud-init.yaml: %w", err) } } - + // Generate terraform.tfvars tfvarsContent := fmt.Sprintf(`# Terraform variables for Hecate deployment domain = "%s"`, domain) - + if useCloud { tfvarsContent += fmt.Sprintf(` # hcloud_token = "your-hetzner-cloud-token" @@ -469,11 +473,11 @@ ssh_key_name = "your-ssh-key" server_type = "%s" location = "%s"`, serverType, location) } - + if err := os.WriteFile(filepath.Join(outputDir, "terraform.tfvars"), []byte(tfvarsContent), 0644); err != nil { return fmt.Errorf("failed to generate terraform.tfvars: %w", err) } - + // Copy existing files if they exist configFiles := []string{"nginx.conf", "Caddyfile"} for _, file := range configFiles { @@ -481,25 +485,27 @@ location = "%s"`, serverType, location) content, err := os.ReadFile(file) if err == nil { destPath := filepath.Join(outputDir, file) - os.WriteFile(destPath, content, 0644) + if err := os.WriteFile(destPath, content, 0644); err != nil { + return fmt.Errorf("failed to copy %s: %w", file, err) + } logger.Info("Copied configuration file", zap.String("file", file)) } } } - + // Initialize and validate if err := tfManager.Init(rc); err != nil { return fmt.Errorf("failed to initialize terraform: %w", err) } - + if err := tfManager.Validate(rc); err != nil { return fmt.Errorf("terraform configuration validation failed: %w", err) } - + if err := tfManager.Format(rc); err != nil { logger.Warn("Failed to format terraform files", zap.Error(err)) } - + fmt.Printf("\nāœ… Hecate Terraform configuration generated in: %s\n", outputDir) fmt.Println("\nNext steps:") if useCloud { @@ -509,16 +515,16 @@ location = "%s"`, serverType, location) fmt.Printf("3. Review the configuration: cd %s\n", outputDir) fmt.Println("4. Plan the deployment: terraform plan") fmt.Println("5. Apply the configuration: terraform apply") - + return nil } func init() { CreateCmd.AddCommand(hecateTerraformCmd) - + hecateTerraformCmd.Flags().String("output-dir", "./terraform-hecate", "Output directory for Terraform files") hecateTerraformCmd.Flags().Bool("cloud", false, "Deploy to cloud infrastructure") hecateTerraformCmd.Flags().String("server-type", "cx21", "Server type for cloud instance") hecateTerraformCmd.Flags().String("location", "nbg1", "Location for cloud instance") hecateTerraformCmd.Flags().String("domain", "", "Domain name for the mail server") -} \ No newline at end of file +} diff --git a/cmd/create/k3s_caddy_nginx.go b/cmd/create/k3s_caddy_nginx.go index abe466450..8d78acb81 100644 --- a/cmd/create/k3s_caddy_nginx.go +++ b/cmd/create/k3s_caddy_nginx.go @@ -34,11 +34,11 @@ Features: func generateK3sCaddyNginx(rc *eos_io.RuntimeContext, cmd *cobra.Command) error { logger := otelzap.Ctx(rc.Ctx) - + if err := terraform.CheckTerraformInstalled(); err != nil { return fmt.Errorf("terraform is required but not installed. Run 'eos create terraform' first: %w", err) } - + // Get flags outputDir, _ := cmd.Flags().GetString("output-dir") cloudDeploy, _ := cmd.Flags().GetBool("cloud") @@ -47,80 +47,84 @@ func generateK3sCaddyNginx(rc *eos_io.RuntimeContext, cmd *cobra.Command) error serverType, _ := cmd.Flags().GetString("server-type") location, _ := cmd.Flags().GetString("location") enableMail, _ := cmd.Flags().GetBool("enable-mail") - + // Interactive prompts for missing values if domain == "" { fmt.Print("Enter primary domain for the cluster: ") - fmt.Scanln(&domain) + if _, err := fmt.Scanln(&domain); err != nil { + return err + } } - + if clusterName == "" { clusterName = "k3s-cluster" fmt.Printf("Enter cluster name [%s]: ", clusterName) var input string - fmt.Scanln(&input) + if _, err := fmt.Scanln(&input); err != nil { + return err + } if input != "" { clusterName = input } } - + // Configure mail ports mailPorts := []int{} if enableMail { mailPorts = []int{25, 587, 465, 110, 995, 143, 993, 4190} } - + config := terraform.K3sCaddyNginxConfig{ - CloudDeploy: cloudDeploy, - ClusterName: clusterName, - ServerType: serverType, - Location: location, - Domain: domain, - CaddyVersion: "2.7-alpine", - NginxVersion: "1.24-alpine", - CaddyReplicas: 2, - NginxReplicas: 1, - CaddyAdminEnabled: true, - CaddyStorageSize: "1Gi", - CaddyMemoryRequest: "128Mi", - CaddyCPURequest: "100m", - CaddyMemoryLimit: "256Mi", - CaddyCPULimit: "200m", - NginxMemoryRequest: "64Mi", - NginxCPURequest: "50m", - NginxMemoryLimit: "128Mi", - NginxCPULimit: "100m", - MailPorts: mailPorts, - MailBackend: "stalwart-mail.default.svc.cluster.local", + CloudDeploy: cloudDeploy, + ClusterName: clusterName, + ServerType: serverType, + Location: location, + Domain: domain, + CaddyVersion: "2.7-alpine", + NginxVersion: "1.24-alpine", + CaddyReplicas: 2, + NginxReplicas: 1, + CaddyAdminEnabled: true, + CaddyStorageSize: "1Gi", + CaddyMemoryRequest: "128Mi", + CaddyCPURequest: "100m", + CaddyMemoryLimit: "256Mi", + CaddyCPULimit: "200m", + NginxMemoryRequest: "64Mi", + NginxCPURequest: "50m", + NginxMemoryLimit: "128Mi", + NginxCPULimit: "100m", + MailPorts: mailPorts, + MailBackend: "stalwart-mail.default.svc.cluster.local", } - - logger.Info("Generating K3s + Caddy + Nginx configuration", + + logger.Info("Generating K3s + Caddy + Nginx configuration", zap.String("domain", domain), zap.String("cluster", clusterName), zap.Bool("cloud", cloudDeploy), zap.Bool("enable_mail", enableMail), zap.String("output_dir", outputDir)) - + tfManager := terraform.NewManager(rc, outputDir) - + // Generate main.tf if err := tfManager.GenerateFromString(terraform.K3sCaddyNginxTemplate, "main.tf", config); err != nil { return fmt.Errorf("failed to generate main.tf: %w", err) } - + // Generate cloud-init if using cloud if cloudDeploy { if err := tfManager.GenerateFromString(terraform.K3sCaddyNginxCloudInit, "k3s-cloud-init.yaml", config); err != nil { return fmt.Errorf("failed to generate cloud-init.yaml: %w", err) } } - + // Create config directory and files configDir := filepath.Join(outputDir, "config") if err := os.MkdirAll(configDir, 0755); err != nil { return fmt.Errorf("failed to create config directory: %w", err) } - + // Generate Caddyfile template caddyfileContent := fmt.Sprintf(`# Caddyfile Template for %s %s { @@ -166,11 +170,11 @@ func generateK3sCaddyNginx(rc *eos_io.RuntimeContext, cmd *cobra.Command) error } } `, clusterName, domain) - + if err := os.WriteFile(filepath.Join(configDir, "Caddyfile.tpl"), []byte(caddyfileContent), 0644); err != nil { return fmt.Errorf("failed to generate Caddyfile template: %w", err) } - + // Generate nginx mail config if mail is enabled if enableMail { nginxMailContent := fmt.Sprintf(`# Nginx Mail Proxy Configuration @@ -281,18 +285,18 @@ http { } } `, domain, config.MailBackend, config.MailBackend) - + if err := os.WriteFile(filepath.Join(configDir, "nginx-mail.conf.tpl"), []byte(nginxMailContent), 0644); err != nil { return fmt.Errorf("failed to generate nginx mail config: %w", err) } } - + // Generate terraform.tfvars tfvarsContent := fmt.Sprintf(`# Terraform variables for K3s + Caddy + Nginx deployment domain = "%s" caddy_admin_enabled = true `, domain) - + if cloudDeploy { tfvarsContent += fmt.Sprintf(` # Cloud deployment settings @@ -302,11 +306,11 @@ server_type = "%s" location = "%s" `, serverType, location) } - + if err := os.WriteFile(filepath.Join(outputDir, "terraform.tfvars"), []byte(tfvarsContent), 0644); err != nil { return fmt.Errorf("failed to generate terraform.tfvars: %w", err) } - + // Generate deployment script deployScript := fmt.Sprintf(`#!/bin/bash # Deployment script for %s @@ -345,37 +349,37 @@ if [[ "$response" =~ ^[Yy]$ ]]; then else echo "Deployment cancelled" fi -`, clusterName, domain, - func() string { - if enableMail { - return "4. Configure mail routing and SSL certificates" - } - return "" - }(), - func() string { - if enableMail { - return " kubectl logs -n ingress-system deployment/nginx-mail-proxy" - } - return "" - }()) - +`, clusterName, domain, + func() string { + if enableMail { + return "4. Configure mail routing and SSL certificates" + } + return "" + }(), + func() string { + if enableMail { + return " kubectl logs -n ingress-system deployment/nginx-mail-proxy" + } + return "" + }()) + if err := os.WriteFile(filepath.Join(outputDir, "deploy.sh"), []byte(deployScript), 0755); err != nil { return fmt.Errorf("failed to generate deploy script: %w", err) } - + // Initialize and validate if err := tfManager.Init(rc); err != nil { return fmt.Errorf("failed to initialize terraform: %w", err) } - + if err := tfManager.Validate(rc); err != nil { return fmt.Errorf("terraform configuration validation failed: %w", err) } - + if err := tfManager.Format(rc); err != nil { logger.Warn("Failed to format terraform files", zap.Error(err)) } - + fmt.Printf("\nāœ… K3s + Caddy + Nginx configuration generated in: %s\n", outputDir) fmt.Println("\nšŸŽÆ What you get:") fmt.Println(" • K3s cluster without Traefik") @@ -383,11 +387,11 @@ fi fmt.Println(" • Nginx for mail protocols (SMTP/IMAP/POP3)") fmt.Println(" • MetalLB for LoadBalancer services") fmt.Println(" • Automatic SSL with Let's Encrypt") - + if enableMail { fmt.Println(" • Mail proxy configuration") } - + fmt.Println("\nšŸ“‚ Generated files:") fmt.Printf(" %s/main.tf - Main Terraform configuration\n", outputDir) fmt.Printf(" %s/config/Caddyfile.tpl - Caddy configuration template\n", outputDir) @@ -395,7 +399,7 @@ fi fmt.Printf(" %s/config/nginx-mail.conf.tpl - Nginx mail proxy config\n", outputDir) } fmt.Printf(" %s/deploy.sh - Deployment script\n", outputDir) - + fmt.Println("\n⚔ Quick start:") fmt.Printf(" cd %s\n", outputDir) if cloudDeploy { @@ -403,13 +407,13 @@ fi fmt.Println(" # Update terraform.tfvars with your SSH key") } fmt.Println(" ./deploy.sh") - + return nil } func init() { CreateCmd.AddCommand(k3sCaddyNginxCmd) - + k3sCaddyNginxCmd.Flags().String("output-dir", "./k3s-caddy-nginx", "Output directory for Terraform files") k3sCaddyNginxCmd.Flags().Bool("cloud", false, "Deploy to cloud infrastructure (Hetzner)") k3sCaddyNginxCmd.Flags().String("domain", "", "Primary domain for the cluster") @@ -417,4 +421,4 @@ func init() { k3sCaddyNginxCmd.Flags().String("server-type", "cx21", "Server type for cloud instance") k3sCaddyNginxCmd.Flags().String("location", "nbg1", "Location for cloud instance") k3sCaddyNginxCmd.Flags().Bool("enable-mail", false, "Include Nginx mail proxy configuration") -} \ No newline at end of file +} diff --git a/integration_scenarios_test.go b/integration_scenarios_test.go index 43c796a83..f9663a95b 100644 --- a/integration_scenarios_test.go +++ b/integration_scenarios_test.go @@ -63,12 +63,14 @@ func TestIntegrationPatterns_MockServices(t *testing.T) { // Test Vault mock pattern vaultPattern := testutil.MockServicePattern("vault", testutil.VaultMockTransport()) - + scenario := testutil.TestScenario{ Name: "vault_mock_pattern_test", Description: "Test vault mock service pattern", Setup: func(s *testutil.IntegrationTestSuite) { - vaultPattern.Setup(s) + if err := vaultPattern.Setup(s); err != nil { + t.Fatalf("setup vault pattern: %v", err) + } }, Steps: []testutil.TestStep{ { @@ -96,12 +98,14 @@ func TestIntegrationPatterns_FileSystem(t *testing.T) { suite := testutil.NewIntegrationTestSuite(t, "filesystem-patterns") fsPattern := testutil.FileSystemPattern("test/data") - + scenario := testutil.TestScenario{ Name: "filesystem_pattern_test", Description: "Test file system operation patterns", Setup: func(s *testutil.IntegrationTestSuite) { - fsPattern.Setup(s) + if err := fsPattern.Setup(s); err != nil { + t.Fatalf("setup fs pattern: %v", err) + } }, Steps: []testutil.TestStep{ { @@ -133,7 +137,7 @@ func TestIntegrationPatterns_Concurrency(t *testing.T) { concurrentOperation := func(workerID int, s *testutil.IntegrationTestSuite) error { rc := s.CreateTestContext("concurrent-worker") rc.Attributes["worker_id"] = string(rune(workerID + '0')) - + // Simulate some work return nil } @@ -141,7 +145,7 @@ func TestIntegrationPatterns_Concurrency(t *testing.T) { concurrencyPattern := testutil.ConcurrencyPattern(5, concurrentOperation) scenario := testutil.TestScenario{ - Name: "concurrency_pattern_test", + Name: "concurrency_pattern_test", Description: "Test concurrent operations pattern", Steps: []testutil.TestStep{ { @@ -155,4 +159,4 @@ func TestIntegrationPatterns_Concurrency(t *testing.T) { } suite.RunScenario(scenario) -} \ No newline at end of file +} diff --git a/integration_test.go b/integration_test.go index 9080f8b7a..7abe7c9ec 100644 --- a/integration_test.go +++ b/integration_test.go @@ -40,7 +40,7 @@ func TestEosIntegration_VaultAuthenticationWorkflow(t *testing.T) { if err != nil { return err } - + // This should fail gracefully with mocked responses err = vault.SecureAuthenticationOrchestrator(rc, client) if err == nil { @@ -59,7 +59,7 @@ func TestEosIntegration_VaultAuthenticationWorkflow(t *testing.T) { if err != nil { return err } - + err = vault.SecureAuthenticationOrchestrator(rc, client) if err != nil { // Check that error doesn't contain sensitive paths @@ -159,7 +159,7 @@ vault_addr: http://127.0.0.1:8200 rc := s.CreateTestContext("config-test") rc.Attributes["config_file"] = "test.yaml" rc.Attributes["log_level"] = "debug" - + if rc.Attributes["config_file"] != "test.yaml" { return errors.New("runtime context attribute storage failed") } @@ -186,15 +186,15 @@ func TestEosIntegration_ErrorHandlingFlow(t *testing.T) { Description: "Verify user errors are properly categorized", Action: func(s *testutil.IntegrationTestSuite) error { rc := s.CreateTestContext("user-error-test") - + // Simulate creating a user error userErr := errors.New("user provided invalid input") wrappedErr := fmt.Errorf("command failed: %w", userErr) - + // Test error handling - var finalErr error = wrappedErr + finalErr := wrappedErr rc.HandlePanic(&finalErr) - + return nil // Success if no panic }, Timeout: 5 * time.Second, @@ -205,17 +205,17 @@ func TestEosIntegration_ErrorHandlingFlow(t *testing.T) { Action: func(s *testutil.IntegrationTestSuite) error { rc, cancel := testutil.TestRuntimeContextWithCancel(t) defer cancel() - + // Start a goroutine that waits for cancellation done := make(chan bool, 1) go func() { <-rc.Ctx.Done() done <- true }() - + // Cancel the context cancel() - + // Should receive cancellation signal select { case <-done: @@ -245,14 +245,14 @@ func TestEosIntegration_TelemetryAndLogging(t *testing.T) { Description: "Verify runtime context creates proper telemetry spans", Action: func(s *testutil.IntegrationTestSuite) error { rc := s.CreateTestContext("telemetry-test") - + // Simulate some work time.Sleep(10 * time.Millisecond) - + // End the context (this creates telemetry) var err error rc.End(&err) - + return nil }, Timeout: 10 * time.Second, @@ -262,17 +262,17 @@ func TestEosIntegration_TelemetryAndLogging(t *testing.T) { Description: "Verify contextual logging works correctly", Action: func(s *testutil.IntegrationTestSuite) error { rc := s.CreateTestContext("logging-test") - + // Test contextual logger creation logger := rc.Log if logger == nil { return errors.New("contextual logger was nil") } - + // Log some test messages logger.Info("Test log message for integration test") logger.Debug("Debug message with context") - + return nil }, Timeout: 5 * time.Second, @@ -298,21 +298,21 @@ func TestEosIntegration_MultiComponentWorkflow(t *testing.T) { Description: "Initialize vault, runtime context, and other components", Action: func(s *testutil.IntegrationTestSuite) error { rc := s.CreateTestContext("multi-component-init") - + // Test vault client creation vaultClient, err := vault.NewClient(rc) if err != nil { return fmt.Errorf("vault client creation failed: %w", err) } - + // Store vault client reference for later steps rc.Attributes["vault_client_created"] = "true" rc.Attributes["vault_addr"] = "http://127.0.0.1:8200" - + if vaultClient == nil { return errors.New("vault client was nil") } - + return nil }, Timeout: 15 * time.Second, @@ -322,23 +322,23 @@ func TestEosIntegration_MultiComponentWorkflow(t *testing.T) { Description: "Test interaction between components under various scenarios", Action: func(s *testutil.IntegrationTestSuite) error { rc := s.CreateTestContext("component-interaction") - + // Test authentication status checking vaultClient, err := vault.NewClient(rc) if err != nil { return err } - + status := vault.GetAuthenticationStatus(rc, vaultClient) if status == nil { return errors.New("authentication status was nil") } - + // Verify status structure if _, ok := status["authenticated"]; !ok { return errors.New("authentication status missing 'authenticated' field") } - + return nil }, Timeout: 10 * time.Second, @@ -348,25 +348,25 @@ func TestEosIntegration_MultiComponentWorkflow(t *testing.T) { Description: "Test system resilience under failure conditions", Action: func(s *testutil.IntegrationTestSuite) error { rc := s.CreateTestContext("resilience-test") - + // Test that system handles failures gracefully vaultClient, err := vault.NewClient(rc) if err != nil { return err } - + // Try authentication (should fail gracefully) err = vault.SecureAuthenticationOrchestrator(rc, vaultClient) if err == nil { return errors.New("expected authentication to fail in test environment") } - + // System should still be functional after auth failure status := vault.GetAuthenticationStatus(rc, vaultClient) if status == nil { return errors.New("system became non-functional after auth failure") } - + return nil }, Timeout: 20 * time.Second, @@ -379,4 +379,4 @@ func TestEosIntegration_MultiComponentWorkflow(t *testing.T) { } suite.RunScenario(scenario) -} \ No newline at end of file +} diff --git a/pkg/alerts/smtp.go b/pkg/alerts/smtp.go index 5ee206fa1..1ab7669d6 100644 --- a/pkg/alerts/smtp.go +++ b/pkg/alerts/smtp.go @@ -88,7 +88,9 @@ func (s *smtpSender) Send(ctx context.Context, subj, htmlBody, txtBody string) e recordFail(err) return cerr.Wrap(err, "starttls") } - defer c.Quit() + defer func() { + _ = c.Quit() + }() // 4. EHLO if err := c.Hello(s.host); err != nil { diff --git a/pkg/clean/clean.go b/pkg/clean/clean.go index 62cbf14ea..213187d3c 100644 --- a/pkg/clean/clean.go +++ b/pkg/clean/clean.go @@ -24,8 +24,6 @@ var reserved = map[string]bool{ // Sanitiser helpers // ----------------------------------------------------------------------------- -var invalidRE = regexp.MustCompile(`[^a-zA-Z0-9._-]+`) - // Sanitize a single file/directory name for Windows func SanitizeName(name string) string { // Remove forbidden characters diff --git a/pkg/container/exec.go b/pkg/container/exec.go index ec2f7c682..d324829bc 100644 --- a/pkg/container/exec.go +++ b/pkg/container/exec.go @@ -51,7 +51,7 @@ func ExecCommandInContainer(rc *eos_io.RuntimeContext, cfg ExecConfig) (string, if err != nil { return "", errors.Wrap(err, "creating docker client") } - defer cli.Close() + defer func() { _ = cli.Close() }() // 6) Create exec instance execResp, err := cli.ContainerExecCreate(rc.Ctx, cfg.ContainerName, container.ExecOptions{ diff --git a/pkg/eos_cli/wrap_test.go b/pkg/eos_cli/wrap_test.go index f7a44a966..bd9699ac9 100644 --- a/pkg/eos_cli/wrap_test.go +++ b/pkg/eos_cli/wrap_test.go @@ -14,14 +14,16 @@ import ( func TestWrap(t *testing.T) { // Initialize telemetry for tests - telemetry.Init("test") + if err := telemetry.Init("test"); err != nil { + t.Fatalf("telemetry init: %v", err) + } t.Run("successful_command_execution", func(t *testing.T) { // Create a simple command function that succeeds successFunc := func(rc *eos_io.RuntimeContext, cmd *cobra.Command, args []string) error { - // Verify runtime context is properly initialized if rc == nil { t.Error("expected non-nil runtime context") + return nil } if rc.Ctx == nil { t.Error("expected non-nil context") @@ -34,7 +36,7 @@ func TestWrap(t *testing.T) { // Create test command cmd := &cobra.Command{ - Use: "test-command", + Use: "test-command", RunE: Wrap(successFunc), } @@ -47,13 +49,13 @@ func TestWrap(t *testing.T) { t.Run("command_execution_with_error", func(t *testing.T) { expectedErr := errors.New("test error") - + errorFunc := func(rc *eos_io.RuntimeContext, cmd *cobra.Command, args []string) error { return expectedErr } cmd := &cobra.Command{ - Use: "test-error-command", + Use: "test-error-command", RunE: Wrap(errorFunc), } @@ -61,7 +63,7 @@ func TestWrap(t *testing.T) { if err == nil { t.Fatal("expected error, got nil") } - + // The error should be wrapped with stack trace unless it's a user error if err == expectedErr { t.Error("expected error to be wrapped with stack trace") @@ -70,13 +72,13 @@ func TestWrap(t *testing.T) { t.Run("expected_user_error_not_wrapped", func(t *testing.T) { userErr := eos_err.NewExpectedError(nil, errors.New("user did something wrong")) - + userErrorFunc := func(rc *eos_io.RuntimeContext, cmd *cobra.Command, args []string) error { return userErr } cmd := &cobra.Command{ - Use: "test-user-error-command", + Use: "test-user-error-command", RunE: Wrap(userErrorFunc), } @@ -84,7 +86,7 @@ func TestWrap(t *testing.T) { if err == nil { t.Fatal("expected error, got nil") } - + // User errors should not be wrapped with stack trace if !eos_err.IsExpectedUserError(err) { t.Error("expected user error to remain as user error") @@ -97,7 +99,7 @@ func TestWrap(t *testing.T) { } cmd := &cobra.Command{ - Use: "test-panic-command", + Use: "test-panic-command", RunE: Wrap(panicFunc), } @@ -122,7 +124,7 @@ func TestWrap(t *testing.T) { os.Setenv("VAULT_ADDR", originalVaultAddr) } }() - + os.Setenv("VAULT_ADDR", "http://test:8200") vaultFunc := func(rc *eos_io.RuntimeContext, cmd *cobra.Command, args []string) error { @@ -135,7 +137,7 @@ func TestWrap(t *testing.T) { } cmd := &cobra.Command{ - Use: "test-vault-command", + Use: "test-vault-command", RunE: Wrap(vaultFunc), } @@ -145,7 +147,7 @@ func TestWrap(t *testing.T) { } }) - // Note: Validation test skipped as it depends on external CUE/OPA validation + // Note: Validation test skipped as it depends on external CUE/OPA validation // which may not be configured in test environment t.Run("validation_skipped", func(t *testing.T) { t.Skip("Validation test requires CUE/OPA configuration") @@ -156,7 +158,7 @@ func TestWrap(t *testing.T) { // Test that we can set and retrieve attributes rc.Attributes["test_key"] = "test_value" rc.Attributes["command_name"] = cmd.Name() - + if rc.Attributes["test_key"] != "test_value" { t.Errorf("expected 'test_value', got %s", rc.Attributes["test_key"]) } @@ -167,7 +169,7 @@ func TestWrap(t *testing.T) { } cmd := &cobra.Command{ - Use: "test-attributes-command", + Use: "test-attributes-command", RunE: Wrap(attributeFunc), } @@ -179,7 +181,7 @@ func TestWrap(t *testing.T) { t.Run("context_timing", func(t *testing.T) { start := time.Now() - + timingFunc := func(rc *eos_io.RuntimeContext, cmd *cobra.Command, args []string) error { // Verify that the context timestamp is recent if rc.Timestamp.Before(start) { @@ -188,14 +190,14 @@ func TestWrap(t *testing.T) { if rc.Timestamp.After(time.Now()) { t.Error("context timestamp should not be in the future") } - + // Add some duration to test timing time.Sleep(10 * time.Millisecond) return nil } cmd := &cobra.Command{ - Use: "test-timing-command", + Use: "test-timing-command", RunE: Wrap(timingFunc), } @@ -211,7 +213,7 @@ func TestWrapValidation(t *testing.T) { validation := &WrapValidation{ Cfg: "test config", SchemaPath: "/path/to/schema.cue", - YAMLPath: "/path/to/data.yaml", + YAMLPath: "/path/to/data.yaml", PolicyPath: "/path/to/policy.rego", PolicyInput: func() any { return map[string]string{"key": "value"} }, } @@ -221,7 +223,7 @@ func TestWrapValidation(t *testing.T) { _ = validation.SchemaPath _ = validation.YAMLPath _ = validation.PolicyPath - + if validation.PolicyInput == nil { t.Error("expected non-nil PolicyInput function") } @@ -235,10 +237,10 @@ func TestWrapValidation(t *testing.T) { t.Run("validation_with_nil_policy_input", func(t *testing.T) { validation := &WrapValidation{ - Cfg: "test", - SchemaPath: "/test", - YAMLPath: "/test", - PolicyPath: "/test", + Cfg: "test", + SchemaPath: "/test", + YAMLPath: "/test", + PolicyPath: "/test", PolicyInput: nil, // This should be allowed } @@ -247,7 +249,7 @@ func TestWrapValidation(t *testing.T) { _ = validation.SchemaPath _ = validation.YAMLPath _ = validation.PolicyPath - + if validation.PolicyInput != nil { t.Error("expected nil PolicyInput") } @@ -256,10 +258,10 @@ func TestWrapValidation(t *testing.T) { // Helper function to check if string contains substring (reused from context_test.go) func contains(s, substr string) bool { - return len(s) >= len(substr) && (s == substr || len(substr) == 0 || - (len(s) > len(substr) && (s[:len(substr)] == substr || - s[len(s)-len(substr):] == substr || - containsInner(s, substr)))) + return len(s) >= len(substr) && (s == substr || len(substr) == 0 || + (len(s) > len(substr) && (s[:len(substr)] == substr || + s[len(s)-len(substr):] == substr || + containsInner(s, substr)))) } func containsInner(s, substr string) bool { @@ -269,4 +271,4 @@ func containsInner(s, substr string) bool { } } return false -} \ No newline at end of file +} diff --git a/pkg/eos_io/context_test.go b/pkg/eos_io/context_test.go index fcdc223bd..4bb23b810 100644 --- a/pkg/eos_io/context_test.go +++ b/pkg/eos_io/context_test.go @@ -13,7 +13,7 @@ func TestNewContext(t *testing.T) { t.Run("creates_valid_context", func(t *testing.T) { ctx := context.Background() rc := NewContext(ctx, "test-command") - + if rc == nil { t.Fatal("expected non-nil runtime context") } @@ -30,26 +30,26 @@ func TestNewContext(t *testing.T) { if rc.Attributes == nil { t.Fatal("expected non-nil attributes map") } - + // Verify timestamp is recent now := time.Now() if rc.Timestamp.After(now) || rc.Timestamp.Before(now.Add(-time.Second)) { t.Errorf("timestamp should be recent, got %v", rc.Timestamp) } }) - + t.Run("creates_unique_contexts", func(t *testing.T) { ctx := context.Background() rc1 := NewContext(ctx, "command1") time.Sleep(time.Millisecond) // Ensure different timestamps rc2 := NewContext(ctx, "command2") - + // Commands may be the same since they're derived from caller context // But timestamps should be different if !rc1.Timestamp.Before(rc2.Timestamp) { t.Error("expected rc1 timestamp to be before rc2 timestamp") } - + // Attributes should be separate instances rc1.Attributes["test"] = "value1" rc2.Attributes["test"] = "value2" @@ -67,12 +67,12 @@ func TestRuntimeContext_HandlePanic(t *testing.T) { ctx := context.Background() rc := NewContext(ctx, "test") var err error - + func() { defer rc.HandlePanic(&err) panic("test panic") }() - + if err == nil { t.Fatal("expected error after panic recovery") } @@ -80,33 +80,33 @@ func TestRuntimeContext_HandlePanic(t *testing.T) { t.Errorf("expected error to contain 'panic: test panic', got %s", err.Error()) } }) - + t.Run("no_panic_leaves_error_unchanged", func(t *testing.T) { ctx := context.Background() rc := NewContext(ctx, "test") var err error - + func() { defer rc.HandlePanic(&err) // No panic }() - + if err != nil { t.Errorf("expected no error, got %v", err) } }) - + t.Run("preserves_existing_error", func(t *testing.T) { ctx := context.Background() rc := NewContext(ctx, "test") existingErr := errors.New("existing error") err := existingErr - + func() { defer rc.HandlePanic(&err) // No panic }() - + if err != existingErr { t.Errorf("expected existing error to be preserved") } @@ -115,10 +115,10 @@ func TestRuntimeContext_HandlePanic(t *testing.T) { // Helper function to check if string contains substring func contains(s, substr string) bool { - return len(s) >= len(substr) && (s == substr || len(substr) == 0 || - (len(s) > len(substr) && (s[:len(substr)] == substr || - s[len(s)-len(substr):] == substr || - containsInner(s, substr)))) + return len(s) >= len(substr) && (s == substr || len(substr) == 0 || + (len(s) > len(substr) && (s[:len(substr)] == substr || + s[len(s)-len(substr):] == substr || + containsInner(s, substr)))) } func containsInner(s, substr string) bool { @@ -132,37 +132,39 @@ func containsInner(s, substr string) bool { func TestRuntimeContext_End(t *testing.T) { // Initialize telemetry to prevent nil pointer dereference - telemetry.Init("test") - + if err := telemetry.Init("test"); err != nil { + t.Fatalf("telemetry init: %v", err) + } + t.Run("logs_successful_completion", func(t *testing.T) { ctx := context.Background() rc := NewContext(ctx, "test") var err error - + // Sleep briefly to ensure measurable duration time.Sleep(time.Millisecond) - + // Should not panic rc.End(&err) }) - + t.Run("logs_failed_completion", func(t *testing.T) { ctx := context.Background() rc := NewContext(ctx, "test") err := errors.New("test failure") - + time.Sleep(time.Millisecond) - + // Should not panic rc.End(&err) }) - + t.Run("includes_vault_context", func(t *testing.T) { ctx := context.Background() rc := NewContext(ctx, "test") rc.Attributes["vault_addr"] = "http://localhost:8200" var err error - + // Should not panic and should include vault address rc.End(&err) }) @@ -172,10 +174,10 @@ func TestRuntimeContext_Attributes(t *testing.T) { t.Run("can_store_and_retrieve_attributes", func(t *testing.T) { ctx := context.Background() rc := NewContext(ctx, "test") - + rc.Attributes["key1"] = "value1" rc.Attributes["key2"] = "value2" - + if rc.Attributes["key1"] != "value1" { t.Errorf("expected 'value1', got %s", rc.Attributes["key1"]) } @@ -183,15 +185,15 @@ func TestRuntimeContext_Attributes(t *testing.T) { t.Errorf("expected 'value2', got %s", rc.Attributes["key2"]) } }) - + t.Run("attributes_are_isolated_per_context", func(t *testing.T) { ctx := context.Background() rc1 := NewContext(ctx, "test1") rc2 := NewContext(ctx, "test2") - + rc1.Attributes["shared_key"] = "value1" rc2.Attributes["shared_key"] = "value2" - + if rc1.Attributes["shared_key"] != "value1" { t.Errorf("expected 'value1', got %s", rc1.Attributes["shared_key"]) } @@ -206,17 +208,17 @@ func TestContextCancellation(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) rc := NewContext(ctx, "test") defer cancel() - + // Start a goroutine that waits for context cancellation done := make(chan bool) go func() { <-rc.Ctx.Done() done <- true }() - + // Cancel the context cancel() - + // Should receive cancellation signal select { case <-done: @@ -225,12 +227,12 @@ func TestContextCancellation(t *testing.T) { t.Fatal("context cancellation did not propagate") } }) - + t.Run("context_timeout_works", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) rc := NewContext(ctx, "test") defer cancel() - + // Wait for timeout select { case <-rc.Ctx.Done(): @@ -248,27 +250,27 @@ func TestLogVaultContext(t *testing.T) { t.Run("logs_valid_vault_address", func(t *testing.T) { ctx := context.Background() rc := NewContext(ctx, "test") - + addr := LogVaultContext(rc.Log, "http://localhost:8200", nil) if addr != "http://localhost:8200" { t.Errorf("expected 'http://localhost:8200', got %s", addr) } }) - + t.Run("logs_vault_error", func(t *testing.T) { ctx := context.Background() rc := NewContext(ctx, "test") - + addr := LogVaultContext(rc.Log, "", errors.New("vault error")) if addr != "(unavailable)" { t.Errorf("expected '(unavailable)', got %s", addr) } }) - + t.Run("logs_empty_address", func(t *testing.T) { ctx := context.Background() rc := NewContext(ctx, "test") - + addr := LogVaultContext(rc.Log, "", nil) if addr != "(unavailable)" { t.Errorf("expected '(unavailable)', got %s", addr) @@ -280,17 +282,17 @@ func TestContextualLogger(t *testing.T) { t.Run("creates_contextual_logger", func(t *testing.T) { ctx := context.Background() rc := NewContext(ctx, "test") - + logger := ContextualLogger(rc, 2, nil) if logger == nil { t.Error("expected non-nil logger") } }) - + t.Run("uses_base_logger_when_provided", func(t *testing.T) { ctx := context.Background() rc := NewContext(ctx, "test") - + logger := ContextualLogger(rc, 2, rc.Log) if logger == nil { t.Error("expected non-nil logger") @@ -302,8 +304,8 @@ func TestLogRuntimeExecutionContext(t *testing.T) { t.Run("logs_execution_context", func(t *testing.T) { ctx := context.Background() rc := NewContext(ctx, "test") - + // Should not panic LogRuntimeExecutionContext(rc) }) -} \ No newline at end of file +} diff --git a/pkg/eos_unix/filesystem.go b/pkg/eos_unix/filesystem.go index f60852988..cf11861d3 100644 --- a/pkg/eos_unix/filesystem.go +++ b/pkg/eos_unix/filesystem.go @@ -132,7 +132,7 @@ func CopyFile(ctx context.Context, src, dst string, perm os.FileMode) error { if err != nil { return cerr.Wrapf(err, "open %s", src) } - defer in.Close() + defer func() { _ = in.Close() }() if err := MkdirP(ctx, filepath.Dir(dst), 0o755); err != nil { return cerr.Wrapf(err, "ensure dir for %s", dst) @@ -142,7 +142,7 @@ func CopyFile(ctx context.Context, src, dst string, perm os.FileMode) error { if err != nil { return cerr.Wrapf(err, "create %s", dst) } - defer out.Close() + defer func() { _ = out.Close() }() if _, err := io.Copy(out, in); err != nil { return cerr.Wrapf(err, "copy %s→%s", src, dst) diff --git a/pkg/hetzner/client_dns.go b/pkg/hetzner/client_dns.go index 61ea8b9b4..43baeb231 100644 --- a/pkg/hetzner/client_dns.go +++ b/pkg/hetzner/client_dns.go @@ -50,7 +50,7 @@ func (c *DNSClient) GetServers(rc *eos_io.RuntimeContext) ([]map[string]interfac if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, errors.Errorf("unexpected status %d from Hetzner API", resp.StatusCode) @@ -72,7 +72,7 @@ func (c *DNSClient) DeleteServer(rc *eos_io.RuntimeContext, id string) error { if err != nil { return err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { return errors.Errorf("failed to delete server %s, status %d", id, resp.StatusCode) @@ -88,7 +88,7 @@ func (c *DNSClient) CreateServer(rc *eos_io.RuntimeContext, name string, image s if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusCreated { return nil, errors.Errorf("failed to create server, status %d", resp.StatusCode) diff --git a/pkg/kvm/lifecycle.go b/pkg/kvm/lifecycle.go index 9446e6a68..eb4b3cf5a 100644 --- a/pkg/kvm/lifecycle.go +++ b/pkg/kvm/lifecycle.go @@ -121,7 +121,7 @@ func getNextVMID(rc *eos_io.RuntimeContext) (string, error) { if err != nil { return "", fmt.Errorf("cannot open ID file: %w", err) } - defer fd.Close() + defer func() { _ = fd.Close() }() // Apply exclusive lock (blocks until available) if err := unix.Flock(int(fd.Fd()), unix.LOCK_EX); err != nil { diff --git a/pkg/kvm/provision_kickstart.go b/pkg/kvm/provision_kickstart.go index f86dea0ea..e350bf2e5 100644 --- a/pkg/kvm/provision_kickstart.go +++ b/pkg/kvm/provision_kickstart.go @@ -23,7 +23,7 @@ func ProvisionKickstartTenantVM(rc *eos_io.RuntimeContext, vmName, pubKeyPath st if err != nil { return fmt.Errorf("failed to generate kickstart: %w", err) } - defer os.Remove(ksPath) + defer func() { _ = os.Remove(ksPath) }() log.Info("🟔 Kickstart file generated", zap.String("path", ksPath)) if err := virtInstall(zap.L(), vmName, ksPath, diskPath); err != nil { diff --git a/pkg/llm/helpers.go b/pkg/llm/helpers.go index 6734a3931..d1a1e6094 100644 --- a/pkg/llm/helpers.go +++ b/pkg/llm/helpers.go @@ -22,14 +22,13 @@ import ( // ───────────────────────── Config (env driven) ──────────────────────────── var ( - apiKey = env("AZURE_API_KEY", "") - endpoint = env("AZURE_ENDPOINT", "https://languageatcodemonkey.openai.azure.com") - deployment = env("AZURE_DEPLOYMENT", "gpt-4.1") - apiVersion = env("AZURE_API_VERSION", "2025-01-01-preview") - promptFile = env("PROMPT_FILE", "/opt/system-prompt.txt") - debugLogFile = env("DEBUG_LOG", "/var/log/stackstorm/llm-debug.log") - promptDbgFile = env("PROMPT_DEBUG", "/var/log/stackstorm/prompt-debug.log") - maxLogBytes = int64(10 * 1024 * 1024) + apiKey = env("AZURE_API_KEY", "") + endpoint = env("AZURE_ENDPOINT", "https://languageatcodemonkey.openai.azure.com") + deployment = env("AZURE_DEPLOYMENT", "gpt-4.1") + apiVersion = env("AZURE_API_VERSION", "2025-01-01-preview") + promptFile = env("PROMPT_FILE", "/opt/system-prompt.txt") + debugLogFile = env("DEBUG_LOG", "/var/log/stackstorm/llm-debug.log") + maxLogBytes = int64(10 * 1024 * 1024) ) func env(key, def string) string { @@ -54,7 +53,7 @@ func appendLine(path, s string) error { if err != nil { return err } - defer f.Close() + defer func() { _ = f.Close() }() _, err = f.WriteString(s) return err } @@ -195,7 +194,7 @@ func CallAzure(ctx context.Context, payload map[string]any, retries int, delay t } func ExtractResponseText(resp *http.Response) (string, error) { - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() var data struct { Choices []struct { Message struct { @@ -222,7 +221,7 @@ func ReopenLog(ctx context.Context, path string) (<-chan string, error) { return nil, err } go func() { - defer f.Close() + defer func() { _ = f.Close() }() r := bufio.NewReader(f) for { select { diff --git a/pkg/logger/lifecycle.go b/pkg/logger/lifecycle.go index 2634fcca5..061448a57 100644 --- a/pkg/logger/lifecycle.go +++ b/pkg/logger/lifecycle.go @@ -93,7 +93,7 @@ func TryWritablePath(paths []string) (string, error) { continue } if file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600); err == nil { - file.Close() + _ = file.Close() return path, nil } } diff --git a/pkg/packer/lifecycle.go b/pkg/packer/lifecycle.go index d3da77671..8045556d9 100644 --- a/pkg/packer/lifecycle.go +++ b/pkg/packer/lifecycle.go @@ -6,12 +6,9 @@ import ( "github.com/CodeMonkeyCybersecurity/eos/pkg/execute" "github.com/CodeMonkeyCybersecurity/eos/pkg/platform" "github.com/cockroachdb/errors" - "go.opentelemetry.io/otel" "go.uber.org/zap" ) -var tracer = otel.Tracer("eos/pkg/packer") - // EnsureInstalled installs Packer cross-platform with full logging, tracing, and error wrapping func EnsureInstalled(rc *eos_io.RuntimeContext, log *zap.Logger) error { diff --git a/pkg/parse/append.go b/pkg/parse/append.go index 77f554504..9b7290b72 100644 --- a/pkg/parse/append.go +++ b/pkg/parse/append.go @@ -20,7 +20,7 @@ func AppendIfMissing(path, line string) error { if err != nil { return err } - defer f.Close() + defer func() { _ = f.Close() }() _, err = f.WriteString("\n" + line + "\n") return err } diff --git a/pkg/stackstorm/emailer.go b/pkg/stackstorm/emailer.go index 06142dd07..112eee231 100644 --- a/pkg/stackstorm/emailer.go +++ b/pkg/stackstorm/emailer.go @@ -176,7 +176,7 @@ func (e *Emailer) Run(ctx context.Context) error { case ev := <-e.fs.Events: if ev.Op&(fsnotify.Write|fsnotify.Create) != 0 { if err := e.consume(ctx, &pos); err != nil { - e.log.Logger.Warn("consume", zap.Error(err)) + e.log.Warn("consume", zap.Error(err)) } } case err := <-e.fs.Errors: diff --git a/pkg/stackstorm/watcher.go b/pkg/stackstorm/watcher.go index 4988aa4a8..4f6cf0387 100644 --- a/pkg/stackstorm/watcher.go +++ b/pkg/stackstorm/watcher.go @@ -36,9 +36,9 @@ func StartWatcher(ctx context.Context, cfg *Config, log *zap.Logger, store HashS // ----------------------------------------------------------------------------- var ( - reSplit = regexp.MustCompile(`\| chatgpt_response:`) - failTimes []time.Time - maxFailsPM = 3 + reSplit = regexp.MustCompile(`\| chatgpt_response:`) + failTimes []time.Time + maxFailsPM = 3 ) func runWatcher(ctx context.Context, cfg *Config, log *zap.Logger, w *fsnotify.Watcher, store HashStore, sender SMTPSender) { @@ -65,8 +65,10 @@ func consume(ctx context.Context, path string, pos *int64, log *zap.Logger, stor if err != nil { return err } - defer f.Close() - f.Seek(*pos, io.SeekStart) + defer func() { _ = f.Close() }() + if _, err := f.Seek(*pos, io.SeekStart); err != nil { + return err + } sc := bufio.NewScanner(f) for sc.Scan() { line := sc.Text() @@ -82,17 +84,17 @@ func consume(ctx context.Context, path string, pos *int64, log *zap.Logger, stor continue } - // Render - rendered, err := alerts.RenderEmail(alerts.Alert{ - Time: time.Now(), - Severity: 5, - Title: alert, - Description: reply, - }) - if err != nil { - log.Error("render email", zap.Error(err)) - continue - } + // Render + rendered, err := alerts.RenderEmail(alerts.Alert{ + Time: time.Now(), + Severity: 5, + Title: alert, + Description: reply, + }) + if err != nil { + log.Error("render email", zap.Error(err)) + continue + } // Rate-limit on recent failures if !canSend() { diff --git a/pkg/terraform/terraform.go b/pkg/terraform/terraform.go index 65b1e2ab4..655345623 100644 --- a/pkg/terraform/terraform.go +++ b/pkg/terraform/terraform.go @@ -29,12 +29,12 @@ type Manager struct { func NewManager(rc *eos_io.RuntimeContext, workingDir string) *Manager { config := &TerraformConfig{ - WorkingDir: workingDir, - Variables: make(map[string]interface{}), + WorkingDir: workingDir, + Variables: make(map[string]interface{}), BackendConfig: make(map[string]string), - Providers: []string{}, + Providers: []string{}, } - + return &Manager{ Config: config, } @@ -43,20 +43,20 @@ func NewManager(rc *eos_io.RuntimeContext, workingDir string) *Manager { func (m *Manager) Init(rc *eos_io.RuntimeContext) error { logger := otelzap.Ctx(rc.Ctx) logger.Info("Initializing Terraform", zap.String("dir", m.Config.WorkingDir)) - + if err := os.MkdirAll(m.Config.WorkingDir, 0755); err != nil { return fmt.Errorf("failed to create working directory: %w", err) } - + cmd := exec.CommandContext(rc.Ctx, "terraform", "init") cmd.Dir = m.Config.WorkingDir - + output, err := cmd.CombinedOutput() if err != nil { logger.Error("Terraform init failed", zap.Error(err), zap.String("output", string(output))) return fmt.Errorf("terraform init failed: %w", err) } - + logger.Info("Terraform initialized successfully") return nil } @@ -64,56 +64,56 @@ func (m *Manager) Init(rc *eos_io.RuntimeContext) error { func (m *Manager) Plan(rc *eos_io.RuntimeContext) error { logger := otelzap.Ctx(rc.Ctx) logger.Info("Running Terraform plan", zap.String("dir", m.Config.WorkingDir)) - + args := []string{"plan"} if m.Config.StateFile != "" { args = append(args, "-state="+m.Config.StateFile) } - + for key, value := range m.Config.Variables { args = append(args, fmt.Sprintf("-var=%s=%v", key, value)) } - + cmd := exec.CommandContext(rc.Ctx, "terraform", args...) cmd.Dir = m.Config.WorkingDir cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - + if err := cmd.Run(); err != nil { logger.Error("Terraform plan failed", zap.Error(err)) return fmt.Errorf("terraform plan failed: %w", err) } - + return nil } func (m *Manager) Apply(rc *eos_io.RuntimeContext, autoApprove bool) error { logger := otelzap.Ctx(rc.Ctx) logger.Info("Running Terraform apply", zap.String("dir", m.Config.WorkingDir)) - + args := []string{"apply"} if autoApprove { args = append(args, "-auto-approve") } - + if m.Config.StateFile != "" { args = append(args, "-state="+m.Config.StateFile) } - + for key, value := range m.Config.Variables { args = append(args, fmt.Sprintf("-var=%s=%v", key, value)) } - + cmd := exec.CommandContext(rc.Ctx, "terraform", args...) cmd.Dir = m.Config.WorkingDir cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - + if err := cmd.Run(); err != nil { logger.Error("Terraform apply failed", zap.Error(err)) return fmt.Errorf("terraform apply failed: %w", err) } - + logger.Info("Terraform apply completed successfully") return nil } @@ -121,30 +121,30 @@ func (m *Manager) Apply(rc *eos_io.RuntimeContext, autoApprove bool) error { func (m *Manager) Destroy(rc *eos_io.RuntimeContext, autoApprove bool) error { logger := otelzap.Ctx(rc.Ctx) logger.Info("Running Terraform destroy", zap.String("dir", m.Config.WorkingDir)) - + args := []string{"destroy"} if autoApprove { args = append(args, "-auto-approve") } - + if m.Config.StateFile != "" { args = append(args, "-state="+m.Config.StateFile) } - + for key, value := range m.Config.Variables { args = append(args, fmt.Sprintf("-var=%s=%v", key, value)) } - + cmd := exec.CommandContext(rc.Ctx, "terraform", args...) cmd.Dir = m.Config.WorkingDir cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - + if err := cmd.Run(); err != nil { logger.Error("Terraform destroy failed", zap.Error(err)) return fmt.Errorf("terraform destroy failed: %w", err) } - + logger.Info("Terraform destroy completed successfully") return nil } @@ -154,64 +154,64 @@ func (m *Manager) Output(rc *eos_io.RuntimeContext, outputName string) (string, if outputName != "" { args = append(args, outputName) } - + if m.Config.StateFile != "" { args = append(args, "-state="+m.Config.StateFile) } - + cmd := exec.CommandContext(rc.Ctx, "terraform", args...) cmd.Dir = m.Config.WorkingDir - + output, err := cmd.Output() if err != nil { return "", fmt.Errorf("terraform output failed: %w", err) } - + return strings.TrimSpace(string(output)), nil } func (m *Manager) GenerateFromTemplate(templatePath string, outputPath string, data interface{}) error { // Note: This method doesn't have access to RuntimeContext, so no logging - + tmpl, err := template.ParseFiles(templatePath) if err != nil { return fmt.Errorf("failed to parse template: %w", err) } - + outputFile := filepath.Join(m.Config.WorkingDir, outputPath) file, err := os.Create(outputFile) if err != nil { return fmt.Errorf("failed to create output file: %w", err) } - defer file.Close() - + defer func() { _ = file.Close() }() + if err := tmpl.Execute(file, data); err != nil { return fmt.Errorf("failed to execute template: %w", err) } - + // File generated successfully return nil } func (m *Manager) GenerateFromString(templateStr string, outputPath string, data interface{}) error { // Note: This method doesn't have access to RuntimeContext, so no logging - + tmpl, err := template.New("terraform").Parse(templateStr) if err != nil { return fmt.Errorf("failed to parse template string: %w", err) } - + outputFile := filepath.Join(m.Config.WorkingDir, outputPath) file, err := os.Create(outputFile) if err != nil { return fmt.Errorf("failed to create output file: %w", err) } - defer file.Close() - + defer func() { _ = file.Close() }() + if err := tmpl.Execute(file, data); err != nil { return fmt.Errorf("failed to execute template: %w", err) } - + // File generated successfully return nil } @@ -231,16 +231,16 @@ func (m *Manager) AddProvider(provider string) { func (m *Manager) Validate(rc *eos_io.RuntimeContext) error { logger := otelzap.Ctx(rc.Ctx) logger.Info("Validating Terraform configuration", zap.String("dir", m.Config.WorkingDir)) - + cmd := exec.CommandContext(rc.Ctx, "terraform", "validate") cmd.Dir = m.Config.WorkingDir - + output, err := cmd.CombinedOutput() if err != nil { logger.Error("Terraform validation failed", zap.Error(err), zap.String("output", string(output))) return fmt.Errorf("terraform validation failed: %w", err) } - + logger.Info("Terraform configuration is valid") return nil } @@ -248,15 +248,15 @@ func (m *Manager) Validate(rc *eos_io.RuntimeContext) error { func (m *Manager) Format(rc *eos_io.RuntimeContext) error { logger := otelzap.Ctx(rc.Ctx) logger.Info("Formatting Terraform files", zap.String("dir", m.Config.WorkingDir)) - + cmd := exec.CommandContext(rc.Ctx, "terraform", "fmt", "-recursive") cmd.Dir = m.Config.WorkingDir - + if err := cmd.Run(); err != nil { logger.Error("Terraform fmt failed", zap.Error(err)) return fmt.Errorf("terraform fmt failed: %w", err) } - + logger.Info("Terraform files formatted successfully") return nil } @@ -267,4 +267,4 @@ func CheckTerraformInstalled() error { return fmt.Errorf("terraform is not installed or not in PATH") } return nil -} \ No newline at end of file +} diff --git a/pkg/testutil/filesystem.go b/pkg/testutil/filesystem.go index e694f8673..fca2e68e1 100644 --- a/pkg/testutil/filesystem.go +++ b/pkg/testutil/filesystem.go @@ -8,7 +8,7 @@ import ( ) // ===================================== -// File System Testing Utilities +// File System Testing Utilities // ===================================== // TempDir creates a temporary directory for testing and ensures cleanup @@ -79,12 +79,18 @@ func AssertFileContent(t *testing.T, path, expected string) { func WithEnvVar(t *testing.T, key, value string) func() { t.Helper() original := os.Getenv(key) - os.Setenv(key, value) + if err := os.Setenv(key, value); err != nil { + t.Fatalf("setenv %s: %v", key, err) + } return func() { if original == "" { - os.Unsetenv(key) + if err := os.Unsetenv(key); err != nil { + t.Fatalf("unsetenv %s: %v", key, err) + } } else { - os.Setenv(key, original) + if err := os.Setenv(key, original); err != nil { + t.Fatalf("setenv %s: %v", key, err) + } } } } @@ -93,10 +99,14 @@ func WithEnvVar(t *testing.T, key, value string) func() { func WithoutEnvVar(t *testing.T, key string) func() { t.Helper() original := os.Getenv(key) - os.Unsetenv(key) + if err := os.Unsetenv(key); err != nil { + t.Fatalf("unsetenv %s: %v", key, err) + } return func() { if original != "" { - os.Setenv(key, original) + if err := os.Setenv(key, original); err != nil { + t.Fatalf("setenv %s: %v", key, err) + } } } } @@ -105,19 +115,19 @@ func WithoutEnvVar(t *testing.T, key string) func() { func SetupTestEnvironment(t *testing.T) func() { t.Helper() cleanups := []func(){} - + // Set test environment variables testVars := map[string]string{ "EOS_TEST_MODE": "true", "VAULT_ADDR": "http://127.0.0.1:8200", "VAULT_SKIP_VERIFY": "true", } - + for key, value := range testVars { cleanup := WithEnvVar(t, key, value) cleanups = append(cleanups, cleanup) } - + return func() { for _, cleanup := range cleanups { cleanup() @@ -133,14 +143,14 @@ func SetupTestEnvironment(t *testing.T) func() { func Eventually(t *testing.T, condition func() bool, timeout time.Duration, interval time.Duration) { t.Helper() deadline := time.Now().Add(timeout) - + for time.Now().Before(deadline) { if condition() { return } time.Sleep(interval) } - + t.Fatalf("condition was not met within %v", timeout) } @@ -148,7 +158,7 @@ func Eventually(t *testing.T, condition func() bool, timeout time.Duration, inte func Consistently(t *testing.T, condition func() bool, duration time.Duration, interval time.Duration) { t.Helper() deadline := time.Now().Add(duration) - + for time.Now().Before(deadline) { if !condition() { t.Fatal("condition failed during consistency check") @@ -161,18 +171,18 @@ func Consistently(t *testing.T, condition func() bool, duration time.Duration, i func ParallelTest(t *testing.T, numGoroutines int, testFunc func(t *testing.T, workerID int)) { t.Helper() t.Parallel() - + done := make(chan bool, numGoroutines) - + for i := 0; i < numGoroutines; i++ { go func(workerID int) { defer func() { done <- true }() testFunc(t, workerID) }(i) } - + // Wait for all goroutines to complete for i := 0; i < numGoroutines; i++ { <-done } -} \ No newline at end of file +} diff --git a/pkg/testutil/integration.go b/pkg/testutil/integration.go index b6a29bb28..49ef52317 100644 --- a/pkg/testutil/integration.go +++ b/pkg/testutil/integration.go @@ -28,7 +28,7 @@ type IntegrationTestSuite struct { // NewIntegrationTestSuite creates a new integration testing environment func NewIntegrationTestSuite(t *testing.T, suiteName string) *IntegrationTestSuite { t.Helper() - + // Initialize telemetry for integration tests err := telemetry.Init("eos-integration-test-" + suiteName) if err != nil { @@ -60,7 +60,7 @@ func (s *IntegrationTestSuite) setupEnvironment() { // Create directory structure dirs := []string{ "vault/tls", - "vault/data", + "vault/data", "eos/secrets", "eos/config", "logs", @@ -120,13 +120,13 @@ func (s *IntegrationTestSuite) setTestEnvironment() { // Store original values for cleanup envVars := map[string]string{ - "EOS_TEST_MODE": "true", - "VAULT_SKIP_VERIFY": "true", - "VAULT_CACERT": filepath.Join(s.tempDir, "vault/tls/tls.crt"), - shared.VaultAddrEnv: "http://127.0.0.1:8200", - "EOS_DATA_DIR": filepath.Join(s.tempDir, "eos"), - "EOS_LOG_LEVEL": "debug", - "EOS_LOG_PATH": filepath.Join(s.tempDir, "logs/eos.log"), + "EOS_TEST_MODE": "true", + "VAULT_SKIP_VERIFY": "true", + "VAULT_CACERT": filepath.Join(s.tempDir, "vault/tls/tls.crt"), + shared.VaultAddrEnv: "http://127.0.0.1:8200", + "EOS_DATA_DIR": filepath.Join(s.tempDir, "eos"), + "EOS_LOG_LEVEL": "debug", + "EOS_LOG_PATH": filepath.Join(s.tempDir, "logs/eos.log"), } originalValues := make(map[string]string) @@ -139,9 +139,9 @@ func (s *IntegrationTestSuite) setTestEnvironment() { s.AddCleanup(func() { for key, originalValue := range originalValues { if originalValue == "" { - os.Unsetenv(key) + _ = os.Unsetenv(key) } else { - os.Setenv(key, originalValue) + _ = os.Setenv(key, originalValue) } } }) @@ -155,10 +155,10 @@ func (s *IntegrationTestSuite) CreateTestContext(commandName string) *eos_io.Run ctx := context.Background() rc := eos_io.NewContext(ctx, commandName) - + // Track for cleanup s.contexts = append(s.contexts, rc) - + return rc } @@ -175,7 +175,7 @@ func (s *IntegrationTestSuite) WithVaultMock() { s.WithMockTransport(VaultMockTransport()) } -// WithDockerMock sets up a complete mock Docker environment +// WithDockerMock sets up a complete mock Docker environment func (s *IntegrationTestSuite) WithDockerMock() { s.t.Helper() s.WithMockTransport(DockerMockTransport()) @@ -241,7 +241,7 @@ func (s *IntegrationTestSuite) RunScenario(scenario TestScenario) { s.t.Helper() s.t.Run(scenario.Name, func(t *testing.T) { t.Helper() - + // Run setup if provided if scenario.Setup != nil { scenario.Setup(s) @@ -314,7 +314,7 @@ func (s *IntegrationTestSuite) ExecuteCommand(cmd *cobra.Command, args []string) // Set up command args cmd.SetArgs(args) - + // Capture output cmd.SetOut(os.Stdout) cmd.SetErr(os.Stderr) @@ -384,4 +384,4 @@ func (s *IntegrationTestSuite) AssertFileContent(relativePath, expectedContent s s.t.Helper() fullPath := filepath.Join(s.tempDir, relativePath) AssertFileContent(s.t, fullPath, expectedContent) -} \ No newline at end of file +} diff --git a/pkg/vault/file_security.go b/pkg/vault/file_security.go index 9b6fcd7e7..4d2744280 100644 --- a/pkg/vault/file_security.go +++ b/pkg/vault/file_security.go @@ -16,7 +16,7 @@ const SecureFilePermissions = 0600 // Owner read/write only // ValidateTokenFilePermissions checks if a token file has secure permissions func ValidateTokenFilePermissions(rc *eos_io.RuntimeContext, filePath string) error { log := otelzap.Ctx(rc.Ctx) - + // Check if file exists info, err := os.Stat(filePath) if os.IsNotExist(err) { @@ -26,7 +26,7 @@ func ValidateTokenFilePermissions(rc *eos_io.RuntimeContext, filePath string) er if err != nil { return fmt.Errorf("failed to stat token file %s: %w", filePath, err) } - + // Check permissions perms := info.Mode().Perm() if perms != SecureFilePermissions { @@ -35,15 +35,15 @@ func ValidateTokenFilePermissions(rc *eos_io.RuntimeContext, filePath string) er zap.String("current_perms", fmt.Sprintf("%o", perms)), zap.String("required_perms", fmt.Sprintf("%o", SecureFilePermissions)), ) - return fmt.Errorf("token file %s has insecure permissions %o, should be %o", + return fmt.Errorf("token file %s has insecure permissions %o, should be %o", filePath, perms, SecureFilePermissions) } - + // Check for setuid/setgid bits (security risk) if info.Mode()&os.ModeSetuid != 0 || info.Mode()&os.ModeSetgid != 0 { return fmt.Errorf("token file %s has setuid/setgid bits set - security risk", filePath) } - + log.Debug("āœ… Token file permissions are secure", zap.String("file", filePath)) return nil } @@ -51,26 +51,26 @@ func ValidateTokenFilePermissions(rc *eos_io.RuntimeContext, filePath string) er // SecureWriteTokenFile writes a token to a file with secure permissions func SecureWriteTokenFile(rc *eos_io.RuntimeContext, filePath, token string) error { log := otelzap.Ctx(rc.Ctx) - + // Ensure parent directory exists dir := filepath.Dir(filePath) if err := os.MkdirAll(dir, 0700); err != nil { return fmt.Errorf("failed to create token directory %s: %w", dir, err) } - + // Write file with secure permissions if err := os.WriteFile(filePath, []byte(token), SecureFilePermissions); err != nil { return fmt.Errorf("failed to write token file %s: %w", filePath, err) } - + // Double-check permissions were set correctly if err := ValidateTokenFilePermissions(rc, filePath); err != nil { // If validation fails, remove the file to prevent security risk - os.Remove(filePath) + _ = os.Remove(filePath) return fmt.Errorf("token file written with insecure permissions: %w", err) } - - log.Info("šŸ”’ Token file written securely", + + log.Info("šŸ”’ Token file written securely", zap.String("file", filePath), zap.String("permissions", fmt.Sprintf("%o", SecureFilePermissions)), ) @@ -80,30 +80,30 @@ func SecureWriteTokenFile(rc *eos_io.RuntimeContext, filePath, token string) err // SecureReadTokenFile reads a token file with permission validation func SecureReadTokenFile(rc *eos_io.RuntimeContext, filePath string) (string, error) { log := otelzap.Ctx(rc.Ctx) - + // Validate permissions before reading if err := ValidateTokenFilePermissions(rc, filePath); err != nil { - log.Warn("🚨 Refusing to read token file with insecure permissions", + log.Warn("🚨 Refusing to read token file with insecure permissions", zap.String("file", filePath), zap.Error(err), ) return "", fmt.Errorf("token file security validation failed: %w", err) } - + // Read the file data, err := os.ReadFile(filePath) if err != nil { return "", fmt.Errorf("failed to read token file %s: %w", filePath, err) } - + token := string(data) - + // Basic token format validation (Vault tokens start with hvs. or s.) if token != "" && !isValidVaultTokenFormat(token) { log.Warn("🚨 Token file contains invalid format", zap.String("file", filePath)) return "", fmt.Errorf("token file %s contains invalid token format", filePath) } - + log.Debug("šŸ”‘ Token file read successfully", zap.String("file", filePath)) return token, nil } @@ -111,38 +111,38 @@ func SecureReadTokenFile(rc *eos_io.RuntimeContext, filePath string) (string, er // isValidVaultTokenFormat performs basic validation of vault token format func isValidVaultTokenFormat(token string) bool { token = trimWhitespace(token) - + // Vault tokens typically start with hvs. (new format) or s. (legacy) // or are UUIDs for older versions if len(token) < 8 { return false } - + // New Vault service tokens start with hvs. if len(token) > 4 && token[:4] == "hvs." { return true } - + // Legacy service tokens start with s. if len(token) > 2 && token[:2] == "s." { return true } - + // UUID format (legacy) - 36 characters with hyphens if len(token) == 36 && token[8] == '-' && token[13] == '-' && token[18] == '-' && token[23] == '-' { return true } - + // Allow any token that's alphanumeric with dots and hyphens (be permissive for testing) for _, char := range token { - if !((char >= 'a' && char <= 'z') || - (char >= 'A' && char <= 'Z') || - (char >= '0' && char <= '9') || - char == '.' || char == '-' || char == '_') { + if !((char >= 'a' && char <= 'z') || + (char >= 'A' && char <= 'Z') || + (char >= '0' && char <= '9') || + char == '.' || char == '-' || char == '_') { return false } } - + return len(token) >= 8 } @@ -156,4 +156,4 @@ func trimWhitespace(s string) string { s = s[:len(s)-1] } return s -} \ No newline at end of file +} diff --git a/pkg/verify/types.go b/pkg/verify/types.go index 0171ecc07..0440a3070 100644 --- a/pkg/verify/types.go +++ b/pkg/verify/types.go @@ -2,10 +2,6 @@ package verify -import "cuelang.org/go/cue/cuecontext" - -var cueCtx = cuecontext.New() - type Context struct { Cfg any SchemaPath string