diff --git a/cmd/explain.go b/cmd/explain.go index 4f565ef..9a18e7e 100644 --- a/cmd/explain.go +++ b/cmd/explain.go @@ -48,14 +48,14 @@ Examples: } var ( - explainManifestFile string - explainExample string - explainFormat string - explainListExamples bool - explainConfigPath string - explainEnableSidecar bool - explainSidecarPort int - explainOutput string + explainManifestFile string + explainExample string + explainFormat string + explainListExamples bool + explainConfigPath string + explainEnableSidecar bool + explainSidecarPort int + explainOutput string ) func init() { diff --git a/cmd/init.go b/cmd/init.go index 8a05565..9f1ccc8 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/confidential-devhub/cococtl/pkg/cluster" "github.com/confidential-devhub/cococtl/pkg/config" "github.com/confidential-devhub/cococtl/pkg/sidecar/certs" "github.com/confidential-devhub/cococtl/pkg/trustee" @@ -21,13 +22,14 @@ var initCmd = &cobra.Command{ This command will: - Optionally deploy Trustee KBS to your cluster - Create configuration file with Trustee URL and other settings + - Auto-detect RuntimeClass with SNP or TDX support (falls back to kata-cc) - Optionally set up sidecar certificates (with --enable-sidecar): - Generate Client CA and upload to Trustee KBS - Generate client certificate for developer access - Save client certificate to ~/.kube/coco-sidecar/ - Prompt for configuration values including: - Trustee server URL (or auto-deploy) - - Default RuntimeClass (default: kata-cc) + - Default RuntimeClass (auto-detected, can be overridden) - Trustee CA cert location (optional) - Kata-agent policy file path (optional) - Default init container image (optional) @@ -109,9 +111,17 @@ func runInit(cmd *cobra.Command, _ []string) error { } } - // Set runtime class from flag if provided + // Set runtime class from flag if provided, otherwise auto-detect if runtimeClass != "" { cfg.RuntimeClass = runtimeClass + } else { + // Auto-detect RuntimeClass with SNP or TDX support + cfg.RuntimeClass = cluster.DetectRuntimeClass(config.DefaultRuntimeClass) + } + + // In non-interactive mode, show the RuntimeClass being used + if !interactive { + fmt.Printf("RuntimeClass: %s\n", cfg.RuntimeClass) } // Continue with other configuration prompts if interactive diff --git a/cmd/init_test.go b/cmd/init_test.go index ad8c154..c4d41aa 100644 --- a/cmd/init_test.go +++ b/cmd/init_test.go @@ -82,10 +82,14 @@ func TestInitCommand_WithoutRuntimeClassFlag(t *testing.T) { t.Fatalf("Failed to load config: %v", err) } - // Verify default runtime class is used - if cfg.RuntimeClass != config.DefaultRuntimeClass { - t.Errorf("RuntimeClass = %q, want %q", cfg.RuntimeClass, config.DefaultRuntimeClass) - } + // Verify runtime class is set (either auto-detected or default) + // Auto-detection may succeed or fail depending on cluster state, + // but RuntimeClass should always be non-empty + if cfg.RuntimeClass == "" { + t.Errorf("RuntimeClass is empty, expected auto-detected or default value") + } + // Log what was detected for debugging + t.Logf("RuntimeClass set to: %q", cfg.RuntimeClass) } // TestInitCommand_RuntimeClassWithTrusteeURL tests runtime-class flag with trustee-url diff --git a/pkg/cluster/runtimeclass.go b/pkg/cluster/runtimeclass.go new file mode 100644 index 0000000..ad8156e --- /dev/null +++ b/pkg/cluster/runtimeclass.go @@ -0,0 +1,62 @@ +// Package cluster provides utilities for interacting with Kubernetes clusters. +package cluster + +import ( + "bytes" + "encoding/json" + "fmt" + "os/exec" + "strings" +) + +// runtimeClassList represents the JSON response from kubectl get runtimeclasses +type runtimeClassList struct { + Items []struct { + Metadata struct { + Name string `json:"name"` + } `json:"metadata"` + Handler string `json:"handler"` + } `json:"items"` +} + +// DetectRuntimeClass attempts to auto-detect a RuntimeClass with SNP or TDX support. +// It retrieves all RuntimeClasses from the cluster and selects the first one whose +// handler contains "snp" or "tdx" (case-insensitive). +// Returns the default RuntimeClass if: +// - There's an error retrieving RuntimeClasses (permissions, kubectl not available, etc.) +// - No RuntimeClasses have handlers containing "snp" or "tdx" +func DetectRuntimeClass(defaultRuntimeClass string) string { + // #nosec G204 -- static command with no user-controlled input + cmd := exec.Command("kubectl", "get", "runtimeclasses", "-o", "json") + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + if err := cmd.Run(); err != nil { + // Error retrieving RuntimeClasses (permissions, kubectl not available, etc.) + // Return default + fmt.Printf("Unable to detect RuntimeClasses: %v (stderr: %s) (using default: %s)\n", err, strings.TrimSpace(stderr.String()), defaultRuntimeClass) + return defaultRuntimeClass + } + + var rcList runtimeClassList + if err := json.Unmarshal(stdout.Bytes(), &rcList); err != nil { + // Error parsing JSON, return default + fmt.Printf("Unable to parse RuntimeClasses: %v (using default: %s)\n", err, defaultRuntimeClass) + return defaultRuntimeClass + } + + // Look for RuntimeClasses with handlers containing "snp" or "tdx" + for _, rc := range rcList.Items { + handler := strings.ToLower(rc.Handler) + if strings.Contains(handler, "snp") || strings.Contains(handler, "tdx") { + fmt.Printf("Detected RuntimeClass: %s\n", rc.Metadata.Name) + return rc.Metadata.Name + } + } + + // No matching RuntimeClass found, return default + fmt.Printf("No SNP/TDX RuntimeClass found (using default: %s)\n", defaultRuntimeClass) + return defaultRuntimeClass +}