Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions cmd/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
14 changes: 12 additions & 2 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Comment on lines +114 to +119
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The auto-detection logic is called in non-interactive mode, which may cause the test to fail if the test environment has RuntimeClasses with SNP/TDX handlers. The test expects config.DefaultRuntimeClass but the new code calls cluster.DetectRuntimeClass which could return a different value. Consider mocking the DetectRuntimeClass function or updating the test to account for the auto-detection behavior.

Suggested change
// 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)
// Set runtime class from flag if provided. If not provided, only auto-detect in interactive mode;
// in non-interactive mode, fall back to the default to avoid environment-dependent behavior.
if runtimeClass != "" {
cfg.RuntimeClass = runtimeClass
} else if interactive {
// Auto-detect RuntimeClass with SNP or TDX support when interactive
cfg.RuntimeClass = cluster.DetectRuntimeClass(config.DefaultRuntimeClass)
} else {
// Non-interactive and no flag: use the default RuntimeClass directly
cfg.RuntimeClass = config.DefaultRuntimeClass

Copilot uses AI. Check for mistakes.
}

// 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
Expand Down
12 changes: 8 additions & 4 deletions cmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
62 changes: 62 additions & 0 deletions pkg/cluster/runtimeclass.go
Original file line number Diff line number Diff line change
@@ -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
}
Comment on lines +50 to +56
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The substring matching logic for "snp" or "tdx" in handler names could match unintended RuntimeClasses. For example, a handler named "snapshot-runtime" would match because it contains "snp", or "stdx-runtime" would match if it contained "tdx" as a substring. Consider using more precise matching such as checking for word boundaries or exact matches (e.g., handler equals "snp", "tdx", or contains these as complete words separated by hyphens or underscores).

Copilot uses AI. Check for mistakes.
}

// No matching RuntimeClass found, return default
fmt.Printf("No SNP/TDX RuntimeClass found (using default: %s)\n", defaultRuntimeClass)
return defaultRuntimeClass
}