Skip to content
Open
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
38 changes: 38 additions & 0 deletions cmd/obol/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"path/filepath"
"strings"

"github.com/ObolNetwork/obol-stack/internal/agent"
"github.com/ObolNetwork/obol-stack/internal/app"
"github.com/ObolNetwork/obol-stack/internal/config"
"github.com/ObolNetwork/obol-stack/internal/executor"
Expand Down Expand Up @@ -45,6 +46,9 @@ COMMANDS:
stack down Stop the Obol Stack
stack purge Delete stack config (use --force to also delete data)

Obol Agent:
agent init Initialize Obol Agent with Google API key

Kubernetes Tools (with auto-configured KUBECONFIG):
kubectl Run kubectl with stack kubeconfig (passthrough)
helm Run helm with stack kubeconfig (passthrough)
Expand Down Expand Up @@ -151,6 +155,40 @@ GLOBAL OPTIONS:
},
},
// ============================================================
// Obol Agent Commands
// ============================================================
{
Name: "agent",
Usage: "Manage Obol Agent",
Subcommands: []*cli.Command{
{
Name: "init",
Usage: "Initialize Obol Agent with Google API key",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "google-api-key",
Aliases: []string{"g"},
Usage: "Google API key for Obol Agent (required for AI features)",
EnvVars: []string{"GOOGLE_API_KEY"},
},
},
Action: func(c *cli.Context) error {
googleAPIKey := c.String("google-api-key")
if err := agent.Init(cfg, googleAPIKey); err != nil {
stackID := stack.GetStackID(cfg)
l, _ := logging.NewSlogLogger(logging.LoggerConfig{
StateDir: cfg.StateDir,
StackID: stackID,
})
l.Error("Failed to initialize agent", "error", err.Error())
return err
}
return nil
},
},
},
},
// ============================================================
// Kubernetes Tool Passthroughs (with auto-configured KUBECONFIG)
// ============================================================
{
Expand Down
84 changes: 84 additions & 0 deletions internal/agent/agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package agent

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/ObolNetwork/obol-stack/internal/config"
"github.com/ObolNetwork/obol-stack/internal/executor"
"github.com/ObolNetwork/obol-stack/internal/logging"
"github.com/ObolNetwork/obol-stack/internal/stack"
)

const (
kubeconfigFile = "kubeconfig.yaml"
)

// Init initializes the Obol Agent with required secrets
func Init(cfg *config.Config, googleAPIKey string) error {
kubeconfigPath := filepath.Join(cfg.ConfigDir, kubeconfigFile)

// Check if kubeconfig exists (stack must be running)
if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) {
return fmt.Errorf("stack not running, use 'obol stack up' first")
}

// Get stack ID for logging
stackID := stack.GetStackID(cfg)
if stackID == "" {
return fmt.Errorf("stack ID not found, run 'obol stack init' first")
}

// Create logger and executor
l, cleanup := logging.NewSlogLogger(logging.LoggerConfig{
StateDir: cfg.StateDir,
StackID: stackID,
})
defer cleanup()

exec := executor.New(l.Logger)
defer exec.Close()

// Validate Google API key was provided
if googleAPIKey == "" {
l.Error("Google API key required")
return fmt.Errorf("Google API key required via --google-api-key flag or GOOGLE_API_KEY environment variable")
}

l.Info("Initializing Obol Agent")
l.Info("Creating Google API key secret for Obol Agent")

kubectlPath := filepath.Join(cfg.BinDir, "kubectl")

// Create namespace (idempotent)
nsCmd := exec.Command(kubectlPath, "--kubeconfig", kubeconfigPath, "create", "namespace", "agent", "--dry-run=client", "-o", "yaml")
nsYAML, err := nsCmd.Output()
if err != nil {
return fmt.Errorf("failed to generate namespace manifest: %w", err)
}
applyNs := exec.CommandWithOutput(kubectlPath, "--kubeconfig", kubeconfigPath, "apply", "-f", "-")
applyNs.SetStdin(strings.NewReader(string(nsYAML)))
if err := applyNs.Run(); err != nil {
return fmt.Errorf("failed to create agent namespace: %w", err)
}

// Create secret (idempotent)
secretCmd := exec.Command(kubectlPath, "--kubeconfig", kubeconfigPath, "create", "secret", "generic", "obol-agent-google-api-key", "--from-literal=GOOGLE_API_KEY="+googleAPIKey, "--namespace=agent", "--dry-run=client", "-o", "yaml")
secretYAML, err := secretCmd.Output()
if err != nil {
return fmt.Errorf("failed to generate secret manifest: %w", err)
}
applySecret := exec.CommandWithOutput(kubectlPath, "--kubeconfig", kubeconfigPath, "apply", "-f", "-")
applySecret.SetStdin(strings.NewReader(string(secretYAML)))
if err := applySecret.Run(); err != nil {
return fmt.Errorf("failed to create Google API key secret: %w", err)
}

l.Success("Google API key secret created")
l.Success("Obol Agent initialized successfully")
l.Info("The Obol Agent deployment will now have access to Google API services")

return nil
}
Loading
Loading