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
89 changes: 85 additions & 4 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,98 @@
package cmd

import (
"fmt"
"os/exec"
"strings"

"github.com/robbycochran/harness-openshell/internal/gateway"
"github.com/robbycochran/harness-openshell/internal/runner"
"github.com/robbycochran/harness-openshell/internal/status"
"github.com/spf13/cobra"
)

func NewDeployCmd(harnessDir string) *cobra.Command {
return &cobra.Command{
func NewDeployCmd(harnessDir, cli string) *cobra.Command {
var (
local bool
remote bool
)

cmd := &cobra.Command{
Use: "deploy [--local|--remote]",
Short: "Deploy or verify the gateway",
DisableFlagParsing: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runner.RunScript(harnessDir, "deploy.sh", args...)
if remote {
return runner.RunScript(harnessDir, "deploy.sh", "--remote")
}
if local {
gw := gateway.NewCLI(cli)
return deployLocal(gw)
}
return fmt.Errorf("specify --local or --remote")
},
}

cmd.Flags().BoolVar(&local, "local", false, "Verify local podman gateway")
cmd.Flags().BoolVar(&remote, "remote", false, "Deploy to OpenShift cluster")

return cmd
}

func deployLocal(gw gateway.Gateway) error {
// CLI check
cliPath := gw.CLIPath()
if cliPath == "" {
return fmt.Errorf("openshell CLI not found. Install it first:\n curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh")
}

// Podman check
status.Section("Container Runtime")
podmanPath, _ := exec.LookPath("podman")
if podmanPath == "" {
status.Fail("Podman not found")
return fmt.Errorf("podman is required")
}
cmd := exec.Command("podman", "--version")
out, _ := cmd.Output()
status.OKf("Podman: %s", strings.TrimSpace(string(out)))

// Find local gateway
status.Section("Gateway")
gateways, err := gw.GatewayList()
if err != nil {
return fmt.Errorf("listing gateways: %w", err)
}

var localGW string
for _, g := range gateways {
if strings.Contains(g.Endpoint, "127.0.0.1") {
localGW = g.Name
break
}
}

if localGW == "" {
status.Fail("No local gateway found")
fmt.Println()
fmt.Println(" Install OpenShell (auto-registers the gateway):")
fmt.Println(" curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh")
return fmt.Errorf("no local gateway")
}

gw.GatewaySelect(localGW)

if gw.InferenceGet() == nil {
status.OKf("%s (active, reachable)", localGW)
} else {
status.Failf("%s (not responding)", localGW)
fmt.Println()
fmt.Println(" Start the gateway:")
fmt.Println(" macOS: brew services start openshell")
fmt.Println(" Linux: systemctl --user start openshell")
return fmt.Errorf("gateway not responding")
}

fmt.Println()
fmt.Println("Done.")
return nil
}
23 changes: 16 additions & 7 deletions cmd/new_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,22 @@ func (m *mockGW) SandboxDelete(name string) error {
m.deletedNames = append(m.deletedNames, name)
return nil
}
func (m *mockGW) CLIVersion() string { return "openshell v0.0.55" }
func (m *mockGW) CLIPath() string { return "/usr/bin/openshell" }
func (m *mockGW) InferenceModel() string { return "" }
func (m *mockGW) ActiveGateway() string { return "" }
func (m *mockGW) SandboxConnect(string) error { return nil }
func (m *mockGW) SandboxUpload(string, string, string) error { return nil }
func (m *mockGW) SandboxExec(string, ...string) error { return nil }
func (m *mockGW) CLIVersion() string { return "openshell v0.0.55" }
func (m *mockGW) CLIPath() string { return "/usr/bin/openshell" }
func (m *mockGW) InferenceModel() string { return "" }
func (m *mockGW) InferenceSet(string, string) error { return nil }
func (m *mockGW) InferenceRemove() error { return nil }
func (m *mockGW) ActiveGateway() string { return "" }
func (m *mockGW) ProviderCreate(string, string, gateway.ProviderCreateOpts) error { return nil }
func (m *mockGW) ProviderDelete(string) error { return nil }
func (m *mockGW) ProviderProfileImport(string) error { return nil }
func (m *mockGW) SettingsSet(string, string) error { return nil }
func (m *mockGW) SandboxList() ([]string, error) { return nil, nil }
func (m *mockGW) SandboxConnect(string) error { return nil }
func (m *mockGW) SandboxUpload(string, string, string) error { return nil }
func (m *mockGW) SandboxExec(string, ...string) error { return nil }
func (m *mockGW) GatewayList() ([]gateway.GatewayInfo, error) { return nil, nil }
func (m *mockGW) GatewaySelect(string) error { return nil }

func setupTestProfile(t *testing.T) string {
t.Helper()
Expand Down
162 changes: 156 additions & 6 deletions cmd/providers.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,167 @@
package cmd

import (
"github.com/robbycochran/harness-openshell/internal/runner"
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/robbycochran/harness-openshell/internal/gateway"
"github.com/robbycochran/harness-openshell/internal/status"
"github.com/spf13/cobra"
)

func NewProvidersCmd(harnessDir string) *cobra.Command {
return &cobra.Command{
Use: "providers [--force]",
func NewProvidersCmd(harnessDir, cli string) *cobra.Command {
var force bool

cmd := &cobra.Command{
Use: "providers",
Short: "Register providers with the gateway",
DisableFlagParsing: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runner.RunScript(harnessDir, "providers.sh", args...)
gw := gateway.NewCLI(cli)
return registerProviders(harnessDir, gw, force)
},
}

cmd.Flags().BoolVar(&force, "force", false, "Delete and recreate all providers")

return cmd
}

func registerProviders(harnessDir string, gw gateway.Gateway, force bool) error {
model := os.Getenv("OPENSHELL_MODEL")
if model == "" {
model = "claude-sonnet-4-6"
}

// Force mode: require no running sandboxes
if force {
sandboxes, _ := gw.SandboxList()
if len(sandboxes) > 0 {
return fmt.Errorf("cannot --force with running sandboxes — delete them first")
}
for _, name := range []string{"github", "vertex-local", "atlassian"} {
gw.ProviderDelete(name)
}
fmt.Println("Deleted existing providers.")
}

// Enable providers v2
status.Section("Enabling providers v2")
if err := gw.SettingsSet("providers_v2_enabled", "true"); err != nil {
return fmt.Errorf("enabling providers v2: %w", err)
}

// Import custom profiles
status.Section("Importing custom profiles")
profilesDir := filepath.Join(harnessDir, "sandbox", "profiles")
if err := gw.ProviderProfileImport(profilesDir); err != nil {
fmt.Println(" (already imported)")
}

// Register providers
status.Section("Registering providers")

// GitHub
if token := os.Getenv("GITHUB_TOKEN"); token != "" {
if gw.ProviderGet("github") != nil {
if err := gw.ProviderCreate("github", "github", gateway.ProviderCreateOpts{
Credentials: []string{"GITHUB_TOKEN=" + token},
}); err != nil {
return fmt.Errorf("creating github provider: %w", err)
}
fmt.Println(" github — registered")
} else {
fmt.Println(" github — exists (use --force to recreate)")
}
} else {
fmt.Println(" github — skipped (GITHUB_TOKEN not set)")
}

// Vertex AI
home, _ := os.UserHomeDir()
adcPath := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
if adcPath == "" {
adcPath = filepath.Join(home, ".config", "gcloud", "application_default_credentials.json")
}
project := os.Getenv("ANTHROPIC_VERTEX_PROJECT_ID")
region := os.Getenv("CLOUD_ML_REGION")
if region == "" {
region = "global"
}

if project == "" {
project = readADCProject(adcPath)
}

if fileExists(adcPath) && project != "" {
if gw.ProviderGet("vertex-local") != nil {
if err := gw.ProviderCreate("vertex-local", "google-vertex-ai", gateway.ProviderCreateOpts{
FromADC: true,
Configs: []string{
"VERTEX_AI_PROJECT_ID=" + project,
"VERTEX_AI_REGION=" + region,
},
}); err != nil {
return fmt.Errorf("creating vertex-local provider: %w", err)
}
fmt.Printf(" vertex-local — registered (project: %s, region: %s)\n", project, region)
} else {
fmt.Println(" vertex-local — exists (use --force to recreate)")
}
if err := gw.InferenceSet("vertex-local", model); err != nil {
return fmt.Errorf("setting inference: %w", err)
}
fmt.Printf(" inference — model: %s\n", model)
} else if !fileExists(adcPath) {
fmt.Printf(" vertex-local — skipped (no ADC file at %s)\n", adcPath)
} else {
fmt.Println(" vertex-local — skipped (no project ID — set ANTHROPIC_VERTEX_PROJECT_ID or run gcloud auth application-default login)")
}

// Atlassian
if token := os.Getenv("JIRA_API_TOKEN"); token != "" {
if gw.ProviderGet("atlassian") != nil {
if err := gw.ProviderCreate("atlassian", "atlassian", gateway.ProviderCreateOpts{
Credentials: []string{"JIRA_API_TOKEN=" + token},
}); err != nil {
return fmt.Errorf("creating atlassian provider: %w", err)
}
fmt.Println(" atlassian — registered")
} else {
fmt.Println(" atlassian — exists (use --force to recreate)")
}
} else {
fmt.Println(" atlassian — skipped (JIRA_API_TOKEN not set)")
}

// Show results
status.Section("Providers")
gw.ProviderList()

status.Section("Inference")
gw.InferenceGet()

fmt.Println()
fmt.Println("Done.")
return nil
}

func readADCProject(path string) string {
data, err := os.ReadFile(path)
if err != nil {
return ""
}
var adc struct {
QuotaProjectID string `json:"quota_project_id"`
}
if json.Unmarshal(data, &adc) != nil {
return ""
}
return adc.QuotaProjectID
}

func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
Loading