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
30 changes: 27 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ DEV_LAUNCHER_IMAGE := $(DEV_REGISTRY):$(DEV_TAG)-launcher
.PHONY: cli sandbox push-sandbox cli-launcher launcher push-launcher \
vet lint test-unit test test-local test-kind test-ocp test-all validate \
validate-local validate-local-ci validate-kind validate-kind-ci \
validate-ocp validate-ocp-ci \
dev-sandbox dev-launcher validate-dev clean help

## ── CLI ──────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -177,14 +178,19 @@ validate-local-ci: cli
./test/test-flow.sh local --ci

## Default validation on kind: unit tests + full integration with user credentials.
## Requires: kind create cluster --name openshell
validate-kind: cli
## Requires: kind create cluster --name openshell. Builds dev sandbox image to quay.io/rcochran.
validate-kind: cli dev-sandbox
@echo "=== Unit tests ==="
CGO_ENABLED=0 go test ./...
cd sandbox/launcher && go test ./...
@echo ""
@echo "=== Integration: kind gateway, default mode ==="
./test/test-flow.sh kind --full
@kubectl create namespace openshell --dry-run=client -o yaml 2>/dev/null | kubectl apply -f - 2>/dev/null || true
@kubectl create secret docker-registry quay-pull \
--docker-server=quay.io --docker-username=rcochran \
--docker-password="$$(python3 -c "import json,base64,pathlib; d=json.loads(pathlib.Path('$$HOME/.docker/config.json').read_text()); print(base64.b64decode(d['auths']['quay.io']['auth']).decode().split(':',1)[1])")" \
-n openshell --dry-run=client -o yaml 2>/dev/null | kubectl apply -f - 2>/dev/null || true
SANDBOX_IMAGE=$(DEV_SANDBOX_IMAGE) SANDBOX_PULL_SECRET=quay-pull ./test/test-flow.sh kind --full

## CI validation on kind: unit tests + integration without credentials.
## Requires: kind create cluster --name openshell
Expand All @@ -196,6 +202,24 @@ validate-kind-ci: cli
@echo "=== Integration: kind gateway, ci mode ==="
./test/test-flow.sh kind --ci

## OCP validation with credentials (default mode). Requires: KUBECONFIG, all provider creds.
validate-ocp: cli dev-launcher dev-sandbox
@echo "=== Unit tests ==="
CGO_ENABLED=0 go test ./...
cd sandbox/launcher && go test ./...
@echo ""
@echo "=== Integration: OCP gateway, default mode ==="
SANDBOX_IMAGE=$(DEV_SANDBOX_IMAGE) LAUNCHER_IMAGE=$(DEV_LAUNCHER_IMAGE) ./test/test-flow.sh ocp --full

## OCP validation without credentials (ci mode). Requires: KUBECONFIG, launcher image pushed.
validate-ocp-ci: cli dev-launcher
@echo "=== Unit tests ==="
CGO_ENABLED=0 go test ./...
cd sandbox/launcher && go test ./...
@echo ""
@echo "=== Integration: OCP gateway, ci mode ==="
LAUNCHER_IMAGE=$(DEV_LAUNCHER_IMAGE) ./test/test-flow.sh ocp --ci --full

## ── Convenience targets ───────────────────────────────────────────────

## Clean built binaries
Expand Down
14 changes: 1 addition & 13 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func NewCreateCmd(harnessDir, cli string) *cobra.Command {
if sandboxName != "" {
cfg.Name = sandboxName
}
injectAtlassianEnv(cfg)

status.Section("Profile")
fmt.Printf(" Name: %s\n", cfg.Name)
Expand Down Expand Up @@ -203,19 +204,6 @@ func createDirect(harnessDir string, gw gateway.Gateway, profileName string, cfg
}
}

providersPath := filepath.Join(harnessDir, "providers.toml")
if allProviders, err := preflight.LoadProviders(providersPath); err == nil {
providerEnv := preflight.ProviderEnvVars(allProviders, cfg.Providers)
if cfg.Env == nil {
cfg.Env = make(map[string]string)
}
for k, v := range providerEnv {
if _, exists := cfg.Env[k]; !exists {
cfg.Env[k] = v
}
}
}

tmpParent, err := os.MkdirTemp("", "harness-")
if err != nil {
return fmt.Errorf("creating staging dir: %w", err)
Expand Down
45 changes: 0 additions & 45 deletions cmd/creds.go

This file was deleted.

77 changes: 0 additions & 77 deletions cmd/creds_test.go

This file was deleted.

3 changes: 2 additions & 1 deletion cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ func deployFromConfig(harnessDir string, gwCfg *gateway.GatewayConfig, gw gatewa
return fmt.Errorf("listing existing gateways: %w", err)
}
for _, g := range existing {
if routeHost != "" && strings.Contains(g.Endpoint, routeHost) {
// Remove stale registration for same name or same route host (idempotent re-deploy).
if g.Name == gatewayName || (routeHost != "" && strings.Contains(g.Endpoint, routeHost)) {
gw.GatewayRemove(g.Name)
}
}
Expand Down
47 changes: 24 additions & 23 deletions cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

"github.com/robbycochran/harness-openshell/internal/gateway"
"github.com/robbycochran/harness-openshell/internal/k8s"
"github.com/robbycochran/harness-openshell/internal/preflight"
"github.com/robbycochran/harness-openshell/internal/profile"
"github.com/robbycochran/harness-openshell/internal/status"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -113,11 +112,6 @@ func upRemote(harnessDir string, gwCfg *gateway.GatewayConfig, gw gateway.Gatewa
}
}

// 3. Ensure credentials
if err := ensureCreds(kc, namespace, false); err != nil {
return fmt.Errorf("credentials setup failed: %w", err)
}

// Parse profile
cfg, err := profile.Parse(harnessDir, profileName)
if err != nil {
Expand All @@ -126,6 +120,7 @@ func upRemote(harnessDir string, gwCfg *gateway.GatewayConfig, gw gateway.Gatewa
if sandboxName != "" {
cfg.Name = sandboxName
}
injectAtlassianEnv(cfg)

// Resolve sandbox image for remote deploys.
// SANDBOX_IMAGE env var overrides everything (dev/CI builds).
Expand Down Expand Up @@ -306,9 +301,13 @@ func upLocal(opts upLocalOpts) error {
if opts.sandboxName != "" {
cfg.Name = opts.sandboxName
}
injectAtlassianEnv(cfg)

// Resolve Dockerfile path relative to harnessDir
if cfg.From != "" && !filepath.IsAbs(cfg.From) {
// SANDBOX_IMAGE env var overrides the profile's image (dev/CI builds).
if envImage := os.Getenv("SANDBOX_IMAGE"); envImage != "" {
cfg.From = envImage
} else if cfg.From != "" && !filepath.IsAbs(cfg.From) {
// Resolve Dockerfile path relative to harnessDir
candidate := filepath.Join(opts.harnessDir, cfg.From)
if info, err := os.Stat(candidate); err == nil && info.IsDir() {
cfg.From = candidate
Expand All @@ -334,21 +333,7 @@ func upLocal(opts upLocalOpts) error {
status.Warn("no providers available — run: harness providers")
}

// 5. Inject non-secret provider env vars into sandbox env
providersPath := filepath.Join(opts.harnessDir, "providers.toml")
if allProviders, err := preflight.LoadProviders(providersPath); err == nil {
providerEnv := preflight.ProviderEnvVars(allProviders, cfg.Providers)
if cfg.Env == nil {
cfg.Env = make(map[string]string)
}
for k, v := range providerEnv {
if _, exists := cfg.Env[k]; !exists {
cfg.Env[k] = v
}
}
}

// 6. Stage files
// 5. Stage files
// The upload preserves the source dir name as a subdirectory at the destination.
// startup.sh expects files at /sandbox/.config/openshell/sandbox.env, so the
// staging dir must be named "openshell".
Expand Down Expand Up @@ -407,6 +392,22 @@ func upLocal(opts upLocalOpts) error {
return nil // unreachable but required by compiler
}

// injectAtlassianEnv adds JIRA_URL and JIRA_USERNAME from the local environment
// into cfg.Env so they land in sandbox.env. Interim until Phase 2 payload renderer
// ships; remove when harness create renders payload/env.sh instead.
func injectAtlassianEnv(cfg *profile.Config) {
if cfg.Env == nil {
cfg.Env = make(map[string]string)
}
for _, key := range []string{"JIRA_URL", "JIRA_USERNAME"} {
if _, exists := cfg.Env[key]; !exists {
if v := os.Getenv(key); v != "" {
cfg.Env[key] = v
}
}
}
}

func launcherEnv(gatewayEndpoint, sandboxImage string) []map[string]any {
env := []map[string]any{
{"name": "GATEWAY_ENDPOINT", "value": gatewayEndpoint},
Expand Down
1 change: 1 addition & 0 deletions cmd/up_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ func TestProfileHasCustomProviders(t *testing.T) {
}

func TestUpLocal_SandboxCreateOpts(t *testing.T) {
t.Setenv("SANDBOX_IMAGE", "") // prevent env leak from Makefile into profile.From check
dir := setupTestProfile(t)
gw := &mockGW{
providerList: []string{"github", "vertex-local"},
Expand Down
1 change: 0 additions & 1 deletion gateways/ocp/gateway.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ scc-privileged = ["openshell", "openshell-sandbox"]
scc-anyuid = ["openshell"]

[secrets]
names = ["openshell-atlassian"]
mtls = "openshell-client-tls"

[launcher]
Expand Down
32 changes: 3 additions & 29 deletions internal/preflight/preflight.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ type Provider struct {
}

type Input struct {
Key string `toml:"key"`
Kind string `toml:"kind"`
Secret bool `toml:"secret"`
Sandbox bool `toml:"sandbox"`
Key string `toml:"key"`
Kind string `toml:"kind"`
Secret bool `toml:"secret"`
}

type ProvidersFile struct {
Expand Down Expand Up @@ -85,31 +84,6 @@ func EnabledProviders(all []Provider, config *ConfigFile) []Provider {
return result
}

// ProviderEnvVars returns environment variables marked with sandbox = true
// for the named providers, resolved from the local environment. Only
// includes env vars that are set.
func ProviderEnvVars(providers []Provider, names []string) map[string]string {
wanted := make(map[string]bool, len(names))
for _, n := range names {
wanted[n] = true
}
env := make(map[string]string)
for _, p := range providers {
if !wanted[p.Name] {
continue
}
for _, inp := range p.Inputs {
if inp.Kind != "env" || !inp.Sandbox {
continue
}
if val := os.Getenv(inp.Key); val != "" {
env[inp.Key] = val
}
}
}
return env
}

func CheckInput(inp Input) (bool, string) {
switch inp.Kind {
case "env":
Expand Down
4 changes: 2 additions & 2 deletions providers.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ type = "openshell"
description = "Jira and Confluence (read-only, Basic auth resolved by proxy)"
inputs = [
{ key = "JIRA_API_TOKEN", kind = "env", secret = true },
{ key = "JIRA_URL", kind = "env", sandbox = true },
{ key = "JIRA_USERNAME", kind = "env", sandbox = true },
{ key = "JIRA_URL", kind = "env" },
{ key = "JIRA_USERNAME", kind = "env" },
{ key = "curl -sf ${JIRA_URL}/rest/api/2/serverInfo -o /dev/null", kind = "check" },
{ key = "curl -sf -u ${JIRA_USERNAME}:${JIRA_API_TOKEN} ${JIRA_URL}/rest/api/2/myself -o /dev/null", kind = "check" },
]
Expand Down
Loading
Loading