diff --git a/go/core/cli/internal/common/exec/docker.go b/go/core/cli/internal/common/exec/docker.go deleted file mode 100644 index 65c4087052..0000000000 --- a/go/core/cli/internal/common/exec/docker.go +++ /dev/null @@ -1,95 +0,0 @@ -// Package exec provides utilities for executing external commands. -package exec - -import ( - "fmt" - "os" - "os/exec" - "strings" -) - -// DockerExecutor provides utilities for running Docker commands with consistent -// error handling, logging, and configuration across the CLI. -type DockerExecutor struct { - // Verbose enables detailed logging of Docker commands - Verbose bool - - // WorkDir sets the working directory for Docker commands - WorkDir string -} - -// NewDockerExecutor creates a new Docker executor with the specified configuration. -func NewDockerExecutor(verbose bool, workDir string) *DockerExecutor { - return &DockerExecutor{ - Verbose: verbose, - WorkDir: workDir, - } -} - -// CheckAvailability checks if Docker is installed and the daemon is running. -// Returns an error if Docker is not available or not properly configured. -func (d *DockerExecutor) CheckAvailability() error { - if _, err := exec.LookPath("docker"); err != nil { - return fmt.Errorf("docker command not found in PATH. Please install Docker") - } - - // Check if Docker daemon is running by querying the server version - cmd := exec.Command("docker", "version", "--format", "{{.Server.Version}}") - output, err := cmd.Output() - if err != nil { - return fmt.Errorf("docker daemon is not running or not accessible. Please start Docker Desktop or Docker daemon") - } - - // Verify we got a valid version string - version := strings.TrimSpace(string(output)) - if version == "" { - return fmt.Errorf("docker daemon returned empty version. Docker may not be properly installed") - } - - return nil -} - -// Run executes a docker command with the given arguments. -// It automatically handles verbose logging and working directory configuration. -func (d *DockerExecutor) Run(args ...string) error { - if d.Verbose { - fmt.Printf("Running: docker %s\n", strings.Join(args, " ")) - if d.WorkDir != "" { - fmt.Printf("Working directory: %s\n", d.WorkDir) - } - } - - cmd := exec.Command("docker", args...) - if d.WorkDir != "" { - cmd.Dir = d.WorkDir - } - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - return cmd.Run() -} - -// Build builds a Docker image with the specified name and context. -// Additional build arguments can be provided (e.g., "--platform", "linux/amd64"). -func (d *DockerExecutor) Build(imageName, context string, extraArgs ...string) error { - args := []string{"build", "-t", imageName} - args = append(args, extraArgs...) - args = append(args, context) - - if err := d.Run(args...); err != nil { - return fmt.Errorf("docker build failed: %w", err) - } - - fmt.Printf("✅ Successfully built Docker image: %s\n", imageName) - return nil -} - -// Push pushes a Docker image to a registry. -func (d *DockerExecutor) Push(imageName string) error { - if err := d.Run("push", imageName); err != nil { - return fmt.Errorf("docker push failed: %w", err) - } - - fmt.Printf("✅ Successfully pushed Docker image: %s\n", imageName) - return nil -} diff --git a/go/core/cli/internal/common/exec/kubectl.go b/go/core/cli/internal/common/exec/kubectl.go index d640a9f089..6e92166dca 100644 --- a/go/core/cli/internal/common/exec/kubectl.go +++ b/go/core/cli/internal/common/exec/kubectl.go @@ -1,14 +1,9 @@ package exec import ( - "bytes" - "context" "fmt" - "io" - "os" "os/exec" "strings" - "time" ) // KubectlExecutor provides utilities for running kubectl commands with consistent @@ -29,34 +24,6 @@ func NewKubectlExecutor(verbose bool, namespace string) *KubectlExecutor { } } -// CheckAvailability checks if kubectl is installed and accessible. -func (k *KubectlExecutor) CheckAvailability() error { - cmd := exec.Command("kubectl", "version", "--client") - if err := cmd.Run(); err != nil { - return fmt.Errorf("kubectl not found or not working: %w", err) - } - return nil -} - -// Run executes a kubectl command with the given arguments. -// It automatically handles verbose logging and captures stderr for better error messages. -func (k *KubectlExecutor) Run(args ...string) error { - if k.Verbose { - fmt.Printf("Running: kubectl %s\n", strings.Join(args, " ")) - } - - cmd := exec.Command("kubectl", args...) - var stderr bytes.Buffer - cmd.Stdout = os.Stdout - cmd.Stderr = io.MultiWriter(os.Stderr, &stderr) - - if err := cmd.Run(); err != nil { - return fmt.Errorf("`kubectl %s` failed: %w\n%s", strings.Join(args, " "), err, stderr.String()) - } - - return nil -} - // RunWithOutput executes a kubectl command and returns the combined output. // This is useful for capturing command output for further processing. func (k *KubectlExecutor) RunWithOutput(args ...string) ([]byte, error) { @@ -72,88 +39,3 @@ func (k *KubectlExecutor) RunWithOutput(args ...string) ([]byte, error) { return output, nil } - -// Apply applies YAML resources to the Kubernetes cluster. -// Multiple YAML documents can be provided and will be combined with separators. -func (k *KubectlExecutor) Apply(yamls ...[]byte) error { - fmt.Printf("🚀 Applying resources to cluster...\n") - - // Check if kubectl is available - if err := k.CheckAvailability(); err != nil { - return fmt.Errorf("kubectl is required for cluster deployment: %w", err) - } - - // Create temporary file for kubectl apply - tmpFile, err := os.CreateTemp("", "resources-*.yaml") - if err != nil { - return fmt.Errorf("failed to create temp file: %w", err) - } - defer func() { - if err := os.Remove(tmpFile.Name()); err != nil { - fmt.Printf("Warning: failed to remove temp file: %v\n", err) - } - }() - - // Combine all YAML resources with separators - var combinedYAML []byte - for i, yaml := range yamls { - if i > 0 { - combinedYAML = append(combinedYAML, []byte("\n---\n")...) - } - combinedYAML = append(combinedYAML, yaml...) - } - - if _, err := tmpFile.Write(combinedYAML); err != nil { - return fmt.Errorf("failed to write to temp file: %w", err) - } - if err := tmpFile.Close(); err != nil { - return fmt.Errorf("failed to close temp file: %w", err) - } - - // Apply using kubectl - if err := k.Run("apply", "-f", tmpFile.Name()); err != nil { - return err - } - - fmt.Printf("✅ Resources applied successfully\n") - return nil -} - -// WaitForDeployment waits for a Kubernetes deployment to be ready. -// It uses kubectl rollout status to monitor the deployment progress. -func (k *KubectlExecutor) WaitForDeployment(name string, timeout time.Duration) error { - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - namespace := k.Namespace - if namespace == "" { - namespace = "default" - } - - args := []string{ - "rollout", "status", "deployment", name, - "-n", namespace, - "--timeout", timeout.String(), - } - - cmd := exec.CommandContext(ctx, "kubectl", args...) - if k.Verbose { - fmt.Printf("Running: kubectl %s\n", strings.Join(args, " ")) - } - - var stderr bytes.Buffer - cmd.Stdout = os.Stdout - cmd.Stderr = io.MultiWriter(os.Stderr, &stderr) - - // Sleep briefly to allow controller to create the deployment - time.Sleep(1 * time.Second) - - if err := cmd.Run(); err != nil { - if ctx.Err() == context.DeadlineExceeded { - return fmt.Errorf("timed out waiting for deployment to be ready") - } - return fmt.Errorf("`kubectl rollout status` failed: %w\n%s", err, stderr.String()) - } - - return nil -} diff --git a/go/core/cli/internal/common/fs/project.go b/go/core/cli/internal/common/fs/project.go deleted file mode 100644 index 412b9bc28c..0000000000 --- a/go/core/cli/internal/common/fs/project.go +++ /dev/null @@ -1,10 +0,0 @@ -// Package fs provides filesystem utilities. -package fs - -import "os" - -// FileExists checks if a file exists at the given path. -func FileExists(path string) bool { - _, err := os.Stat(path) - return err == nil -} diff --git a/go/core/cli/internal/common/generator/base.go b/go/core/cli/internal/common/generator/base.go deleted file mode 100644 index f1d05c24b8..0000000000 --- a/go/core/cli/internal/common/generator/base.go +++ /dev/null @@ -1,163 +0,0 @@ -// Package generator provides shared utilities for project code generation. -package generator - -import ( - "fmt" - "io/fs" - "os" - "os/exec" - "path/filepath" - "strings" - "text/template" -) - -// ProjectConfig defines the interface that project configuration must implement. -// This allows both agent and MCP configs to use the same base generator. -type ProjectConfig interface { - // GetDirectory returns the target directory for project generation - GetDirectory() string - - // IsVerbose returns whether verbose logging is enabled - IsVerbose() bool - - // ShouldInitGit returns whether to initialize a git repository - ShouldInitGit() bool - - // ShouldSkipPath returns whether to skip a specific template path - ShouldSkipPath(path string) bool -} - -// BaseGenerator provides common functionality for all project generators. -// It handles template walking, rendering, and file creation with consistent -// error handling and logging across both agent and MCP generators. -type BaseGenerator struct { - // TemplateFiles is the embedded filesystem containing template files - TemplateFiles fs.FS - - // TemplateRoot is the root directory within TemplateFiles (usually "templates") - TemplateRoot string -} - -// NewBaseGenerator creates a new base generator with the specified configuration. -func NewBaseGenerator(templateFiles fs.FS, templateRoot string) *BaseGenerator { - if templateRoot == "" { - templateRoot = "templates" - } - return &BaseGenerator{ - TemplateFiles: templateFiles, - TemplateRoot: templateRoot, - } -} - -// GenerateProject generates a new project using the provided templates. -// It walks through all template files, renders them with the config data, -// and writes the results to the target directory. -func (g *BaseGenerator) GenerateProject(config ProjectConfig) error { - // Get templates subdirectory - templateRoot, err := fs.Sub(g.TemplateFiles, g.TemplateRoot) - if err != nil { - return fmt.Errorf("failed to get templates subdirectory: %w", err) - } - - // Walk through all template files - err = fs.WalkDir(templateRoot, ".", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - // Allow config to skip specific paths (e.g., tool templates, mcp_server dirs) - if config.ShouldSkipPath(path) { - if d.IsDir() { - return fs.SkipDir - } - return nil - } - - // Determine destination path by removing .tmpl extension - destPath := filepath.Join(config.GetDirectory(), strings.TrimSuffix(path, ".tmpl")) - - // Handle directories - if d.IsDir() { - return os.MkdirAll(destPath, 0755) - } - - // Read template file - templateContent, err := fs.ReadFile(templateRoot, path) - if err != nil { - return fmt.Errorf("failed to read template file %s: %w", path, err) - } - - // Render template content - renderedContent, err := g.RenderTemplate(string(templateContent), config) - if err != nil { - return fmt.Errorf("failed to render template for %s: %w", path, err) - } - - // Create file - if err := os.WriteFile(destPath, []byte(renderedContent), 0644); err != nil { - return fmt.Errorf("failed to write file %s: %w", destPath, err) - } - - // Log if verbose - if config.IsVerbose() { - fmt.Printf(" Generated: %s\n", destPath) - } - - return nil - }) - - if err != nil { - return fmt.Errorf("failed to walk templates: %w", err) - } - - // Initialize git repository if requested - if config.ShouldInitGit() { - if err := g.initGitRepo(config.GetDirectory(), config.IsVerbose()); err != nil { - // Don't fail the whole operation if git init fails - if config.IsVerbose() { - fmt.Printf("Warning: failed to initialize git repository: %v\n", err) - } - } - } - - return nil -} - -// RenderTemplate renders a template string with the provided data. -// This is the core template rendering logic used by all generators. -func (g *BaseGenerator) RenderTemplate(tmplContent string, data any) (string, error) { - tmpl, err := template.New("template").Parse(tmplContent) - if err != nil { - return "", fmt.Errorf("failed to parse template: %w", err) - } - - var result strings.Builder - if err := tmpl.Execute(&result, data); err != nil { - return "", fmt.Errorf("failed to execute template: %w", err) - } - - return result.String(), nil -} - -// initGitRepo initializes a git repository in the specified directory. -func (g *BaseGenerator) initGitRepo(dir string, verbose bool) error { - cmd := exec.Command("git", "init") - cmd.Dir = dir - - if verbose { - fmt.Printf(" Initializing git repository...\n") - } - - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to run git init: %w", err) - } - - return nil -} - -// ReadTemplateFile reads a template file from the embedded filesystem. -// This is a convenience method for reading individual template files. -func (g *BaseGenerator) ReadTemplateFile(templatePath string) ([]byte, error) { - fullPath := filepath.Join(g.TemplateRoot, templatePath) - return fs.ReadFile(g.TemplateFiles, fullPath) -} diff --git a/go/core/cli/internal/common/k8s/config.go b/go/core/cli/internal/common/k8s/config.go deleted file mode 100644 index 76911d775f..0000000000 --- a/go/core/cli/internal/common/k8s/config.go +++ /dev/null @@ -1,70 +0,0 @@ -// Package k8s provides utilities for working with Kubernetes configurations. -package k8s - -import ( - "fmt" - "strings" - - "k8s.io/client-go/tools/clientcmd" - - clientset "github.com/kagent-dev/kagent/go/api/clientset/versioned" -) - -// NewKagentClientset builds the generated kagent clientset from the ambient -// kubeconfig. Harness and AgentTemplate are CRDs, so every reader goes through here. -func NewKagentClientset() (clientset.Interface, error) { - restConfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - clientcmd.NewDefaultClientConfigLoadingRules(), - &clientcmd.ConfigOverrides{}, - ).ClientConfig() - if err != nil { - return nil, fmt.Errorf("load Kubernetes client config: %w", err) - } - clients, err := clientset.NewForConfig(restConfig) - if err != nil { - return nil, fmt.Errorf("create Kubernetes client: %w", err) - } - return clients, nil -} - -// GetCurrentNamespace returns the current namespace from the kubeconfig. -// If no namespace is configured, it returns an error. -func GetCurrentNamespace() (string, error) { - config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - clientcmd.NewDefaultClientConfigLoadingRules(), - &clientcmd.ConfigOverrides{}, - ) - - namespace, _, err := config.Namespace() - if err != nil { - return "", fmt.Errorf("failed to get current namespace: %w", err) - } - - return namespace, nil -} - -// GetCurrentKindClusterName extracts the kind cluster name from the current kubeconfig context. -// Returns an error if the current context is not a kind cluster. -func GetCurrentKindClusterName() (string, error) { - config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - clientcmd.NewDefaultClientConfigLoadingRules(), - &clientcmd.ConfigOverrides{}, - ) - - rawConfig, err := config.RawConfig() - if err != nil { - return "", fmt.Errorf("failed to get raw kubeconfig: %w", err) - } - - currentContext, ok := rawConfig.Contexts[rawConfig.CurrentContext] - if !ok { - return "", fmt.Errorf("current context %q not found in kubeconfig", rawConfig.CurrentContext) - } - - const kindPrefix = "kind-" - if after, ok0 := strings.CutPrefix(currentContext.Cluster, kindPrefix); ok0 { - return after, nil - } - - return "", fmt.Errorf("current cluster %q is not a kind cluster", currentContext.Cluster) -} diff --git a/go/core/cli/internal/common/prompt/input.go b/go/core/cli/internal/common/prompt/input.go deleted file mode 100644 index d4e31ae630..0000000000 --- a/go/core/cli/internal/common/prompt/input.go +++ /dev/null @@ -1,21 +0,0 @@ -// Package prompt provides utilities for interactive user input. -package prompt - -import ( - "bufio" - "fmt" - "os" - "strings" -) - -// PromptForInput displays a prompt and reads user input from stdin. -// Returns the trimmed input string or an error if reading fails. -func PromptForInput(promptText string) (string, error) { - fmt.Print(promptText) - reader := bufio.NewReader(os.Stdin) - input, err := reader.ReadString('\n') - if err != nil { - return "", fmt.Errorf("failed to read input: %w", err) - } - return strings.TrimSpace(input), nil -} diff --git a/go/pkg/logging/logging.go b/go/pkg/logging/logging.go index da8d8794a9..3fef7f7baa 100644 --- a/go/pkg/logging/logging.go +++ b/go/pkg/logging/logging.go @@ -56,11 +56,6 @@ func FromContext(ctx context.Context) *slog.Logger { return slog.New(logr.ToSlogHandler(logger)) } -// FromLogr adapts a logger supplied by controller-runtime. -func FromLogr(logger logr.Logger) *slog.Logger { - return slog.New(logr.ToSlogHandler(logger)) -} - // AsLogr adapts logger for dependencies which still require logr. func AsLogr(logger *slog.Logger) logr.Logger { return logr.FromSlogHandler(logger.Handler())