diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..c7ba5e7f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,9 @@ +# AGENTS + + +## Ecosystem (generated) + +This repository is a service in the OpenFrame ecosystem. +It publishes: github.com/flamingo-stack/openframe-cli. +Details: docs/reference/architecture/ecosystem.md (generated on every merge). + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 035639cd..d6484cfb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,81 +1,34 @@ # Contributing to OpenFrame CLI -Thank you for your interest in contributing to OpenFrame CLI! This document covers everything you need to know to submit high-quality contributions. +Thank you for contributing to OpenFrame CLI! This guide covers everything you need to know to submit high-quality contributions. ---- +## Before You Start -## πŸ“‹ Before You Start +- Join the [OpenMSP Slack community](https://www.openmsp.ai/) to discuss your ideas before starting large features. +- Read the [Architecture](./docs/development/architecture/README.md) documentation to understand the codebase. +- Set up your [development environment](./docs/development/setup/environment.md) and verify you can build and run [locally](./docs/development/setup/local-development.md). -- Join the [OpenMSP Slack community](https://www.openmsp.ai/) to discuss your ideas before starting large features -- Read the [Architecture Overview](./docs/development/architecture/README.md) to understand the codebase -- Set up your [development environment](./docs/development/setup/environment.md) and verify you can [build and run locally](./docs/development/setup/local-development.md) +## Code Style and Conventions -> **Note:** All contribution discussions happen in the [OpenMSP Slack](https://www.openmsp.ai/). There are no GitHub Issues or Discussions for this project β€” bring your questions, feature ideas, and bug reports to Slack. -> -> **Slack invite:** [https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) - ---- - -## πŸ› οΈ Development Setup - -### Prerequisites - -| Tool | Version | Purpose | -|---|---|---| -| **Go** | 1.21+ | Primary language runtime | -| **Git** | 2.30+ | Version control | -| **Docker** | 24.x+ | Container runtime (integration tests) | -| **k3d** | 5.x+ | Local Kubernetes clusters (integration tests) | -| **Helm** | 3.x+ | Kubernetes package manager (integration tests) | - -### Clone and Build - -```bash -# Clone the repository -git clone https://github.com/flamingo-stack/openframe-cli.git -cd openframe-cli - -# Download dependencies -go mod download - -# Build the binary -go build -o openframe . +### Go Style -# Verify -./openframe --version -``` +OpenFrame CLI follows standard Go conventions: -### Code Quality Tools +- `gofmt` / `goimports` formatting is required β€” no unformatted code will be merged. +- `go vet` must pass with no warnings. +- Follow [Effective Go](https://go.dev/doc/effective_go) and the [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments). ```bash -# Install goimports (formatting + import management) -go install golang.org/x/tools/cmd/goimports@latest - -# Install golangci-lint -curl -sSfL https://raw.githubusercontent.com/golangci-lint/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin - -# Format code +# Format and organize imports goimports -w . # Run vet go vet ./... -# Run linter +# Run linter (if golangci-lint is configured) golangci-lint run ``` ---- - -## πŸ“ Code Style and Conventions - -### Go Style - -OpenFrame CLI follows standard Go conventions: - -- **`gofmt` / `goimports`** formatting is required β€” no unformatted code will be merged -- **`go vet`** must pass with no warnings -- Follow [Effective Go](https://go.dev/doc/effective_go) and the [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) - ### Naming Conventions | Element | Convention | Example | @@ -89,7 +42,10 @@ OpenFrame CLI follows standard Go conventions: ### Error Handling -Wrap errors with context and use structured types from `shared/errors`: +- Wrap errors with context using `fmt.Errorf("context: %w", err)`. +- Use `shared/errors` types for structured errors (`CommandError`, `AlreadyHandledError`). +- Use `friendlyHint` patterns for user-facing errors. +- Never swallow errors silently β€” return them or log them. ```go // GOOD: Wrap with context @@ -112,11 +68,11 @@ func getMyCmd() *cobra.Command { cmd := &cobra.Command{ Use: "mycommand [name]", Short: "One-line description", - Long: `Multi-line detailed description. + Long: `Multi-line detailed description. The long description should explain what the command does, when to use it, and any important caveats.`, - Args: cobra.MaximumNArgs(1), + Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { // Validate input // Delegate to service layer @@ -124,56 +80,22 @@ when to use it, and any important caveats.`, return nil }, } + + // Add flags cmd.Flags().StringVar(&flagVar, "flag-name", "default", "Flag description") + return cmd } ``` ### Service Layer Conventions -- Services must accept interfaces (not concrete types) for all dependencies -- Always accept `context.Context` as the first argument for cancellable operations -- Return descriptive errors, not boolean success flags -- Use the `CommandExecutor` interface for all external binary invocations β€” **never** `os/exec` directly - ---- - -## πŸ” Security Guidelines - -### Secret Redaction - -Any credential read from environment variables, config files, or user prompts **must** be registered with the redact package before use: - -```go -import "github.com/flamingo-stack/openframe-cli/internal/shared/redact" - -redact.RegisterSecret(githubToken) -redact.RegisterSecret(registryPassword) -``` - -### Command Injection Prevention - -Always use argv arrays via `CommandExecutor`, never shell string concatenation: - -```go -// SAFE: argv array -result, err := exec.Execute(ctx, "k3d", "cluster", "list", "--output", "json") - -// NEVER: shell injection risk -// exec.Execute(ctx, "sh", "-c", "k3d cluster list --output " + userInput) -``` - -### Security Checklist for New Commands - -- [ ] User-supplied cluster names are validated via `ValidateClusterName` -- [ ] Any new credential/token is registered with `redact.RegisterSecret()` -- [ ] External commands use argv arrays, not shell strings -- [ ] New YAML/JSON input is validated via structured types before use -- [ ] Sensitive flags are not printed in error messages +- Services must accept interfaces (not concrete types) for all dependencies. +- Always accept `context.Context` as the first argument for cancellable operations. +- Return descriptive errors, not boolean success flags. +- Use the `CommandExecutor` interface for all external binary invocations β€” never `os/exec` directly. ---- - -## 🌿 Branch Naming +## Branch Naming | Type | Pattern | Example | |---|---|---| @@ -184,11 +106,12 @@ result, err := exec.Execute(ctx, "k3d", "cluster", "list", "--output", "json") | Test | `test/` | `test/add-bootstrap-integration` | | Chore | `chore/` | `chore/update-go-dependencies` | -**Rules:** Lowercase and hyphens only. Branch from `main` unless working on a specific release branch. - ---- +**Rules:** +- Use lowercase and hyphens only (no underscores, no uppercase). +- Keep descriptions short and meaningful. +- Branch from `main` unless working on a specific release branch. -## πŸ’¬ Commit Message Format +## Commit Message Format OpenFrame CLI uses [Conventional Commits](https://www.conventionalcommits.org/): @@ -213,11 +136,33 @@ OpenFrame CLI uses [Conventional Commits](https://www.conventionalcommits.org/): | `perf` | Performance improvements | | `ci` | CI/CD configuration changes | +> This repository squash-merges PRs, and the PR title is the analyzed commit subject for release versioning β€” a free-form title contributes nothing to any release. See [Releasing](./docs/development/releasing.md) for details. + +### Scopes (Optional but Recommended) + +| Scope | Area | +|---|---| +| `cluster` | Cluster commands and services | +| `app` | App commands and chart services | +| `bootstrap` | Bootstrap command and service | +| `prereq` | Prerequisites system | +| `update` | Self-update mechanism | +| `executor` | Command executor | +| `k8s` | Kubernetes client package | +| `argocd` | ArgoCD provider | +| `helm` | Helm provider | +| `ui` | Terminal UI and wizards | +| `errors` | Error handling | +| `redact` | Secret redaction | + ### Examples ```text feat(cluster): add --wait flag to cluster create command +Adds a --wait flag that blocks until all cluster nodes are Ready. +Useful for CI pipelines that need the cluster immediately after creation. + fix(argocd): handle stalled sync after ref change docs(contributing): add commit message guidelines @@ -227,61 +172,12 @@ test(bootstrap): add integration test for non-interactive mode chore: upgrade go-git to v5.12.0 ``` ---- - -## πŸ§ͺ Testing - -### Running Tests - -```bash -# Run all unit tests with race detector (recommended) -go test -race ./... - -# Run tests with coverage -go test -coverprofile=coverage.out ./... -go tool cover -html=coverage.out - -# Run integration tests (requires Docker, k3d, Helm, and 24GB+ RAM) -go test ./tests/integration/... -v -timeout 30m -``` - -### Writing Unit Tests - -Use the `MockCommandExecutor` for isolated unit tests β€” never invoke real subprocesses: - -```go -func TestCreateCluster(t *testing.T) { - testutil.InitializeTestMode() - - mock := testutil.NewTestMockExecutor() - mock.SetResponse("k3d cluster create", &executor.CommandResult{ - ExitCode: 0, - Stdout: `{"name": "test-cluster"}`, - }) - - svc := cluster.NewClusterService(mock) - err := svc.CreateCluster(context.Background(), "test-cluster") - assert.NoError(t, err) -} -``` - -### Coverage Targets - -| Package Type | Target | -|---|---| -| Core services (`internal/`) | β‰₯ 80% | -| Command layer (`cmd/`) | β‰₯ 70% | -| Provider implementations | β‰₯ 75% | -| Shared utilities | β‰₯ 85% | - ---- - -## πŸ“€ Pull Request Process +## Pull Request Process ### Before Opening a PR ```bash -# 1. Run all tests +# 1. Ensure all tests pass go test -race ./... # 2. Format code @@ -292,6 +188,9 @@ go vet ./... # 4. Build successfully go build -o openframe . + +# 5. Test your changes manually +./openframe --help ``` ### PR Description Template @@ -314,11 +213,11 @@ go build -o openframe . ## Checklist - [ ] Code follows the style guidelines - [ ] Self-review completed -- [ ] Tests pass (go test -race ./...) -- [ ] go vet ./... passes -- [ ] goimports formatting applied +- [ ] Tests pass (`go test -race ./...`) +- [ ] `go vet ./...` passes +- [ ] `goimports` formatting applied - [ ] No secrets or credentials in code -- [ ] Security guidelines followed +- [ ] Security guidelines followed (secrets registered with redact, no shell injection) ``` ### PR Size Guidelines @@ -329,35 +228,7 @@ go build -o openframe . | Medium | 100–500 lines | Include detailed description | | Large | 500+ lines | Split into smaller PRs if possible | ---- - -## βž• Adding a New Command - -Follow these steps when adding a new CLI command: - -1. **Create the command file** in `cmd//.go` -2. **Define a `getCmd()` function** returning `*cobra.Command` -3. **Register it** in the parent command group (e.g., `cmd/cluster/cluster.go`) -4. **Create a service** in `internal//` with injected dependencies -5. **Write unit tests** using `testutil.TestClusterCommand` -6. **Add integration tests** if the command interacts with external systems -7. **Verify `--help` output** is accurate and descriptive - ---- - -## βž• Adding a New Provider - -To add a new cluster provider (e.g., Kind): - -1. **Implement the `Provider` interface** in `internal/cluster/providers//manager.go` -2. **Add prerequisite definitions** in `internal/cluster/prerequisites/` -3. **Register the provider** in the cluster service provider resolution -4. **Add the cluster type** to `internal/cluster/models/cluster.go` -5. **Write unit and integration tests** - ---- - -## πŸ” Review Checklist +## Review Checklist When reviewing a PR, check: @@ -387,38 +258,58 @@ When reviewing a PR, check: - [ ] Complex logic has inline comments - [ ] `--help` text is accurate and helpful ---- +## Adding a New Command -## πŸ“¦ Release Signing +1. **Create the command file** in `cmd//.go`. +2. **Define a `getCmd()` function** returning `*cobra.Command`. +3. **Register it** in the parent command group (e.g., `cmd/cluster/cluster.go`). +4. **Create a service** in `internal//` with injected dependencies. +5. **Write unit tests** using `testutil.TestClusterCommand`. +6. **Add integration tests** if the command interacts with external systems. +7. **Verify `--help` output** is accurate and descriptive. -Release binaries are code-signed automatically during the release workflow: +## Adding a New Provider -| Platform | Mechanism | -|---|---| -| macOS | `codesign` (Developer ID Application, hardened runtime) + `notarytool` notarization | -| Windows | Authenticode via Azure Trusted Signing | -| Linux | Integrity via `checksums.txt` + cosign bundle | +To add a new cluster provider (e.g., Kind): + +1. **Implement the `Provider` interface** in `internal/cluster/providers//manager.go`. +2. **Add prerequisite definitions** in `internal/cluster/prerequisites/`. +3. **Register the provider** in the cluster service provider resolution. +4. **Add the cluster type** to `internal/cluster/models/cluster.go`. +5. **Write unit and integration tests**. + +## Security-Sensitive Changes + +If your change touches credentials, downloads, or self-update code: -All release binaries can be verified using cosign: +- New secrets/credentials must be registered with `redact.RegisterSecret()` before any logging path can reach them. +- New external tool installers must use the verified-download path, not a raw shell pipe. +- New shelled-out commands must go through `CommandExecutor` with argv slices, not interpolated shell strings. +- New user-supplied identifiers (names, refs, paths) must be validated before being passed to any external command. + +See the [Security](./docs/development/security/README.md) documentation for the full set of practices. + +## Testing + +Run all unit tests: ```bash -cosign verify-blob --bundle checksums.txt.bundle \ - --certificate-oidc-issuer https://token.actions.githubusercontent.com \ - --certificate-identity-regexp '^https://github.com/flamingo-stack/openframe-cli/\.github/workflows/release\.yml@.*$' \ - checksums.txt +go test ./... ``` ---- +Run integration tests (these build and execute the real binary, and may skip themselves if required tools like Docker/k3d aren't present): -## πŸ’¬ Community +```bash +go test ./tests/integration/... +``` + +See the [Testing](./docs/development/testing/README.md) documentation for the full testing strategy, including mocked unit tests and standardized per-command test coverage via `TestClusterCommand`. + +## Community + +All contribution discussions happen in the [OpenMSP Slack](https://www.openmsp.ai/). There are no GitHub Issues or Discussions for this project β€” bring your questions, feature ideas, and bug reports to Slack. -- **OpenMSP Slack:** [https://www.openmsp.ai/](https://www.openmsp.ai/) - **Slack invite:** [https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) - **OpenFrame platform repo:** [https://github.com/flamingo-stack/openframe-oss-tenant](https://github.com/flamingo-stack/openframe-oss-tenant) - **Releases:** [https://github.com/flamingo-stack/openframe-cli/releases](https://github.com/flamingo-stack/openframe-cli/releases) - ---- - -
- Built with πŸ’› by the Flamingo team -
+- **Pull Requests:** [https://github.com/flamingo-stack/openframe-cli/pulls](https://github.com/flamingo-stack/openframe-cli/pulls) diff --git a/README.md b/README.md index 39f1e242..fca890f2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@
- - - OpenFrame + + + OpenFrame
@@ -12,269 +12,168 @@ # OpenFrame CLI -OpenFrame CLI is a modern, interactive command-line tool for managing OpenFrame Kubernetes clusters and development workflows. It provides seamless cluster lifecycle management, chart installation with ArgoCD, and developer-friendly tools for service intercepts and scaffolding. +**OpenFrame CLI** (`openframe`) is a modern, interactive command-line tool for provisioning Kubernetes clusters β€” locally via [k3d](https://k3d.io) or in the cloud via GKE/EKS (using Terraform) β€” and deploying the [OpenFrame](https://openframe.ai) platform onto them using ArgoCD's app-of-apps pattern. -[![OpenFrame Preview Webinar](https://img.youtube.com/vi/bINdW0CQbvY/maxresdefault.jpg)](https://www.youtube.com/watch?v=bINdW0CQbvY) +It is the primary bootstrap and lifecycle-management tool for [OpenFrame](https://www.flamingo.run/openframe), the unified, AI-driven MSP platform built by [Flamingo](https://flamingo.run). OpenFrame CLI manages the full lifecycle of an OpenFrame deployment: checking prerequisites, provisioning a cluster, installing the platform, monitoring status, upgrading, and tearing down β€” with both fully interactive wizards and non-interactive flags for CI/automation. -## What is OpenFrame CLI? +> **Note:** OpenFrame CLI is one component of the broader OpenFrame ecosystem. The main platform code lives in a separate repository, [`flamingo-stack/openframe-oss-tenant`](https://github.com/flamingo-stack/openframe-oss-tenant), which this CLI deploys and manages. -OpenFrame CLI is part of the broader [OpenFrame](https://openframe.ai) ecosystem - an AI-powered MSP platform that replaces expensive proprietary software with open-source alternatives enhanced by intelligent automation. The CLI serves as the entry point for developers and operators to bootstrap, manage, and develop on OpenFrame environments. +## Features -## Key Features +- **One-command bootstrap** β€” `openframe bootstrap` creates a local k3d cluster and installs the entire OpenFrame platform (ArgoCD + app-of-apps) in a single step. +- **Multi-provider cluster support** β€” Provision clusters locally with k3d (Docker-based, Kubernetes-in-Docker) or in the cloud with GKE (Google) and EKS (AWS), all through Terraform under the hood. +- **Platform lifecycle management** β€” Install, upgrade, monitor status, and uninstall the OpenFrame platform via ArgoCD, without touching the underlying cluster. +- **Interactive and CI-friendly** β€” Every workflow supports an interactive wizard (prompts, spinners, cost estimates) as well as `--non-interactive`/`--skip-wizard` flags for automation pipelines. +- **Built-in prerequisites management** β€” Detects missing tools (Docker, k3d, Helm, Terraform, gcloud, AWS CLI) and can auto-install them on macOS/Linux. +- **Live status dashboard** β€” An interactive, k9s-style terminal UI (`openframe app status --interactive`) for inspecting ArgoCD application health and triggering syncs. +- **Secure by default** β€” All tool binaries are downloaded with pinned versions and SHA256 checksum verification (no `curl | bash`), and CLI self-updates are verified with Sigstore/cosign signatures. +- **Self-updating** β€” `openframe update` checks for, downloads, verifies, and applies new CLI releases, with rollback support. -### πŸš€ Complete Environment Bootstrapping -- **One-command setup**: Bootstrap entire OpenFrame environments with `openframe bootstrap` -- **OSS-tenant deployment**: Installs the public `openframe-oss-tenant` chart β€” no credentials or mode selection needed -- **Automated cluster creation**: Creates K3D clusters with all necessary components -- **ArgoCD integration**: Automatic chart installation and application management +## Hardware Requirements -### πŸ”§ Cluster Management -- **Lifecycle operations**: Create, delete, list, and monitor Kubernetes clusters -- **K3D integration**: Lightweight Kubernetes for development and testing -- **Status monitoring**: Real-time cluster health and resource monitoring -- **Easy teardown**: `cluster delete` removes a cluster and its resources; `cluster cleanup` reclaims disk by pruning unused node images - -### πŸ“¦ Chart & Application Management -- **Helm chart installation**: Streamlined chart deployment with dependency management -- **ArgoCD applications**: GitOps-based application lifecycle management -- **App-of-apps pattern**: Hierarchical application management for complex deployments -- **Synchronization monitoring**: Track deployment progress with detailed logging - -### πŸ›  Development Tools -- **Service intercepts**: Local development with Telepresence integration -- **Scaffolding**: Generate boilerplate code and configurations -- **Live debugging**: Debug services running in Kubernetes from your local environment -- **Hot reload**: Rapid development cycles with instant feedback - -## Architecture Overview +| Resource | Minimum | Recommended | +|---|---|---| +| RAM | 24 GB | 32 GB | +| CPU Cores | 6 | 12 | +| Disk Space | 50 GB | 100 GB | -```mermaid -graph TB - subgraph "CLI Commands" - Bootstrap[openframe bootstrap] - Cluster[openframe cluster] - App[openframe app] - Dev[openframe dev] - end - - subgraph "Core Services" - ClusterSvc[Cluster Management] - ChartSvc[Chart Installation] - DevSvc[Development Tools] - end - - subgraph "External Tools" - K3D[K3D Clusters] - Helm[Helm Charts] - ArgoCD[ArgoCD Apps] - Telepresence[Service Intercepts] - end - - subgraph "Target Environment" - K8s[Kubernetes] - Apps[Applications] - Services[Microservices] - end - - Bootstrap --> ClusterSvc - Bootstrap --> ChartSvc - Cluster --> ClusterSvc - App --> ChartSvc - Dev --> DevSvc - - ClusterSvc --> K3D - ChartSvc --> Helm - ChartSvc --> ArgoCD - DevSvc --> Telepresence - - K3D --> K8s - Helm --> Apps - ArgoCD --> Apps - Telepresence --> Services -``` +These figures reflect running a full local OpenFrame platform install (ArgoCD + app-of-apps) inside a k3d cluster on your machine. Cloud cluster deployments (EKS/GKE) shift most resource consumption to the cloud provider, but the CLI host still needs enough local resources to run Docker, Terraform, and Helm operations. ## Quick Start -Get OpenFrame CLI up and running in 5 minutes! - -### System Requirements +### Install -| Resource | Minimum | Recommended | -|----------|---------|-------------| -| **RAM** | 24GB | 32GB | -| **CPU Cores** | 6 cores | 12 cores | -| **Disk Space** | 50GB free | 100GB free | +**Windows** β€” download the AMD64 build directly: -### Prerequisites +```text +https://github.com/flamingo-stack/openframe-cli/releases/latest/download/openframe-cli_windows_amd64.zip +``` -Before installation, ensure you have: -- [Docker](https://docs.docker.com/get-docker/) 20.10+ -- [kubectl](https://kubernetes.io/docs/tasks/tools/) 1.25+ -- [Helm](https://helm.sh/docs/intro/install/) 3.10+ -- [K3D](https://k3d.io/v5.4.6/#installation) 5.0+ +Unzip the archive and run the `openframe` executable the same way you would run any other installer/binary on your system. -### Installation +**macOS / Linux** β€” download the platform-appropriate archive from the [Releases page](https://github.com/flamingo-stack/openframe-cli/releases/latest), unzip it, and place the `openframe` binary somewhere on your `$PATH` (e.g. `/usr/local/bin`). -Choose your platform and install OpenFrame CLI: +If you have a Go toolchain available, you can alternatively install directly from source: -#### Linux (AMD64) ```bash -curl -fsSL https://github.com/flamingo-stack/openframe-cli/releases/latest/download/openframe-cli_linux_amd64.tar.gz | tar -xz -sudo mv openframe /usr/local/bin/ -chmod +x /usr/local/bin/openframe +go install github.com/flamingo-stack/openframe-cli@latest ``` -#### macOS (Apple Silicon) +Verify the install: + ```bash -curl -fsSL https://github.com/flamingo-stack/openframe-cli/releases/latest/download/openframe-cli_darwin_arm64.tar.gz | tar -xz -sudo mv openframe /usr/local/bin/ -chmod +x /usr/local/bin/openframe +openframe --version ``` -#### Windows (WSL2) -1. Download: https://github.com/flamingo-stack/openframe-cli/releases/latest/download/openframe-cli_windows_amd64.zip -2. Extract and move `openframe.exe` to a directory in your `PATH` -3. Open WSL2 terminal and verify access - -### Bootstrap Your Environment - -Create a complete OpenFrame environment with a single command: +### Bootstrap your first environment ```bash -# Verify installation -openframe --version +# 1. Check that Docker/k3d/helm are ready (auto-installs on macOS/Linux where possible) +openframe prerequisites check -# Bootstrap complete environment +# 2. Bootstrap: creates a local k3d cluster AND installs the OpenFrame platform openframe bootstrap - -# Check cluster status -openframe cluster status ``` -The bootstrap process creates: -- K3D Kubernetes cluster -- ArgoCD for GitOps deployment -- Traefik ingress controller -- Core monitoring and logging components - -## Core Commands - -| Command | Description | Example | -|---------|-------------|---------| -| `openframe bootstrap` | Create a cluster and deploy OpenFrame | `openframe bootstrap my-cluster` | -| `openframe cluster create` | Create a k3d or cloud (EKS/GKE) cluster | `openframe cluster create dev --nodes 1` | -| `openframe cluster list` | List clusters | `openframe cluster list -o json` | -| `openframe cluster status` | Show cluster status | `openframe cluster status dev` | -| `openframe cluster delete` | Delete a cluster | `openframe cluster delete dev --force` | -| `openframe app install` | Install ArgoCD + app-of-apps | `openframe app install -c k3d-dev` | -| `openframe app upgrade` | Re-sync or move to a new ref | `openframe app upgrade -c k3d-dev --sync` | -| `openframe app status` | Report platform readiness | `openframe app status -c k3d-dev` | -| `openframe app access` | Show ArgoCD sign-in details | `openframe app access -c k3d-dev` | -| `openframe app uninstall` | Remove the app (keep the cluster) | `openframe app uninstall -c k3d-dev --yes` | -| `openframe prerequisites` | Check/install required tools | `openframe prerequisites install` | -| `openframe update` | Self-update the CLI | `openframe update check` | - -### Usage Examples - -Cluster lifecycle: - -```bash -openframe cluster create dev --type k3d --nodes 1 --skip-wizard -openframe cluster create my-gke --type gke --project my-project --region us-central1 --skip-wizard # cloud (billed!) -openframe cluster create my-eks --type eks --region us-east-1 --skip-wizard # cloud (billed!) -openframe cluster list # add -o json|yaml for scripts -openframe cluster status dev -openframe cluster delete dev --force -``` +`openframe bootstrap` runs interactively by default β€” it validates (or prompts for) a cluster name, creates a local k3d cluster, installs ArgoCD via Helm, installs the app-of-apps chart, waits for all ArgoCD applications to become synced/healthy, and prints a summary card with access instructions. -Deploy and manage the platform (OSS tenant deployment): +For CI/automation, run it non-interactively: ```bash -openframe app install # interactive: pick context -openframe app install dev --non-interactive # reuse existing openframe-helm-values.yaml -openframe app install -c k3d-dev --ref v1.3.0 # deploy a specific release tag -openframe app status -c k3d-dev # -o json|yaml supported -openframe app status -c k3d-dev --watch # live view, refreshes in place (Ctrl+C to exit) -openframe app status -c k3d-dev --interactive # k9s-style TUI: navigate apps, inspect, trigger syncs -openframe app access -c k3d-dev # ArgoCD URL + admin credentials -openframe app upgrade -c k3d-dev --sync # force ArgoCD to re-sync current ref -openframe app upgrade -c k3d-dev --ref v1.4.0 # move to a new release tag -openframe app uninstall -c k3d-dev --yes +openframe bootstrap --non-interactive ``` -Keep the CLI up to date (each release is checksum- and cosign-verified before it -replaces the running binary; the previous version is kept for rollback): +Confirm everything is healthy and view ArgoCD access credentials: ```bash -openframe update # update to the latest release -openframe update check # report availability only (-o json|yaml) -openframe update v1.4.0 # switch to a specific release (up or down) -openframe update rollback # revert to the previous version, offline +openframe app status +openframe app access ``` -## Terminal Output +## Technology Stack -On an interactive terminal the CLI renders live surfaces: a stage checklist -with per-stage timings during `bootstrap`, an in-place dashboard during the -application wait (progress bar, per-app health, slowest-apps timing), download -progress bars, and a desktop notification when a long install finishes. Long -operations under `--verbose`, `--plain`, or redirected output switch to -timestamped sequential log lines that carry the same information (ready -deltas, pending apps with their health). +OpenFrame CLI is a Go service (module `github.com/flamingo-stack/openframe-cli`) built with: -Output controls: +- **[Cobra](https://github.com/spf13/cobra)** β€” command routing and flag parsing for the entire `cmd/` tree. +- **[client-go](https://github.com/kubernetes/client-go)** β€” native Kubernetes API access, replacing shelled-out `kubectl` calls. +- **[pterm](https://github.com/pterm/pterm)** β€” terminal rendering: tables, spinners, boxes, colored status printers. +- **[huh](https://github.com/charmbracelet/huh)** and **[bubbletea](https://github.com/charmbracelet/bubbletea)** β€” interactive prompts/wizards and the `app status --interactive` TUI. +- **[sigstore-go](https://github.com/sigstore/sigstore-go)** β€” cosign keyless signature verification for self-update integrity. +- **External CLI tools** invoked via a testable `CommandExecutor` abstraction: Docker, k3d, Helm, Terraform, gcloud, aws β€” binaries verified and pinned via a checksum-verified download layer (k3d, Helm, mkcert, Terraform, infracost). -- `--plain` β€” sequential output with colors but no spinners or in-place - redraws (for `watch`, `script`, tmux logging) -- `--silent` β€” suppress everything except errors -- `--verbose` β€” timestamped debug lines (helm/k3d command lines, wait internals) -- `NO_COLOR` / `CLICOLOR_FORCE=1` β€” strip / force ANSI styling -- `OPENFRAME_ASCII=1` β€” plain ASCII glyphs (also automatic under `TERM=dumb` - or a non-UTF-8 locale) +## Architecture -In GitHub Actions, bootstrap stages fold into log groups, failures surface as -job annotations, and the closing summary lands in the job's Step Summary. +OpenFrame CLI is organized around three core abstractions β€” **cluster** (provisioning), **app** (platform deployment via ArgoCD), and **prerequisites** (tool verification/installation) β€” plus supporting shared infrastructure for UI, execution, and self-update. -Non-interactive flags (`--non-interactive`, `--yes`, `--force`, `--skip-wizard`) -make every command scriptable; prompts are also skipped automatically in CI or -when stdin is not a terminal. See [Terminal output reference](./docs/reference/terminal-output.md) -for the full behavior matrix. +```mermaid +graph TB + subgraph "CLI Layer" + Bootstrap[bootstrap] + Cluster[cluster] + App[app] + Prereq[prerequisites] + Update[update] + end -## Technology Stack + subgraph "Domain Services" + ClusterSvc["ClusterService"] + ChartSvc["ChartService"] + AppStatus["app.status.Service"] + PrereqFw["prerequisites.Runner"] + SelfUpdate["selfupdate.Updater"] + end -OpenFrame CLI integrates with industry-standard tools: + subgraph "Providers" + K3d["k3d provider"] + EKS["EKS provider (terraform)"] + GKE["GKE provider (terraform)"] + ArgoCD["ArgoCD provider"] + Helm["Helm provider"] + end -- **Kubernetes**: Container orchestration with K3D for development -- **ArgoCD**: GitOps continuous deployment -- **Helm**: Package management for Kubernetes -- **Telepresence**: Local development with remote services -- **Docker**: Container runtime and image management -- **Cobra**: Modern CLI framework with rich help and completion + subgraph "External Systems" + Docker[(Docker)] + K8sAPI[(Kubernetes API)] + CloudAPI[(GCP / AWS APIs)] + GitHub[(GitHub Releases)] + end -## Documentation + Bootstrap --> ClusterSvc + Bootstrap --> ChartSvc + Cluster --> ClusterSvc + App --> ChartSvc + App --> AppStatus + Prereq --> PrereqFw + Update --> SelfUpdate -πŸ“š See the [Documentation](./docs/README.md) for comprehensive guides including: + ClusterSvc --> K3d + ClusterSvc --> EKS + ClusterSvc --> GKE + ChartSvc --> ArgoCD + ChartSvc --> Helm -- **Getting Started**: Prerequisites, installation, and first steps -- **Development**: Local setup, architecture, and contribution guidelines -- **Reference**: Technical documentation, API specs, and configuration -- **CLI Tools**: Links to external repositories and tools + K3d --> Docker + EKS --> CloudAPI + GKE --> CloudAPI + ArgoCD --> K8sAPI + Helm --> K8sAPI + SelfUpdate --> GitHub +``` -## Community and Support +The CLI deploys the [OpenFrame](https://openframe.ai) platform, whose main code lives in the separate [`flamingo-stack/openframe-oss-tenant`](https://github.com/flamingo-stack/openframe-oss-tenant) repository. -OpenFrame is built by the community for the community: +## Documentation -- **OpenMSP Slack**: [Join the community](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) - Primary support channel -- **Website**: [https://flamingo.run](https://flamingo.run) -- **OpenFrame Platform**: [https://openframe.ai](https://openframe.ai) +πŸ“š See the [Documentation](./docs/README.md) for comprehensive guides, including getting-started tutorials, development workflows, and architecture reference. -> **Note**: We don't use GitHub Issues or Discussions. All support and community interaction happens in the OpenMSP Slack community. +## Community -## License +There are no GitHub Issues or Discussions for this project β€” all discussions happen in the **OpenMSP Slack community**: -This project is licensed under the Flamingo AI Unified License v1.0 - see the [LICENSE.md](LICENSE.md) file for details. +- Join: [https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) +- Visit: [https://www.openmsp.ai/](https://www.openmsp.ai/) ---
Built with πŸ’› by the Flamingo team -
\ No newline at end of file + diff --git a/cmd/.root.md b/cmd/.root.md index 20beca40..c2a3e525 100644 --- a/cmd/.root.md +++ b/cmd/.root.md @@ -1,45 +1,45 @@ - -Defines the root Cobra command for the OpenFrame CLI, wiring together all subcommands, global flags, version metadata, signal handling, and self-update logic into a single executable entry point. + +## root.go -## Key Components - -| Symbol | Type | Description | -|--------|------|-------------| -| `VersionInfo` | struct | Holds `Version`, `Commit`, and `Date` build metadata | -| `DefaultVersionInfo` | var | Package-level version vars populated at release via `-ldflags -X` | -| `GetRootCmd` | func | Constructs and returns the root `*cobra.Command` | -| `Execute` | func | Entry point using `DefaultVersionInfo` | -| `ExecuteWithVersion` | func | Full execution pipeline: WSL forwarding, config init, signal context, self-update | -| `buildRootCommand` | func | Assembles root command with subcommands, persistent flags, and usage templates | +Defines and wires up the root Cobra command for the OpenFrame CLI, including version metadata resolution, global flags, subcommand registration, and the top-level `Execute` entry points invoked by `main`. -**Subcommands registered:** `cluster`, `app`, `bootstrap`, `prerequisites`, `update` +## Key Components -**Global flags:** `--verbose` / `-v` (enables pterm debug output), `--silent` (suppresses all non-error output) +- **`VersionInfo`** β€” struct holding `Version`, `Commit`, and `Date`, populated at build time via `-ldflags -X` on the package-level `version`, `commit`, `date` vars. +- **`DefaultVersionInfo`** β€” resolved once at package init via `resolveVersionInfo`. +- **`resolveVersionInfo` / `backfillFromVCS`** β€” fall back to Go's embedded VCS build info (`vcs.revision`, `vcs.time`, `vcs.modified`) when ldflags weren't applied (dev builds), so `dev` builds still report a real commit/date instead of `none`/`unknown`. +- **`GetRootCmd(versionInfo)`** β€” returns the fully constructed root `*cobra.Command`. +- **`buildRootCommand(versionInfo)`** β€” builds the `openframe` root command: help text, `--version` output (including `pinnedDependencies()`), persistent flags (`--verbose`, `--silent`, `--plain`), custom usage/version templates, and registers subcommands (`cluster`, `app`, `bootstrap`, `prerequisites`, `update`). +- **`pinnedDependencies()`** β€” renders the exact pinned versions of `terraform`, `helm`, `k3d`, `mkcert`, `infracost`, and the ArgoCD chart that this build installs/deploys. +- **`Execute()`** β€” runs the root command using `DefaultVersionInfo`. +- **`ExecuteWithVersion(versionInfo)`** β€” main execution path: handles Windowsβ†’WSL forwarding, initializes config, ensures the CLI-managed bin dir is on `$PATH`, runs the command under a signal-cancelable context (Ctrl-C/SIGTERM), and performs best-effort post-command self-update checks/notifications. ## Usage Example ```go -// Standard CLI entry point (main.go) package main import ( - "fmt" - "os" + "fmt" + "os" - "github.com/flamingo-stack/openframe-cli/cmd" + "github.com/flamingo-stack/openframe-cli/cmd" +) + +// Populated via -ldflags -X at release build time. +var ( + version = "dev" + commit = "none" + date = "unknown" ) func main() { - if err := cmd.Execute(); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } + versionInfo := cmd.VersionInfo{Version: version, Commit: commit, Date: date} + if err := cmd.ExecuteWithVersion(versionInfo); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } } - -// Inject build-time version at release via goreleaser: -// -ldflags "-X github.com/flamingo-stack/openframe-cli/cmd.version={{.Version}} -// -X github.com/flamingo-stack/openframe-cli/cmd.commit={{.Commit}} -// -X github.com/flamingo-stack/openframe-cli/cmd.date={{.Date}}" ``` -> **Note:** On Windows, `ExecuteWithVersion` automatically re-executes the command inside WSL before any subcommand runs. Ctrl-C / `SIGTERM` is handled via a shared `context.Context` propagated to all subcommands via `cmd.Context()`. \ No newline at end of file +Note: `getClusterCmd`, `getAppCmd`, `getBootstrapCmd`, `getPrerequisitesCmd`, and `getUpdateCmd` are thin internal wrappers around the respective `cmd/*` package `Get*Cmd` constructors, used only to assemble subcommands in `buildRootCommand`. \ No newline at end of file diff --git a/cmd/app/.access.md b/cmd/app/.access.md index b2227467..5972ae85 100644 --- a/cmd/app/.access.md +++ b/cmd/app/.access.md @@ -1,45 +1,41 @@ - -Retrieves and displays ArgoCD admin credentials and UI access instructions for the OpenFrame control plane. + +This file implements the `openframe app access` CLI subcommand, which retrieves and displays ArgoCD admin credentials for signing in to the OpenFrame control plane UI. ## Key Components -| Symbol | Type | Description | -|---|---|---| -| `getAccessCmd` | `func` | Builds the `openframe app access` Cobra subcommand | -| `runAccessCommand` | `func` | Command handler β€” resolves credentials and renders output | -| `resolveRestConfig` | `func` | Builds a `rest.Config` for a given kube-context (shared with status command) | -| `newArgoCDManager` | `func` | Constructs an `argocd.Manager` bound to a specific kube-context | -| `printAccess` | `func` | Renders human-readable ArgoCD credentials and port-forward instructions | +- **`getAccessCmd()`** β€” Constructs the Cobra `access` subcommand, including its help text, `--context` flag (kube-context selection) and shared output-format flag. Marked read-only via annotation. +- **`runAccessCommand(cmd, args)`** β€” The command's `RunE` handler. Resolves the kube-context, connects to the cluster via an ArgoCD manager, fetches the admin password, and prints it either as structured output (JSON/YAML via `renderMachine`) or human-readable text. +- **`resolveRestConfig(contextName)`** β€” Builds a `*rest.Config` for the given kube-context (or the current context if empty). Shared with the `status` command. +- **`newArgoCDManager(contextName, verbose)`** β€” Creates an `argocd.Manager` bound to the resolved cluster config and a real command executor. +- **`printAccess(password)`** β€” Renders the username, password, and port-forward/UI-access instructions using `pterm` printers (ensuring output respects silent mode). ## Usage Example ```go -// Register the access subcommand under `app` +// Wiring the access command into the parent "app" command appCmd.AddCommand(getAccessCmd()) ``` +CLI usage: + ```bash -# Print credentials using the current kube-context +# Show ArgoCD credentials for the current kube-context openframe app access -# Target a specific context, output as JSON -openframe app access --context k3d-openframe-dev --output json +# Target a specific kube-context +openframe app access --context k3d-openframe-dev + +# Get machine-readable output +openframe app access --output json ``` -**Example text output:** +Example text output: ```text ArgoCD access Username: admin - Password: - -β„Ή Open the ArgoCD UI: + Password: +Open the ArgoCD UI: 1. kubectl port-forward -n argocd svc/argocd-server 8080:443 2. open https://localhost:8080 -``` - -## Notes - -- Marked `readonly: true` β€” performs no mutations on the cluster. -- Supports `--output json` / `--output yaml` for machine-readable output containing `username` and `password` fields. -- Password is read from the ArgoCD initial admin secret in the cluster; returns a descriptive error if OpenFrame is not installed. \ No newline at end of file +``` \ No newline at end of file diff --git a/cmd/app/.app.md b/cmd/app/.app.md index 0eeb0fb1..19e6e9f6 100644 --- a/cmd/app/.app.md +++ b/cmd/app/.app.md @@ -1,42 +1,46 @@ - -Registers the `app` command group for the OpenFrame CLI, wiring together all application lifecycle subcommands under a single `cobra.Command`. + +## app.go + +Defines the `app` command group for the OpenFrame CLI, responsible for deploying and managing the OpenFrame application (ArgoCD + app-of-apps) on an existing Kubernetes cluster. It wires together install, upgrade, status, access, and uninstall subcommands and enforces consistent output/UX behavior across them. ## Key Components -| Export | Type | Description | -|--------|------|-------------| -| `GetAppCmd()` | `func` | Constructs and returns the `app` parent command with all subcommands attached | +- **`noPositionalArgs(cmd *cobra.Command, args []string) error`** β€” Validation function that rejects any positional arguments, returning a clear error instructing users to use `--context` instead. Prevents accidental targeting of the wrong cluster (especially dangerous for uninstall). +- **`GetAppCmd() *cobra.Command`** β€” Constructs and returns the root `app` cobra command, including: + - A `PersistentPreRunE` hook that applies global `--silent`/`--verbose` flag behavior (since defining a local hook shadows the root command's), skips logo output for machine-readable formats (JSON/YAML), and shows the logo plus current kube-context for subcommands. + - A `RunE` that shows the logo and prints help when `app` is invoked without a subcommand. + - Registration of subcommands: `install`, `upgrade`, `status`, `access`, `uninstall`. -### Subcommands Registered +## Usage Example -| Subcommand | Source | -|------------|--------| -| `install` | `getInstallCmd()` | -| `upgrade` | `getUpgradeCmd()` | -| `status` | `getStatusCmd()` | -| `access` | `getAccessCmd()` | -| `uninstall` | `getUninstallCmd()` | +```go +package main -### Behavior Notes +import ( + "github.com/flamingo-stack/openframe-cli/internal/app" + "github.com/spf13/cobra" +) -- **`PersistentPreRunE`**: Runs before any subcommand; honors `--silent` flag via `ui.SetSilent()` and suppresses the logo for machine-readable output (`--output json/yaml`). The logo is shown for subcommands but not for the bare `app` command itself. -- **Prerequisites**: Intentionally not checked here β€” delegated to `InstallChartsWithConfigContext` inside the install/upgrade flow to avoid redundant checks. -- **`RunE`**: Falls back to displaying help when `app` is invoked without a subcommand. +func main() { + rootCmd := &cobra.Command{Use: "openframe"} + rootCmd.AddCommand(app.GetAppCmd()) -## Usage Example - -```go -// Registering the app command group on the root command -rootCmd.AddCommand(app.GetAppCmd()) + if err := rootCmd.Execute(); err != nil { + panic(err) + } +} ``` +Example CLI usage once wired into the root command: + ```bash -# Deploy OpenFrame onto an existing cluster +# Install the OpenFrame app onto the current cluster context openframe app install -# Target a named cluster -openframe app install my-cluster +# Install onto a specific cluster +openframe app install --context my-cluster -# List available subcommands -openframe app --help +# Passing a positional argument instead of --context returns an error +openframe app uninstall my-cluster +# Error: "openframe app uninstall" takes no positional arguments (got "my-cluster") β€” use --context to target a cluster ``` \ No newline at end of file diff --git a/cmd/app/.install.md b/cmd/app/.install.md index 7ea36de6..e420de5c 100644 --- a/cmd/app/.install.md +++ b/cmd/app/.install.md @@ -1,46 +1,33 @@ - -Implements the `openframe app install` Cobra subcommand, orchestrating ArgoCD and app-of-apps installation onto a Kubernetes cluster with support for interactive context selection, dry-run, and CI/CD non-interactive modes. + +## install.go -## Key Components - -### Functions - -| Function | Description | -|---|---| -| `getInstallCmd()` | Builds and returns the `install` Cobra command with all flags registered | -| `runInstallCommand()` | Entry point for command execution; extracts flags, builds the request, and delegates to the chart service | -| `buildInstallRequest()` | Assembles `types.InstallationRequest`; resolves kubeconfig from `--context` or interactive prompt; shared with the upgrade command | -| `recommendedRequirements()` | Returns advisory minimum cluster resources (6 CPU cores, 24 GB RAM) used to warn but never block | -| `extractInstallFlags()` | Parses all Cobra flags into an `InstallFlags` struct | -| `addInstallFlags()` | Registers all CLI flags (`--force`, `--dry-run`, `--ref`, `--context`, etc.) on the command | -| `getVerboseFlag()` | Resolves the `--verbose` flag from root or current command with a `false` fallback | +This file implements the `openframe app install` CLI subcommand, which installs ArgoCD and the app-of-apps configuration onto an existing Kubernetes cluster. It handles flag parsing, interactive cluster/context selection, and delegates the actual installation work to the chart services layer. -### Types +## Key Components -| Type | Description | -|---|---| -| `InstallFlags` | Holds parsed flag values: `Force`, `DryRun`, `GitHubRepo`, `Ref`, `CertDir`, `NonInteractive` | -| `InstallFlags.resolvedRef()` | Returns `--ref` value when set, otherwise falls back to `chartmodels.DefaultGitBranch` | +- **`getInstallCmd()`** β€” Constructs the `cobra.Command` for `install`, wiring up help text, flags, and the `RunE` handler. +- **`runInstallCommand(cmd, args)`** β€” Entry point executed on `install`; extracts flags, builds the installation request, and calls `services.InstallChartsWithConfigContext`. +- **`buildInstallRequest(cmd, args, flags, verbose, action)`** β€” Assembles a `types.InstallationRequest`, resolving the target cluster's `rest.Config` either from an explicit `--context` flag or via interactive context selection when no cluster name/flags are given. Shared with the upgrade command. +- **`InstallFlags`** β€” Struct holding parsed flag values (`Force`, `DryRun`, `GitHubRepo`, `Ref`, `CertDir`, `NonInteractive`), with `resolvedRef()` returning the effective git ref to deploy. +- **`extractInstallFlags(cmd)`** β€” Parses all install-related flags from the cobra command into an `InstallFlags` struct. +- **`getVerboseFlag(cmd)`** β€” Resolves the `--verbose` flag from either the root or current command. +- **`addInstallFlags(cmd)`** β€” Registers all install flags (`--force`, `--dry-run`, `--github-repo`, `--ref`, `--cert-dir`, `--non-interactive`, `--context`) on the command. +- **`recommendedRequirements()`** β€” Returns advisory minimum cluster capacity (6 CPU cores / 24GB RAM) used to warn (not block) undersized clusters during interactive selection. ## Usage Example -```bash -# Interactive mode β€” prompts for kube-context -openframe app install +```go +// Registering the install command under the app parent command +appCmd := &cobra.Command{Use: "app"} +appCmd.AddCommand(getInstallCmd()) +``` -# Target a specific cluster by name -openframe app install my-cluster +Typical CLI usage: -# CI/CD: skip prompts, use existing openframe-helm-values.yaml -openframe app install --non-interactive - -# Deploy a specific branch or release tag -openframe app install --ref develop -openframe app install --ref 1.0.48 - -# Target an explicit kube-context (scriptable) -openframe app install --context my-context - -# Preview without executing -openframe app install --dry-run +```bash +openframe app install # Interactive mode +openframe app install my-cluster # Install on a named cluster +openframe app install --non-interactive # CI/CD mode, uses existing values file +openframe app install --ref 1.0.48 # Deploy a specific release tag +openframe app install --context my-ctx # Skip interactive selection ``` \ No newline at end of file diff --git a/cmd/app/.status.md b/cmd/app/.status.md index 678642cc..e5906738 100644 --- a/cmd/app/.status.md +++ b/cmd/app/.status.md @@ -1,34 +1,42 @@ - -Defines the `status` subcommand for the `openframe app` CLI group, reporting the current health and readiness of the OpenFrame platform on a Kubernetes cluster. + +## status.go + +Implements the `openframe app status` subcommand, which reports whether the OpenFrame platform is up and running on a Kubernetes cluster. It checks cluster reachability, lists ArgoCD application sync/health state, summarizes overall readiness, and prints sign-in information. Supports one-shot, watch (auto-refreshing), interactive TUI, and machine-readable output modes. ## Key Components -| Symbol | Type | Description | -|---|---|---| -| `getStatusCmd` | `func` | Builds and returns the `cobra.Command` for `openframe app status`, registering `--context` and `--output` flags | -| `runStatusCommand` | `func` | Entry point for the command; resolves the kube config, creates an ArgoCD manager and K8s accessor, delegates to `appstatus.NewService`, then routes output to the text or machine-readable renderer | -| `statusJSON` | `struct` | Machine-readable top-level payload (JSON/YAML output) containing cluster reachability, node counts, overall readiness, and aggregated app stats | -| `statusAppJSON` | `struct` | Per-application entry within `statusJSON` holding `name`, `sync`, and `health` strings | -| `statusToJSON` | `func` | Maps an `appstatus.Report` to the serialisable `statusJSON` struct | -| `renderStatus` | `func` | Renders a human-readable pterm table of applications plus cluster and readiness summaries to stdout | +- **`getStatusCmd()`** β€” Builds the `status` Cobra subcommand with `--context`, `--watch`, `-i/--interactive`, and output-format flags. +- **`runStatusCommand(cmd, args)`** β€” Main entry point; validates flag combinations, resolves cluster config, builds the status service, and dispatches to interactive, watch, or single-report rendering. +- **`watchStatus(ctx, svc, verbose)`** β€” Polls `svc.Report` every 3 seconds and re-renders the view in-place using `pterm.DefaultArea`, until the context is cancelled (e.g. Ctrl+C). +- **`renderWatchFrame(rep, rerr)`** β€” Builds the text content for a single watch-mode frame (header, reachability, app table, readiness summary). +- **`renderStatus(rep)`** β€” Renders a single, non-watching status report to the terminal, including reachability, app table, readiness summary, and admin access info. +- **`statusJSON` / `statusAppJSON`** β€” Machine-readable JSON shapes for `--output json`/other formats. +- **`statusToJSON(rep)`** β€” Converts an `appstatus.Report` into its JSON representation. ## Usage Example +```go +// Registering the status command with a parent Cobra command +rootCmd.AddCommand(getStatusCmd()) +``` + +Typical CLI usage: + ```bash -# Default text output using the current kube-context +# One-shot status check openframe app status -# Target a specific context +# Use a specific kube-context openframe app status --context k3d-openframe-dev -# Machine-readable JSON output -openframe app status --output json -``` +# Live-refreshing view +openframe app status --watch -```go -// Internal wiring (simplified) -cmd := getStatusCmd() // returns *cobra.Command -_ = cmd.Execute() +# Interactive TUI (navigate apps, inspect details, trigger syncs) +openframe app status --interactive + +# Machine-readable output +openframe app status --output json ``` -The command is annotated with `"readonly": "true"`, indicating it performs no mutations and is safe to run at any time. \ No newline at end of file +Note: `--watch` and `--interactive` require an interactive terminal and cannot be combined with `--output` (non-text) or `--plain` mode. \ No newline at end of file diff --git a/cmd/app/.uninstall.md b/cmd/app/.uninstall.md index 7f7c0c56..1c3cb4a7 100644 --- a/cmd/app/.uninstall.md +++ b/cmd/app/.uninstall.md @@ -1,40 +1,43 @@ - -Defines the `uninstall` subcommand (`openframe app uninstall`) which removes ArgoCD and all OpenFrame applications from a Kubernetes cluster while leaving the cluster itself intact. + +## Overview + +Implements the `openframe app uninstall` CLI subcommand, which removes the OpenFrame application (ArgoCD applications and associated Helm releases) from a Kubernetes cluster while leaving the cluster itself intact. ## Key Components -| Symbol | Description | -|---|---| -| `getUninstallCmd()` | Builds and returns the `*cobra.Command` for `openframe app uninstall`, registering `--context`, `--yes`, and `--delete-namespace` flags | -| `runUninstallCommand()` | Executes the uninstall flow: confirmation prompt, REST config resolution, ArgoCD/Helm manager construction, and delegation to `appuninstall.Service` | +- **`getUninstallCmd() *cobra.Command`** β€” Constructs and returns the `uninstall` Cobra subcommand, registering its flags: + - `--context` / `-c`: target kube-context (defaults to current context) + - `--yes` / `-y`: skip the confirmation prompt (for automation/CI) + - `--delete-namespace`: also delete the `argocd` namespace +- **`runUninstallCommand(cmd *cobra.Command, _ []string) error`** β€” Command handler that: + 1. Resolves flags and target context. + 2. Prompts for destructive-action confirmation (unless `--yes` is set), failing fast in non-interactive sessions. + 3. Resolves the cluster's REST config and builds an ArgoCD manager and Helm manager. + 4. Delegates the actual uninstall work to `appuninstall.NewService(...).Uninstall(...)`. + 5. Reports results (apps deleted, Helm releases removed, namespace deletion status) via `pterm`. ## Usage Example +```go +// Wiring the uninstall command into the parent "app" command +func NewAppCommand() *cobra.Command { + appCmd := &cobra.Command{Use: "app"} + appCmd.AddCommand(getUninstallCmd()) + return appCmd +} +``` + +CLI usage: + ```bash -# Interactive uninstall (prompts for confirmation) +# Interactive uninstall on the current kube-context openframe app uninstall -# Target a specific kube-context -openframe app uninstall --context k3d-openframe-dev - -# Skip confirmation for CI/automation -openframe app uninstall --yes +# Uninstall on a specific context, skipping confirmation +openframe app uninstall --context k3d-openframe-dev --yes -# Also remove the argocd namespace after uninstall +# Also remove the argocd namespace openframe app uninstall --yes --delete-namespace ``` -```go -// Registered on the parent "app" command -appCmd.AddCommand(getUninstallCmd()) -``` - -**Flags** - -| Flag | Type | Default | Description | -|---|---|---|---| -| `--context`, `-c` | `string` | `""` | Kube-context to target; defaults to current context | -| `--yes`, `-y` | `bool` | `false` | Skip the interactive confirmation prompt | -| `--delete-namespace` | `bool` | `false` | Delete the `argocd` namespace after uninstall | - -On success the command prints the number of ArgoCD applications and Helm releases removed, and reminds the user the cluster is still running and can be reinstalled with `openframe app install`. \ No newline at end of file +Note: this command only removes ArgoCD and OpenFrame apps β€” the underlying cluster is preserved. Use `openframe cluster delete` to remove the cluster itself. \ No newline at end of file diff --git a/cmd/app/.upgrade.md b/cmd/app/.upgrade.md index 5e0d41b1..0b399db7 100644 --- a/cmd/app/.upgrade.md +++ b/cmd/app/.upgrade.md @@ -1,48 +1,41 @@ - -Implements the `upgrade` subcommand for the OpenFrame CLI, providing two modes: re-deploying at a new git ref (Mode 1) or force-syncing the current ref via ArgoCD (Mode 2). + +## upgrade.go + +Implements the `openframe app upgrade` subcommand, which upgrades an already-installed OpenFrame platform in one of two mutually exclusive modes: changing the deployed git ref (Mode 1) or forcing ArgoCD to refresh and re-sync the current ref (Mode 2, the default). ## Key Components -| Symbol | Description | -|---|---| -| `getUpgradeCmd()` | Builds and returns the `cobra.Command` for `openframe app upgrade` with all flags | -| `runUpgradeCommand()` | Dispatches to Mode 1 (change ref) or Mode 2 (force sync); enforces mutual exclusivity of `--ref` and `--sync` | -| `upgradeIsChangeRef()` | Pure predicate β€” returns `true` when `--ref` was explicitly set and `--sync` was not | -| `runUpgradeChangeRef()` | Mode 1: re-deploys at a new ref non-interactively, requires existing values, syncs stragglers on stall | -| `runUpgradeForceSync()` | Mode 2: refreshes and re-syncs the current ref via ArgoCD with optional `--prune` | -| `previewOutOfSync()` | Dry-run helper for Mode 2 β€” lists out-of-sync apps without triggering a sync | -| `resolveUpgradeTarget()` | Resolves the `rest.Config` for Mode 2 via `--context`, positional cluster name, or interactive prompt | -| `clusterNameArg()` | Utility β€” returns the first positional argument or an empty string | +- **`getUpgradeCmd()`** β€” Builds the `cobra.Command` for `app upgrade`, registering install flags plus `--sync` and `--prune`. +- **`runUpgradeCommand(cmd, args)`** β€” Entry point that validates `--ref`/`--sync` are not both set and dispatches to the appropriate mode. +- **`upgradeIsChangeRef(refChanged, sync)`** β€” Determines whether a changed `--ref` means Mode 1 (deploy new ref) vs Mode 2 (force-sync current ref). +- **`runUpgradeChangeRef(...)`** β€” Mode 1: non-interactively re-deploys the app-of-apps at a new ref, reusing existing Helm values (`RequireExistingValues`) and letting the wait step sync stalled children. +- **`runUpgradeForceSync(...)`** β€” Mode 2: resolves the target cluster, refreshes/syncs via ArgoCD with a 15-minute wait budget, optionally pruning resources removed from git. +- **`previewOutOfSync(ctx, manager, verbose, prune)`** β€” Supports `--dry-run` by listing ArgoCD applications and reporting out-of-sync ones without triggering an actual sync. +- **`resolveUpgradeTarget(...)`** β€” Resolves the Kubernetes `rest.Config` and cluster name using `--context`, a positional cluster name, the current context (non-interactive), or an interactive prompt. +- **`clusterNameArg(args)`** β€” Extracts the optional positional cluster name argument. ## Usage Example +```go +// Register the upgrade command under the parent "app" command +appCmd.AddCommand(getUpgradeCmd()) +``` + +CLI usage: + ```bash -# Force re-sync the current ref (Mode 2 β€” default) +# Force re-sync the currently deployed ref (default) openframe app upgrade # Force re-sync and delete resources removed from git openframe app upgrade --sync --prune -# Upgrade to a specific release tag (Mode 1) +# Upgrade to a new release tag (Mode 1) openframe app upgrade --ref v1.3.0 -# Preview what a ref change would do without applying +# Preview a ref change without applying it openframe app upgrade --ref main --dry-run -# Target a specific cluster by name or kubeconfig context +# Target a specific cluster/context openframe app upgrade my-cluster --context k3d-my-cluster -``` - -```go -// The two modes are mutually exclusive at the flag level: -// --ref and --sync together return an error immediately. -if refChanged && sync { - return fmt.Errorf("--ref and --sync are mutually exclusive ...") -} -``` - -## Notes - -- Mode 1 sets `RequireExistingValues = true` to prevent Helm from wiping registry credentials and ingress settings with chart defaults. -- Mode 2 caps the ArgoCD wait timeout at **15 minutes** (vs. the 60-minute install budget) so a stuck child fails fast. -- `--prune` is destructive: it deletes Kubernetes resources no longer present in git. A warning is printed before proceeding. \ No newline at end of file +``` \ No newline at end of file diff --git a/cmd/cluster/.aws_identity.md b/cmd/cluster/.aws_identity.md new file mode 100644 index 00000000..03126c32 --- /dev/null +++ b/cmd/cluster/.aws_identity.md @@ -0,0 +1,40 @@ + +## Overview + +`aws_identity.go` implements a safety check that resolves and confirms the AWS identity (account/ARN) an EKS cluster operation is about to run under, before any billed resources are provisioned. It prevents accidental operations against the wrong AWS account by verifying credentials via `aws sts get-caller-identity` and requiring explicit user confirmation in interactive sessions. + +## Key Components + +- **`awsIdentity`** β€” struct mapping the JSON output of `aws sts get-caller-identity` (`Account`, `Arn`). +- **`confirmAWSIdentity(ctx, exec, profile)`** β€” main entry point. Resolves the caller identity for the given AWS profile (or default credential chain), then: + - In non-interactive mode: logs the identity being used and proceeds without prompting. + - In interactive mode: prompts the user to confirm the account/ARN before continuing. + - On authentication failure: returns an actionable error, distinguishing "no AWS config at all" (links to AWS setup docs) from "this profile/selection is broken" (lists available profiles). +- **`describeAWSSelection(profile)`** β€” formats a human-readable description of the credential source (named profile or default credentials). +- **`awsConfigDocsURL`** β€” constant pointing to the official AWS CLI configuration guide, surfaced in error messages. +- **`confirmAWSIdentityFn` / `awsInteractiveFn`** β€” package-level function variables (seams) allowing tests to stub out terminal confirmation and interactivity detection. + +## Usage Example + +```go +ctx := context.Background() +exec := executor.NewCommandExecutor() // real or test executor + +// Before performing an EKS operation with a given profile: +if err := confirmAWSIdentity(ctx, exec, "my-aws-profile"); err != nil { + log.Fatalf("aborting EKS operation: %v", err) +} + +// Proceed with EKS provisioning/deletion logic only after confirmation. +``` + +In tests, override the seams to avoid real terminal interaction: + +```go +confirmAWSIdentityFn = func(msg string, defaultVal bool) (bool, error) { + return true, nil // simulate user confirming +} +awsInteractiveFn = func() bool { return true } +``` + +This file's logic mirrors the GKE equivalent (`discovery.AuthFlow`) but is specific to AWS EKS identity verification. \ No newline at end of file diff --git a/cmd/cluster/.cleanup.md b/cmd/cluster/.cleanup.md index 0c4fbc5e..f2ef8741 100644 --- a/cmd/cluster/.cleanup.md +++ b/cmd/cluster/.cleanup.md @@ -1,38 +1,39 @@ - -Handles the `cluster cleanup` subcommand, removing unused Docker images and resources from cluster nodes to free disk space. + +## cleanup.go + +Defines the `cluster cleanup` Cobra subcommand, which prunes unused container images from cluster nodes to reclaim disk space without touching installed applications, Helm releases, or namespaces. ## Key Components -- **`getCleanupCmd()`** β€” Builds and returns the `cobra.Command` for `openframe cluster cleanup [NAME]`, wiring up flag validation, aliases (`c`), and the run handler. -- **`runCleanupCluster()`** β€” Core execution function that orchestrates cluster selection, ArgoCD application cleanup injection, and cleanup execution via the service layer. +- **`getCleanupCmd() *cobra.Command`** β€” Constructs the `cleanup` subcommand, wiring up flags, argument validation, and the `PreRunE`/`RunE` lifecycle. Initializes and syncs global flags, validates cleanup-specific flags via `models.ValidateCleanupFlags`, and registers cleanup flags with `models.AddCleanupFlags`. +- **`runCleanupCluster(cmd *cobra.Command, args []string) error`** β€” The command's execution logic: + 1. Lists available clusters via the command service. + 2. Uses `ui.OperationsUI.SelectClusterForCleanup` to resolve the target cluster (with confirmation prompts unless `--force` is set). + 3. Detects the cluster type (`models.ClusterTypeK3d` vs. cloud). + 4. Runs a type-aware prerequisite check β€” only k3d clusters require local tooling (e.g., Docker), so cloud clusters can be cleanly rejected with a pointer to `cluster delete` instead. + 5. Calls `service.CleanupCluster` and reports results (including partial failures) via `operationsUI.ShowCleanupSummary`. ## Usage Example ```bash -# Clean up the default/only cluster interactively +# Interactively select a cluster to clean up openframe cluster cleanup -# Target a specific cluster by name +# Clean up a specific cluster openframe cluster cleanup my-cluster # Skip confirmation prompt openframe cluster cleanup my-cluster --force ``` -## ArgoCD Integration - -Before executing cleanup, the function attempts to inject an ArgoCD-backed application cleaner at the composition root. This handles `Application` deletion and finalizer stripping, preventing namespaces from getting stuck in `Terminating`. If the cluster is unreachable or ArgoCD is not present, cleanup continues without it (best-effort). Pass `--verbose` to surface ArgoCD availability warnings. +Programmatic registration within the cluster command group: -## Flow +```go +func NewClusterCmd() *cobra.Command { + clusterCmd := &cobra.Command{Use: "cluster"} + clusterCmd.AddCommand(getCleanupCmd()) + return clusterCmd +} +``` -```mermaid -graph TD - A[List Clusters] --> B[Select Cluster via UI] - B --> C[Detect Cluster Type] - C --> D{ArgoCD reachable?} - D -->|yes| E[Inject App Cleaner] - D -->|no| F[Skip ArgoCD Step] - E --> G[CleanupCluster] - F --> G - G --> H[Show Summary] -``` \ No newline at end of file +Note: this command only prunes dangling images. Use `openframe app uninstall` to remove the OpenFrame platform, or `openframe cluster delete` to remove the entire cluster. \ No newline at end of file diff --git a/cmd/cluster/.cluster.md b/cmd/cluster/.cluster.md index 02a4688b..45d0a400 100644 --- a/cmd/cluster/.cluster.md +++ b/cmd/cluster/.cluster.md @@ -1,15 +1,18 @@ - -Registers and wires together the `cluster` command group for the OpenFrame CLI, exposing Kubernetes cluster lifecycle management via Cobra subcommands. + +## cluster.go + +Defines the top-level `cluster` Cobra command and its subcommand group for Kubernetes cluster lifecycle management, wiring together shared flags, prerequisite checks, and UI conventions applied to `create`, `delete`, `list`, `status`, `use`, and `cleanup` subcommands. ## Key Components -| Symbol | Description | -|---|---| -| `GetClusterCmd()` | Returns the root `cluster` cobra.Command with all subcommands and persistent middleware attached | -| `PersistentPreRunE` | Middleware that handles `--silent` mode, suppresses prerequisite checks for machine output (`json`/`yaml`), shows the logo, and validates prerequisites | -| Subcommands | `create`, `delete`, `list`, `status`, `cleanup` β€” registered via `clusterCmd.AddCommand(...)` | -| `utils.InitGlobalFlags()` | Initialises shared flags before command construction | -| `models.AddGlobalFlags()` | Attaches global flags (e.g. `--output`, `--silent`) to the command | +- **`GetClusterCmd() *cobra.Command`** β€” Constructs and returns the `cluster` command (aliased `k`), registers all subcommands, initializes global flags, and defines `PersistentPreRunE`/`RunE` hooks: + - Applies global `--silent`/`--verbose` output flags (since this command group's own `PersistentPreRunE` shadows the root's). + - Skips logo/context header/prerequisite checks entirely for machine-readable output (`--output json|yaml`). + - Shows the logo and a kube-context header for subcommands (not for the bare `cluster` command). + - Runs the generic prerequisite gate (Docker/k3d/helm) unless the subcommand handles its own type-aware gating. + - Falls back to displaying help when invoked with no subcommand. + +- **`skipsGenericPrerequisiteGate(name string) bool`** β€” Internal helper listing subcommands (`create`, `use`, `status`, `list`, `delete`, `cleanup`) that bypass the generic prerequisite gate, either because they perform type-aware gating internally (cloud vs. local clusters) or because they're read-only/no-tool operations. ## Usage Example @@ -17,34 +20,24 @@ Registers and wires together the `cluster` command group for the OpenFrame CLI, package main import ( - "github.com/flamingo-stack/openframe-cli/internal/cluster" - "github.com/spf13/cobra" + "github.com/flamingo-stack/openframe-cli/internal/cluster" + "github.com/spf13/cobra" ) func main() { - rootCmd := &cobra.Command{Use: "openframe"} + rootCmd := &cobra.Command{Use: "openframe"} + rootCmd.AddCommand(cluster.GetClusterCmd()) - // Register the cluster command group - rootCmd.AddCommand(cluster.GetClusterCmd()) - - rootCmd.Execute() + if err := rootCmd.Execute(); err != nil { + panic(err) + } } ``` +Running the resulting CLI: + ```bash -# CLI usage once registered openframe cluster create -openframe cluster list -openframe cluster status -openframe cluster delete -openframe cluster cleanup - -# Machine-readable output (skips logo and prerequisite checks) -openframe cluster list --output json -``` - -## Notes - -- The alias `k` is available as a shorthand for `cluster` -- Prerequisite checks are bypassed when `--output json` or `--output yaml` is set, keeping stdout clean for scripted consumption -- `PersistentPreRunE` shadows the root command's hook, so `--silent` must be honoured explicitly here \ No newline at end of file +openframe cluster status --output json +openframe k list +``` \ No newline at end of file diff --git a/cmd/cluster/.create.md b/cmd/cluster/.create.md index a8fb0425..0fa17b42 100644 --- a/cmd/cluster/.create.md +++ b/cmd/cluster/.create.md @@ -1,43 +1,40 @@ - -Handles the `create` subcommand for provisioning new Kubernetes clusters, supporting both an interactive configuration wizard and a direct non-interactive mode via flags. + +Note: The provided source appears truncated at the end (`retur`), but based on the visible logic, here's the documentation: ## Key Components -| Symbol | Type | Description | -|--------|------|-------------| -| `getCreateCmd()` | `func` | Builds and returns the `cobra.Command` for `cluster create`, wiring flags and pre-run validation | -| `runCreateCluster()` | `func` | Core handler β€” branches between interactive wizard flow and flag-driven config, then delegates to the service layer | +- **`getCreateCmd()`** β€” Builds the `cluster create` Cobra command, wiring flag validation (`PreRunE`) and execution (`RunE`) via `utils.WrapCommandWithCommonSetup`. +- **`runCreateCluster(cmd, args)`** β€” Main command handler. Resolves cluster configuration either through an interactive wizard (`ui.NewConfigurationHandler`) or directly from CLI flags/args when `--skip-wizard` is set, validates node/scaling flags, and dispatches to dry-run preview or actual cluster creation. +- **`cloudPlanPreview(ctx, config)`** (assigned to `planPreviewFn`) β€” Runs a real Terraform plan for cloud cluster types (GKE/EKS) to preview resource changes without provisioning, including cloud auth checks and cost estimation. Overridable for tests. +- **`showCostEstimate(...)`** β€” Best-effort monthly cost estimate using `infracost`; offers install/login flows interactively, falling back to a generic pricing hint (`ui.CostHint`) if unavailable. +- **`offerInfracostInstall()` / `offerInfracostLogin()`** β€” Interactive helpers that prompt the user to install `infracost` or run its browser-based login, attached directly to the terminal. +- **Test seams**: `infracostAvailableFn`, `infracostOfferFn`, `infracostLoginFn`, `planPreviewFn` β€” package-level function variables allowing unit tests to stub out real network/CLI/browser interactions. ## Usage Example +```go +// Registering the create command with a parent cluster command +func NewClusterCmd() *cobra.Command { + clusterCmd := &cobra.Command{Use: "cluster"} + clusterCmd.AddCommand(getCreateCmd()) + return clusterCmd +} +``` + +Typical CLI invocations handled by this file: + ```bash -# Show creation mode selection (interactive) +# Interactive selection menu openframe cluster create -# Pass a custom cluster name into the wizard -openframe cluster create my-cluster - -# Skip wizard β€” create immediately with defaults +# Direct creation with defaults, no prompts openframe cluster create --skip-wizard -# Skip wizard with explicit overrides -openframe cluster create --nodes 3 --type k3d --skip-wizard - -# Dry-run to preview config without creating -openframe cluster create --skip-wizard --dry-run -``` +# Cloud cluster (GKE) with explicit settings +openframe cluster create my-gke --type gke --project my-project --region us-central1 --skip-wizard -## Flow Summary - -```mermaid -graph TD - A["cluster create"] --> B{"--skip-wizard?"} - B -->|No| C["ConfigurationHandler wizard"] - B -->|Yes| D["Build ClusterConfig from flags"] - C --> E{"--dry-run?"} - D --> E - E -->|Yes| F["Print summary and exit"] - E -->|No| G["service.CreateCluster()"] +# Preview cloud resources/costs without provisioning +openframe cluster create my-eks --type eks --region us-east-1 --dry-run ``` -**Non-interactive defaults:** cluster type `k3d`, node count `3`, name `openframe-dev`. If a cluster with the same name already exists it is reused without modification β€” run `cluster delete` first to start fresh. Use the `bootstrap` command after creation to install OpenFrame components. \ No newline at end of file +This file lives at [`internal/cluster/create.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/cluster/create.go) in the `cluster` package. \ No newline at end of file diff --git a/cmd/cluster/.delete.md b/cmd/cluster/.delete.md index 5a36fc92..100219ba 100644 --- a/cmd/cluster/.delete.md +++ b/cmd/cluster/.delete.md @@ -1,31 +1,38 @@ - -Implements the `cluster delete` subcommand, which deletes a named (or interactively selected) Kubernetes cluster and cleans up all associated resources. + +# delete.go + +Implements the `openframe cluster delete` CLI command, which safely deletes a Kubernetes cluster (local or cloud) and cleans up associated resources like intercepts, Docker artifacts, and configuration files. ## Key Components -- **`getDeleteCmd() *cobra.Command`** β€” Constructs and returns the Cobra command definition for `openframe cluster delete [NAME]`, registering delete-specific flags and wiring `PreRunE` validation with `RunE` execution. -- **`runDeleteCluster(cmd, args)`** β€” Core handler that lists available clusters, prompts for selection (if no name is provided), detects the cluster type, and delegates deletion to the service layer with optional `--force` support. +- **`getDeleteCmd() *cobra.Command`** β€” Constructs the `delete` Cobra command, wiring up flag initialization/validation and registering `runDeleteCluster` as the execution handler. +- **`confirmCloudDeletion(clusterType, clusterName, force) (bool, error)`** β€” Extra destroy gate for cloud clusters (EKS/GKE). Requires the user to re-type the cluster name unless `--force` is passed. Refuses non-interactive sessions without `--force` to avoid hanging or accidental destruction. +- **`runDeleteCluster(cmd, args) error`** β€” Core deletion workflow: + 1. Lists available clusters and resolves the target via interactive selection or CLI args. + 2. Displays a friendly operation-start message. + 3. Detects the cluster type (local vs. cloud). + 4. Applies the stronger cloud confirmation gate via `confirmCloudDeletion`. + 5. Runs type-aware prerequisite checks (e.g., terraform/cloud CLI for cloud clusters) β€” deliberately placed *after* confirmation so a declined deletion never triggers tool installation. + 6. Delegates actual deletion to the service layer and reports success/failure. ## Usage Example -```bash -# Delete a named cluster -openframe cluster delete my-cluster +```go +// Registering the delete command with the cluster command group +rootCmd.AddCommand(getDeleteCmd()) +``` -# Delete without confirmation prompt -openframe cluster delete my-cluster --force +Command-line usage: -# Interactive cluster selection +```bash +# Interactive selection openframe cluster delete -``` -```go -// Registered on the parent cluster command (internal wiring) -clusterCmd.AddCommand(getDeleteCmd()) -``` +# Delete a specific cluster +openframe cluster delete my-cluster -The command follows a three-phase flow: +# Skip confirmation prompts (required for non-interactive/CI use with cloud clusters) +openframe cluster delete my-cluster --force +``` -1. **Selection** β€” resolves a cluster name from CLI args or an interactive UI picker via `operationsUI.SelectClusterForDelete`. -2. **Detection** β€” calls `service.DetectClusterType` to determine the provider (e.g., kind, k3d). -3. **Deletion** β€” calls `service.DeleteCluster`, forwarding context, cluster name, type, and the `--force` flag; errors are surfaced through `sharedErrors.HandleGlobalError` with optional verbose output. \ No newline at end of file +This command is registered as a subcommand under `openframe cluster` and relies on shared utilities (`utils.GetCommandService`, `utils.GetGlobalFlags`) and UI helpers (`ui.NewOperationsUI`, `ui.ConfirmTypedClusterName`) for its behavior. \ No newline at end of file diff --git a/cmd/cluster/.list.md b/cmd/cluster/.list.md index 4a530bab..defe643b 100644 --- a/cmd/cluster/.list.md +++ b/cmd/cluster/.list.md @@ -1,47 +1,40 @@ - -Implements the `cluster list` subcommand, which retrieves and displays all Kubernetes clusters managed by OpenFrame CLI in text, JSON, or YAML formats. + +# list.go + +Implements the `openframe cluster list` command, which displays all Kubernetes clusters managed by OpenFrame CLI. Supports text, JSON, and YAML output formats, and can optionally discover unmanaged cloud clusters (GKE and EKS) via the `--all` flag. ## Key Components -| Symbol | Description | -|--------|-------------| -| `getListCmd()` | Builds and returns the `cobra.Command` for `openframe cluster list`, wiring up flags, pre-run validation, and the run handler | -| `runListClusters()` | Fetches clusters via the command service and dispatches to the appropriate output formatter based on the `--output` flag | -| `clusterJSON` | Serialization struct mapping `ClusterInfo` fields to machine-readable JSON/YAML keys (`name`, `type`, `status`, `nodeCount`, `k8sVersion`) | -| `clustersToJSON()` | Converts a slice of `models.ClusterInfo` to `[]clusterJSON` for structured output | -| `printClustersJSON()` | Marshals the cluster list to indented JSON and writes it to stdout | -| `printClustersYAML()` | Marshals the cluster list to YAML (reusing `json:` struct tags via `sigs.k8s.io/yaml`) and writes it to stdout | +- **`getListCmd()`** – Builds the Cobra `list` subcommand, wires up flag validation (`PreRunE`) and execution (`RunE`), and registers the `--output` flag alongside global list flags. +- **`runListClusters(cmd, args)`** – Main command handler: fetches managed clusters from the command service, optionally merges in externally discovered clusters (`--all`), and renders output based on the `--output` format. +- **`discoverExternalClusters(ctx, managed)`** – Orchestrates GKE and EKS discovery, filters out clusters already tracked in the registry (matched by name, type, and project), and aggregates non-fatal notices (e.g., auth issues). +- **`discoverGKE(ctx)` / `discoverEKS(ctx)`** – Provider-specific discovery helpers. GKE offers an interactive `gcloud` login flow when unauthenticated; EKS instead returns an informational notice since no interactive AWS login exists. +- **`clusterJSON`** – Machine-readable struct (JSON tags) representing a cluster's name, type, status, node count, and Kubernetes version. +- **`clustersToJSON(clusters)`** – Converts internal `models.ClusterInfo` slices into `clusterJSON` for serialization. +- **`printClustersJSON(clusters)` / `printClustersYAML(clusters)`** – Serialize and print cluster data as JSON or YAML (YAML reuses JSON struct tags via `sigs.k8s.io/yaml`). ## Usage Example +```go +// Registering the list command with the root cluster command +rootCmd.AddCommand(getListCmd()) +``` + +Example CLI usage: + ```bash -# Default table output +# List managed clusters in a formatted table openframe cluster list -# Verbose table output -openframe cluster list --verbose +# Include externally discovered GKE/EKS clusters +openframe cluster list --all -# Suppress headers / minimal output -openframe cluster list --quiet - -# Machine-readable JSON +# Output as JSON or YAML openframe cluster list --output json - -# Machine-readable YAML openframe cluster list --output yaml -``` -```go -// JSON output shape (one entry per cluster) -[ - { - "name": "prod-cluster", - "type": "kind", - "status": "Running", - "nodeCount": 3, - "k8sVersion": "v1.29.0" - } -] +# Suppress extra output +openframe cluster list --quiet ``` -The `--output` flag accepts `text` (default), `json`, or `yaml`. Any other value returns an error. Text output respects `--quiet` and `--verbose` global flags for controlling table verbosity. \ No newline at end of file +Discovery failures (e.g., missing `gcloud`/`aws` CLI, expired credentials) never cause the command to fail β€” they surface as informational notices printed after the cluster table, keeping `list` resilient to auth/config issues in external providers. \ No newline at end of file diff --git a/cmd/cluster/.use.md b/cmd/cluster/.use.md new file mode 100644 index 00000000..6ee9c179 --- /dev/null +++ b/cmd/cluster/.use.md @@ -0,0 +1,35 @@ + +Implements the `openframe cluster use` command, which switches the local kubectl context (and, for GKE, the active gcloud configuration) to a named cluster. It resolves clusters known to the CLI (local k3d, openframe-managed) as well as external GKE/EKS clusters discovered via `gcloud`/`aws` CLIs, fetching credentials automatically when no kubeconfig entry exists yet. + +## Key Components + +- **`getUseCmd()`** – Builds the Cobra command definition for `cluster use [NAME]`, including help text, argument validation, and global flag setup. +- **`runUseCluster(cmd, args)`** – Main command handler: resolves the target cluster name (explicit arg or interactive selection), detects if it's a known cluster type (k3d/GKE), and switches context; otherwise delegates to external cluster lookup. +- **`useExternalCluster(ctx, exec, kubeconfig, name)`** – Searches external GKE then EKS clusters by name, aggregating which clouds were searched for error reporting. +- **`useExternalGKE(...)`** – Discovers external GKE clusters, handles gcloud auth flow (interactive login if needed), fetches credentials via `gcloud container clusters get-credentials` if missing, and aligns the gcloud configuration. +- **`useExternalEKS(...)`** – AWS twin of the GKE flow; discovers EKS clusters and fetches credentials via `aws eks update-kubeconfig` (no interactive login, since AWS auth relies on profiles/SSO). +- **`switchTo(kubeconfig, contextName, clusterName)`** – Validates the kubeconfig context exists and performs the actual `kubectl` context switch, printing a success message. +- **`alignGcloudConfiguration(ctx, exec, project)`** – Best-effort helper that activates the matching gcloud configuration for a given GCP project. + +## Usage Example + +```go +// Register the "use" subcommand under "cluster" +clusterCmd.AddCommand(getUseCmd()) +``` + +Command-line usage: + +```bash +# Switch to a local k3d cluster +openframe cluster use openframe-dev + +# Switch to an openframe-managed GKE cluster +openframe cluster use my-gke + +# Switch to an externally discovered GKE/EKS cluster +openframe cluster use tenant-cluster-1 + +# Interactive selection when no name is given +openframe cluster use +``` \ No newline at end of file diff --git a/cmd/prerequisites/.prerequisites.md b/cmd/prerequisites/.prerequisites.md index c18376e1..4fc6e825 100644 --- a/cmd/prerequisites/.prerequisites.md +++ b/cmd/prerequisites/.prerequisites.md @@ -1,34 +1,45 @@ - -Wires the OS-aware prerequisites framework into the `openframe prerequisites` Cobra command, providing subcommands to check and install required tools (Docker, kubectl, k3d, helm). + +# prerequisites.go + +Implements the `openframe prerequisites` command, which lets users check and install the tools required for a given cluster type (k3d, eks, gke) before running other OpenFrame commands. ## Key Components -| Symbol | Type | Description | -|---|---|---| -| `GetPrerequisitesCmd` | `func` | Returns the root `prerequisites` Cobra command with `check` and `install` subcommands registered | -| `checkCmd` | `func` | Builds the `check` subcommand β€” reports installed/missing prerequisites without making changes | -| `installCmd` | `func` | Builds the `install` subcommand β€” auto-installs missing tools on macOS/Linux; prints manual docs links on Windows | -| `printResult` | `func` | Renders a user-friendly pterm summary of satisfied, newly installed, and still-missing prerequisites | +- **`GetPrerequisitesCmd()`** – Builds the top-level `prerequisites` (aliases: `prereq`, `prereqs`) cobra command and registers its `check` and `install` subcommands. +- **`addTypeFlag(cmd, *string)`** – Registers a shared `--type`/`-t` flag (default `k3d`) matching the flag shape used on `cluster create`, supporting aliases like `aws`/`gcp`. +- **`installCommandFor(clusterType)`** – Builds the recovery command string suggested after a failed check, preserving the selected `--type` (except the default k3d). +- **`checkCmd()`** – Returns the `check` subcommand: parses the cluster type, resolves the required prerequisite set via `clusterprereq.SetForClusterType`, runs `fw.NewRunner().Check(set)`, prints results, and returns an error listing missing tools plus the install command to fix them. +- **`installCmd()`** – Returns the `install` subcommand: resolves the prerequisite set, warns if automatic installs aren't supported on the current OS, runs `fw.NewRunner().Run(ctx, set)`, and reports any tools still missing. +- **`printResult(res fw.Result)`** – Renders a user-friendly summary (βœ“/βœ— per tool) covering already-satisfied, newly-installed, and missing prerequisites, including reasons, docs links, and debug error detail. ## Usage Example +```go +package main + +import ( + "github.com/flamingo-stack/openframe-cli/internal/prerequisites" + "github.com/spf13/cobra" +) + +func main() { + root := &cobra.Command{Use: "openframe"} + root.AddCommand(prerequisites.GetPrerequisitesCmd()) + root.Execute() +} +``` + +Typical CLI usage: + ```bash -# Report status without changes +# Check prerequisites for the default k3d cluster type openframe prerequisites check -# Install any missing prerequisites -openframe prerequisites install +# Check prerequisites for an EKS cluster +openframe prerequisites check --type eks -# Aliases also work -openframe prereq check -openframe prereqs install +# Install missing prerequisites for GKE +openframe prerequisites install --type gke ``` -```go -// Registering the command in a parent Cobra command -rootCmd.AddCommand(prerequisites.GetPrerequisitesCmd()) -``` - -**`check` exit behavior:** exits non-zero with a message directing the user to `install` if any prerequisites are missing. - -**`install` exit behavior:** exits non-zero if prerequisites remain missing after the install attempt; on unsupported OSes (Windows), prints a warning and shows manual install docs URLs from the [`clusterprereq.ClusterSet()`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/cluster/prerequisites) definitions instead of attempting auto-install. \ No newline at end of file +If `check` finds missing tools, it returns a non-zero exit code and suggests the exact `install --type ...` command to run next. \ No newline at end of file diff --git a/cmd/update/.update.md b/cmd/update/.update.md index 0003da9f..e34ea6ab 100644 --- a/cmd/update/.update.md +++ b/cmd/update/.update.md @@ -1,44 +1,46 @@ - -Implements the `openframe update` Cobra command tree, handling in-place binary upgrades, version-specific installs, update checks, and offline rollbacks with checksum verification and interactive confirmation. + +# update.go + +Implements the `openframe update` command family, providing self-update capability for the OpenFrame CLI: checking for new releases, updating/downgrading to a specific version, and rolling back to a previously-installed binary. All downloads are checksum-verified, and a backup of the running binary is retained for offline rollback. ## Key Components -| Export / Function | Description | -|---|---| -| `GetUpdateCmd(currentVersion string)` | Returns the root `update` command with `--yes` and `--force` flags; registers `check` and `rollback` subcommands | -| `newCheckCmd(current string)` | `openframe update check` β€” queries GitHub releases and reports availability without modifying anything; supports `--output text\|json\|yaml` | -| `newRollbackCmd(current string)` | `openframe update rollback` β€” reverts to the previously saved binary offline, with `--yes` to skip confirmation | -| `run(...)` | Core update logic: checks release, prompts for consent, applies download with a 15-minute timeout, and shows spinner progress | -| `runRollback(...)` | Restores the backup binary retained by the last successful update | -| `reportStatus(cmd, st)` | Renders a `selfupdate.Status` value as `text`, `json`, or `yaml` to stdout | +- **`GetUpdateCmd(currentVersion string) *cobra.Command`** β€” Builds the root `update` command tree with `--yes`/`--force` flags and `check`/`rollback` subcommands. +- **`newCheckCmd(current string) *cobra.Command`** β€” Implements `openframe update check`; reports update availability without modifying anything. Supports `--output text|json|yaml`. +- **`newRollbackCmd(current string) *cobra.Command`** β€” Implements `openframe update rollback`; reverts to the previously saved binary, offline. +- **`run(ctx, current, target string, assumeYes, force bool) error`** β€” Core update/downgrade/reinstall logic: checks for updates, prompts for confirmation (unless `--yes`), applies the update with a bounded 15-minute timeout. +- **`runRollback(ctx, current string, assumeYes bool) error`** β€” Restores the previous binary via `selfupdate.PreviousVersion()` and `Updater.Rollback`. +- **`reportStatus(cmd, st selfupdate.Status) error`** β€” Renders check results in text, JSON, or YAML format. +- **`validateOutputFormat(format string) error`** β€” Single source of truth validating `--output` values before any network call is made. ## Usage Example ```go -// Wiring into the root command (typically in cmd/root.go): -rootCmd.AddCommand(update.GetUpdateCmd(versionInfo.Version)) +package main + +import ( + "os" + + "github.com/flamingo-stack/openframe-cli/internal/commands/update" + "github.com/spf13/cobra" +) + +func main() { + root := &cobra.Command{Use: "openframe"} + root.AddCommand(update.GetUpdateCmd("v1.3.2")) + if err := root.Execute(); err != nil { + os.Exit(1) + } +} ``` -```bash -# Update to the latest release -openframe update - -# Downgrade or switch to a specific version -openframe update v1.4.0 - -# Non-interactive update (CI-safe, requires explicit flag) -openframe update --yes +Command-line usage: -# Check availability and emit JSON for scripting -openframe update check --output json - -# Revert to the previously installed version without a download -openframe update rollback --yes +```bash +openframe update # update to the latest release +openframe update v1.4.0 # switch to a specific version, up or down +openframe update check -o json # machine-readable availability check +openframe update rollback -y # revert to previous binary, no prompt ``` -## Notes - -- Binary replacement always requires explicit consent (`--yes` or interactive prompt); non-interactive sessions are never auto-confirmed. -- Downloads time out after **15 minutes** via a context deadline applied only to the apply phase. -- Automatic background updates are opt-in via `OPENFRAME_AUTO_UPDATE=1` and are handled outside this command path. -- Spinner output is routed to stderr; `check --output json|yaml` keeps stdout clean for piping. \ No newline at end of file +Automatic (opt-in) daily checks can be enabled by setting `OPENFRAME_AUTO_UPDATE=1`, which is skipped in CI/non-interactive shells and never crosses major versions. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index eef5a6d0..4eef2ed9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,53 +1,69 @@ # OpenFrame CLI Documentation -`openframe` is an interactive command-line tool for standing up and managing OpenFrame Kubernetes environments. It provisions local k3d clusters, deploys the OpenFrame platform via an ArgoCD app-of-apps GitOps workflow, and keeps itself up to date. +Welcome to the documentation for **OpenFrame CLI** β€” the command-line tool for provisioning Kubernetes clusters (local k3d, or cloud GKE/EKS via Terraform) and deploying the [OpenFrame](https://openframe.ai) platform onto them via ArgoCD's app-of-apps pattern. -This repository (`flamingo-stack/openframe-cli`) is the CLI. The platform and application manifests it deploys live in [`flamingo-stack/openframe-oss-tenant`](https://github.com/flamingo-stack/openframe-oss-tenant). +## πŸ“š Table of Contents -## Getting Started +### Getting Started -- [Introduction](./getting-started/introduction.md) β€” Overview and key concepts -- [Prerequisites](./getting-started/prerequisites.md) β€” System requirements and dependencies -- [Quick Start](./getting-started/quick-start.md) β€” Install and bootstrap in a few minutes -- [First Steps](./getting-started/first-steps.md) β€” Core commands and workflows -- [Cloud Clusters](./getting-started/cloud-clusters.md) β€” Provision EKS/GKE clusters with Terraform (reference) -- [GKE Workflow](./getting-started/gke-workflow.md) β€” Step-by-step: from zero to a running GKE cluster +- [Introduction](./getting-started/introduction.md) β€” What OpenFrame CLI is, key features, and who it's for +- [Prerequisites](./getting-started/prerequisites.md) β€” Required tools, hardware requirements, and environment variables +- [Quick Start](./getting-started/quick-start.md) β€” From zero to a running local OpenFrame platform in ~5 minutes +- [First Steps](./getting-started/first-steps.md) β€” Day-to-day commands after your first bootstrap +- [GKE Workflow](./getting-started/gke-workflow.md) β€” Step-by-step walkthrough for provisioning a GKE cluster +- [Cloud Clusters (EKS / GKE)](./getting-started/cloud-clusters.md) β€” Reference for cloud cluster flags, state model, and troubleshooting -## Reference +### Development -- [Terminal Output](./reference/terminal-output.md) β€” Live dashboards, sequential/CI mode, `--plain`/`--silent`/`--verbose`, color and glyph controls, GitHub Actions integration +- [Development Overview](./development/README.md) β€” Where to start, project at a glance +- [Environment Setup](./development/setup/environment.md) β€” Toolchain and editor setup +- [Local Development](./development/setup/local-development.md) β€” Cloning, building, running, and debugging +- [Architecture](./development/architecture/README.md) β€” Core components, data flow, and key design decisions +- [Security](./development/security/README.md) β€” Secure-by-default patterns, secret handling, and code review checklist +- [Testing](./development/testing/README.md) β€” Test structure, running tests, and coverage expectations +- [Contributing Guidelines](./development/contributing/guidelines.md) β€” Code style, branching, commits, and PR process +- [Releasing](./development/releasing.md) β€” Semantic-release flow and release invariants +- [Release Signing](./development/release-signing.md) β€” macOS/Windows binary signing and verification -## Commands +### Reference -- `openframe bootstrap` β€” Create a cluster and install the platform in one step -- `openframe cluster {create,delete,list,status,cleanup}` β€” Manage k3d and cloud (EKS/GKE) clusters -- `openframe app {install,upgrade,status,access,uninstall}` β€” Manage the OpenFrame app-of-apps deployment (`status` also has `--watch` and `--interactive` live views) -- `openframe prerequisites {check,install}` β€” Check and install required tools -- `openframe update` (`check`, `rollback`, `update `) β€” Self-update the CLI -- `openframe completion` β€” Generate shell completion scripts +Technical reference documentation generated from the codebase: -## System Requirements +- [OpenFrame CLI Overview](./reference/architecture/overview.md) β€” Architecture, core components, dependency diagram, data flow, and CLI command reference +- [Ecosystem](./reference/architecture/ecosystem.md) β€” Published artifacts, upstream/downstream dependency graph -A full local platform is demanding. Recommended host: +### Diagrams -| Resource | Recommended | -|----------|-------------| -| RAM | 24 GB | -| CPU | 6 cores | -| Disk | 50 GB free | +Visual documentation β€” Mermaid diagrams available in `./diagrams/architecture/`: -## Dependencies +- `architecture-diagram.mmd` β€” High-level CLI layer, domain services, and provider architecture +- `dependency-diagram.mmd` β€” Component dependency graph across `cmd/` and `internal/` +- `bootstrap-sequence.mmd` β€” Sequence diagram of the `openframe bootstrap` flow +- `app-status-aggregation.mmd` β€” Sequence diagram of `openframe app status` aggregation logic -**Docker is the only tool you install and run yourself.** The CLI auto-installs pinned, verified copies of `kubectl`, `k3d`, and `helm` into `~/.openframe/bin`. `mkcert` is used to issue a locally-trusted certificate for the HTTPS ingress. See [Prerequisites](./getting-started/prerequisites.md). +See also the [diagrams README](./diagrams/architecture/README.md) for context on each diagram. -## Community and Support +### CLI Tools -- **Slack**: [OpenMSP community](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) (primary support channel) -- **Website**: [https://flamingo.run](https://flamingo.run) -- **Platform**: [https://openframe.ai](https://openframe.ai) +The OpenFrame platform that this CLI deploys is maintained in a separate repository: -We don't monitor GitHub Issues for support β€” use Slack. +- **Repository**: [flamingo-stack/openframe-oss-tenant](https://github.com/flamingo-stack/openframe-oss-tenant) +- **Documentation**: [OpenFrame Documentation](https://github.com/flamingo-stack/openframe-oss-tenant/tree/main/docs) -## License +**Note**: The OpenFrame platform source code is NOT located in this repository. This repository contains only the CLI that provisions clusters and installs that platform. Always refer to the external repository for platform-specific documentation. -See [LICENSE.md](../LICENSE.md). +## πŸ“– Quick Links + +- [Project README](../README.md) β€” Main project README +- [Contributing](../CONTRIBUTING.md) β€” How to contribute +- [License](../LICENSE.md) β€” License information + +## Community + +There are no GitHub Issues or Discussions for this project. All discussions, questions, and support happen in the **OpenMSP Slack community**: + +- Join: [https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) +- Visit: [https://www.openmsp.ai/](https://www.openmsp.ai/) + +--- +*Documentation generated by [🦩 Flamingo Code Documentation](https://flamingo.run)* diff --git a/docs/architecture/decisions.md b/docs/architecture/decisions.md deleted file mode 100644 index 01ade042..00000000 --- a/docs/architecture/decisions.md +++ /dev/null @@ -1,183 +0,0 @@ -# Architecture Decisions - -This document records the key design decisions for the OpenFrame CLI restructure. -It is the authoritative reference for how the CLI is organized and why. - -Status: **accepted** Β· Last updated: 2026-06-24 - ---- - -## Context - -OpenFrame CLI is used by operators and semi-technical users to stand up OpenFrame -on Kubernetes. The primary supported path is **OSS** (a local cluster, no -credentials). SaaS modes come later. The CLI is being restructured into three -clearly isolated abstractions so each can be used on its own. - ---- - -## D1 β€” Three isolated abstractions: cluster, app, prerequisites - -The CLI is organized around three independent concerns: - -- **cluster** β€” make a Kubernetes cluster (local now; cloud later). -- **app** β€” deploy the OpenFrame application (Helm chart β†’ ArgoCD β†’ apps) onto a - cluster that already exists and is online. -- **prerequisites** β€” check and install the tools each of the above needs. - -**Rule:** the `app` subsystem must not import cluster-creation code. It only -talks to a cluster through a small Kubernetes access API (list contexts, check -health, check resources). This lets a user who already has a cluster (their own, -or one made outside OpenFrame) install the app into it, and lets a user create a -cluster without installing anything. - ---- - -## D2 β€” OSS-tenant is the only deployment - -The CLI supports a single deployment: **oss-tenant**. The app is always installed -from the public `openframe-oss-tenant` chart repository, which requires no -credentials. There is no `--deployment-mode` flag; `--non-interactive` simply -reuses the existing `openframe-helm-values.yaml`. - -| deployment | chart repository | credentials | -|--------------|---------------------------------|-------------| -| `oss-tenant` | `openframe-oss-tenant` (public) | none | - -The cluster is always a local k3d cluster. - ---- - -## D3 β€” Commands: `cluster` and `app` are the two primitives - -- `openframe cluster create|delete|list|status|cleanup` β€” cluster lifecycle. - `create` **only creates the cluster**; it never installs the app. (Verb is - `create`; there is no `apply`.) `cleanup` only prunes unused container images - on the nodes; removing the platform is `app uninstall`'s job. -- `openframe app install|upgrade|status|access|uninstall` β€” installs and operates - the OpenFrame app on an existing, online cluster. `upgrade` re-deploys the - app-of-apps at a new git ref (`--ref`) or forces an ArgoCD hard refresh + sync - (`--sync`); `access` prints the ArgoCD admin credentials and how to open the - UI. (`app` was previously named `chart`.) -- `openframe prerequisites check|install [cluster|app]` β€” the prerequisite - checks/installs as first-class commands. -- `openframe update` β€” self-update of the CLI binary (checksum + cosign verified, - with `check` and `rollback`); see D6-adjacent tooling in - `internal/shared/selfupdate`. - ---- - -## D4 β€” `bootstrap` is a thin orchestrator - -`openframe bootstrap [name] [--non-interactive] [--verbose]` stays as a single, -beginner-friendly command. Internally it only orchestrates: - -```text -prerequisites β†’ cluster create β†’ app install -``` - -It contains no business logic of its own β€” everything lives in the primitives. -`openframe bootstrap --non-interactive` reuses the existing `openframe-helm-values.yaml` -for the OSS tenant deployment. - ---- - -## D5 β€” Cluster providers behind a unified interface - -Cluster creation goes through a `Provider` interface with three backends: -**k3d** (local), **EKS**, and **GKE** (cloud). Backends are selected via the -`provider.New(type)` factory, keyed on `ClusterConfig.Type`; the rest of the -CLI never knows which backend runs. Cloud providers additionally implement -`Planner` (`--dry-run` renders a real `terraform plan` footprint). - -The cloud backends share one terraform engine (D7/D8): each generates a -pinned, self-contained root module on the public `terraform-aws-modules` / -`terraform-google-modules` modules and drives `terraform` via terraform-exec. -Kubeconfig entries carry no static credentials β€” auth runs through the -provider CLI exec plugins (`aws eks get-token`, `gke-gcloud-auth-plugin`), -with the context named after the cluster so exact-match context resolution -works unchanged. - -For OSS the default remains **local** (k3d); cloud clusters are an explicit -`--type eks|gke` opt-in with a cost warning and a typed-name confirmation on -delete. - ---- - -## D6 β€” No dependency on the ArgoCD Go module (use the dynamic client) - -ArgoCD is **not importable as a Go library** (its `go.mod` uses a local -`replace => ./gitops-engine`), which previously pinned the entire Kubernetes -*server* tree (`k8s.io/kubernetes`) into this CLI. - -The CLI reads ArgoCD `Application` resources through the Kubernetes **dynamic -client** (unstructured, GVR `argoproj.io/v1alpha1 applications`) instead of the -typed argo-cd clientset. Benefits: - -- **version-agnostic** β€” compatible with whatever ArgoCD version is deployed, - including the latest; -- removes the largest supply-chain dependency; -- unblocks keeping `k8s.io/*` on the latest stable release. - ---- - -## D7 β€” Terraform (BUSL) as the provisioning engine, installed verified - -Cloud clusters are provisioned with **HashiCorp Terraform**, not OpenTofu. -BUSL 1.1 only restricts "hosted or embedded" offerings **competitive with -HashiCorp's products**; this CLI uses terraform as an internal tool to -provision the user's own infrastructure, which is not a competitive offering. -The binary is installed like every other prerequisite: a pinned version with -SHA256 verification into `~/.openframe/bin` (no curl-pipe-bash, no sudo). An -already-installed `terraform` on PATH in `~/.openframe/bin` is preferred. - -If a server-side scenario ever provisions clusters *as a service* with -terraform, that is a different BUSL use profile and needs its own review. - -## D8 β€” Local terraform state in per-cluster workspaces - -Each cloud cluster owns a workspace under `~/.openframe/clusters//`: -the generated root module, `terraform.tfvars.json`, local state, a -`terraform.log` every apply/destroy appends its output stream to, and a -`cluster.json` registry record (type, status, endpoint/CA). The registry is -what makes cloud clusters visible to `list`/`status`/`delete` without cloud -API calls, and the state file is the only pointer to billed resources β€” so a -workspace is **never deleted on a failed apply**, only after a successful -destroy. Re-running `create` resumes an interrupted apply. - -Remote state is opt-in via `--backend-config s3://bucket/prefix` (EKS) or -`gcs://bucket/prefix` (GKE) for users who need the state to survive the -machine that created the cluster. - -## Platform support - -- **macOS / Linux** β€” full support; prerequisites are checked and auto-installed. -- **Windows** β€” prerequisites are not auto-installed; the CLI prints a link to - the documentation describing what to install and how (WSL2, Docker, etc.). - -The primary audience is non-technical and semi-technical users, so every -interactive flow uses plain-language prompts, safe defaults, and confirmations -rather than raw errors. - ---- - -## Target layout - -```text -cmd/ - cluster/ create, delete, list, status, cleanup - app/ install, upgrade, status, access, uninstall - prerequisites/ check, install - bootstrap/ orchestrator (prerequisites β†’ cluster create β†’ app install) - update/ self-update: (update), check, rollback -internal/ - cluster/provider/ Provider interface + Target(local|cloud) + k3d impl - cluster/ cluster lifecycle (service + k3d provider) - chart/ helm/argocd/git providers + app-of-apps install - k8s/ cluster-access API: contexts, rest.Config, health, resources - prerequisites/ OS-aware checker/installer framework - platform/ OS detection + Windows/WSL2 doc hints - shared/ executor, errors, ui, redact, files, config, flags, - download (pinned tools), selfupdate, wsllauncher -docs/ all documentation -``` diff --git a/docs/development/README.md b/docs/development/README.md index 40286b7c..f2de9831 100644 --- a/docs/development/README.md +++ b/docs/development/README.md @@ -1,104 +1,27 @@ # Development Documentation -Welcome to the OpenFrame CLI development documentation. This section covers everything you need to contribute to, extend, and understand the internals of the `openframe` CLI. +This section covers everything you need to contribute to and work on OpenFrame CLI itself β€” the Go-based command-line tool that provisions Kubernetes clusters and deploys the OpenFrame platform. -OpenFrame CLI is written in **Go** and uses [Cobra](https://github.com/spf13/cobra) for command-line parsing. It orchestrates K3D clusters, ArgoCD GitOps deployments, and Helm chart management through a layered service/provider architecture. +> If you're looking to **use** OpenFrame CLI rather than develop it, start with the [Getting Started](../getting-started/introduction.md) documentation instead. ---- +## Contents -## Documentation Index - -| Document | Description | +| Section | Description | |---|---| -| [Environment Setup](setup/environment.md) | IDE configuration, Go toolchain, editor extensions | -| [Local Development](setup/local-development.md) | Clone, build, run, and debug the CLI locally | -| [Architecture Overview](architecture/README.md) | High-level design, component breakdown, data flows | -| [Security Guidelines](security/README.md) | Auth patterns, secret handling, vulnerability mitigations | -| [Testing Guide](testing/README.md) | Unit tests, integration tests, test utilities | -| [Contributing Guidelines](contributing/guidelines.md) | Code style, PR process, commit messages | - ---- +| [Environment Setup](setup/environment.md) | Toolchain, editor setup, and environment variables for development | +| [Local Development](setup/local-development.md) | Cloning the repo, building, running, and debugging locally | +| [Architecture](architecture/README.md) | High-level architecture, core components, and data flow | +| [Security](security/README.md) | Security patterns, secret handling, and secure-by-default practices | +| [Testing](testing/README.md) | Test structure, running tests, and coverage expectations | +| [Contributing Guidelines](contributing/guidelines.md) | Code style, branching, commit conventions, and review checklist | ## Quick Navigation -### I want to... - -**Build and run the CLI locally** -β†’ See [Local Development](setup/local-development.md) - -**Understand how the codebase is structured** -β†’ See [Architecture Overview](architecture/README.md) - -**Add a new command or feature** -β†’ Start with [Architecture Overview](architecture/README.md), then [Contributing Guidelines](contributing/guidelines.md) - -**Write or run tests** -β†’ See [Testing Guide](testing/README.md) - -**Handle secrets or security concerns** -β†’ See [Security Guidelines](security/README.md) - -**Set up my development environment** -β†’ See [Environment Setup](setup/environment.md) - ---- - -## Repository Structure - -```text -openframe-cli/ -β”œβ”€β”€ cmd/ # Cobra command definitions (entry points) -β”‚ β”œβ”€β”€ root.go # Root command, wires all subcommands -β”‚ β”œβ”€β”€ bootstrap/ # openframe bootstrap -β”‚ β”œβ”€β”€ cluster/ # openframe cluster (create/delete/list/status/cleanup) -β”‚ β”œβ”€β”€ app/ # openframe app (install/upgrade/status/access/uninstall) -β”‚ β”œβ”€β”€ prerequisites/ # openframe prerequisites (check/install) -β”‚ └── update/ # openframe update (self-update/rollback) -β”œβ”€β”€ internal/ # All internal business logic -β”‚ β”œβ”€β”€ bootstrap/ # Bootstrap service (cluster + chart orchestration) -β”‚ β”œβ”€β”€ cluster/ # Cluster service + K3D provider -β”‚ β”œβ”€β”€ chart/ # Chart services, ArgoCD/Helm/Git providers -β”‚ β”œβ”€β”€ app/ # App status and uninstall services -β”‚ β”œβ”€β”€ k8s/ # Kubernetes client utilities -β”‚ β”œβ”€β”€ platform/ # OS detection and platform hints -β”‚ β”œβ”€β”€ prerequisites/ # Prerequisite framework -β”‚ └── shared/ # Cross-cutting: executor, UI, errors, config, selfupdate -β”œβ”€β”€ tests/ -β”‚ β”œβ”€β”€ integration/ # Integration tests (requires running cluster) -β”‚ └── testutil/ # Shared test utilities and patterns -β”œβ”€β”€ scripts/ -β”‚ └── sign-binary.sh # Binary signing helper -└── main.go # Entry point -``` - ---- - -## Tech Stack - -| Technology | Role | -|---|---| -| **Go** | Primary language | -| **Cobra** | CLI framework (command/flag parsing) | -| **K3D** | Local Kubernetes cluster provider | -| **ArgoCD** | GitOps deployment engine (via client-go dynamic client) | -| **Helm** | Kubernetes package manager (CLI wrapper) | -| **go-git** | Git operations (no `git` binary dependency) | -| **client-go** | Kubernetes API client | -| **pterm** | Terminal UI rendering (spinners, prompts, colors) | -| **Sigstore/cosign** | Binary signature verification for self-updates | - ---- - -## External Dependencies - -The OpenFrame platform chart lives in a separate repository: - -- **openframe-oss-tenant:** [https://github.com/flamingo-stack/openframe-oss-tenant](https://github.com/flamingo-stack/openframe-oss-tenant) -- Documentation: [https://github.com/flamingo-stack/openframe-oss-tenant/tree/main/docs](https://github.com/flamingo-stack/openframe-oss-tenant/tree/main/docs) - ---- +- New to the codebase? Start with [Architecture](architecture/README.md) to understand how `cmd/`, `internal/cluster`, `internal/chart`, and `internal/shared` fit together. +- Setting up your machine? See [Environment Setup](setup/environment.md) and [Local Development](setup/local-development.md). +- Writing a change? Check [Testing](testing/README.md) and the [Contributing Guidelines](contributing/guidelines.md) before opening a PR. +- Touching credentials, downloads, or self-update code? Read [Security](security/README.md) first. -## Getting Help +## Project at a Glance -- **OpenMSP Slack:** [https://www.openmsp.ai/](https://www.openmsp.ai/) -- **CLI Source:** [https://github.com/flamingo-stack/openframe-cli](https://github.com/flamingo-stack/openframe-cli) +OpenFrame CLI is a Go service (module `github.com/flamingo-stack/openframe-cli`) built with [Cobra](https://github.com/spf13/cobra) for command routing and `client-go` for native Kubernetes API access. It shells out to Docker, k3d, Helm, Terraform, gcloud, and the AWS CLI via a testable `CommandExecutor` abstraction, and renders its terminal UI with `pterm`, `huh`, and `bubbletea`. diff --git a/docs/development/architecture/README.md b/docs/development/architecture/README.md index 24e3aaba..f88566a3 100644 --- a/docs/development/architecture/README.md +++ b/docs/development/architecture/README.md @@ -1,280 +1,168 @@ # Architecture Overview -OpenFrame CLI is a Go-based command-line tool with a layered architecture that cleanly separates command definitions, business logic, provider integrations, and shared infrastructure. +OpenFrame CLI is organized around three core abstractions β€” **cluster** (provisioning), **app** (platform deployment via ArgoCD), and **prerequisites** (tool verification/installation) β€” plus supporting shared infrastructure for UI, execution, and self-update. -For the full generated reference, see the [architecture reference documentation](../../reference/architecture/overview.md). - ---- - -## High-Level Design +## High-Level Architecture ```mermaid graph TB - subgraph Entry["Entry Point"] - main["main.go"] - root["cmd/root.go (Cobra)"] - end - - subgraph Commands["Command Layer (cmd/)"] - bootstrap["bootstrap"] - cluster["cluster/*"] - app["app/*"] - prereq["prerequisites"] - update["update"] + subgraph "CLI Layer (cmd/)" + Bootstrap[bootstrap] + Cluster[cluster] + App[app] + Prereq[prerequisites] + Update[update] end - subgraph Services["Service Layer (internal/)"] - bsvc["bootstrap.Service"] - csvc["cluster.ClusterService"] - chsvc["chart/services.ChartService"] - appsvc["app/status + uninstall"] - prefw["prerequisites.Runner"] - supdater["selfupdate.Updater"] + subgraph "Domain Services (internal/)" + ClusterSvc["cluster.ClusterService"] + ChartSvc["chart/services.ChartService"] + AppStatus["app/status.Service"] + AppUninstall["app/uninstall.Service"] + PrereqFw["prerequisites.Runner"] + SelfUpdate["selfupdate.Updater"] end - subgraph Providers["Provider Layer"] - k3dp["K3D Provider"] - argop["ArgoCD Manager"] - helmp["Helm Manager"] - gitp["Git Repository"] + subgraph "Providers" + K3d["cluster/providers/k3d"] + EKS["cluster/providers/eks (terraform)"] + GKE["cluster/providers/gke (terraform)"] + ArgoCD["chart/providers/argocd"] + Helm["chart/providers/helm"] + Git["chart/providers/git"] end - subgraph Shared["Shared Infrastructure"] - exec["executor.CommandExecutor"] - k8spkg["k8s (rest.Config, Accessor)"] - uipkg["shared/ui (pterm)"] - errpkg["shared/errors"] - redact["shared/redact"] - dl["download.Downloader"] + subgraph "External Systems" + Docker[(Docker)] + K8sAPI[(Kubernetes API)] + CloudAPI[(GCP / AWS APIs)] + GitHub[(GitHub Releases)] end - main --> root - root --> Commands - bootstrap --> bsvc - cluster --> csvc - app --> chsvc - app --> appsvc - prereq --> prefw - update --> supdater - - bsvc --> csvc - bsvc --> chsvc - csvc --> k3dp - chsvc --> argop - chsvc --> helmp - chsvc --> gitp - appsvc --> argop - - k3dp --> exec - helmp --> exec - argop --> k8spkg - helmp --> k8spkg - prefw --> dl - supdater --> dl - exec --> redact + Bootstrap --> ClusterSvc + Bootstrap --> ChartSvc + Cluster --> ClusterSvc + App --> ChartSvc + App --> AppStatus + App --> AppUninstall + Prereq --> PrereqFw + Update --> SelfUpdate + + ClusterSvc --> K3d + ClusterSvc --> EKS + ClusterSvc --> GKE + ChartSvc --> ArgoCD + ChartSvc --> Helm + ChartSvc --> Git + AppStatus --> ArgoCD + + K3d --> Docker + EKS --> CloudAPI + GKE --> CloudAPI + ArgoCD --> K8sAPI + Helm --> K8sAPI + SelfUpdate --> GitHub ``` ---- +The `internal/k8s` package deliberately isolates read/inspect access to an *existing* cluster (contexts, health, resources) from `internal/cluster`, which handles cluster *creation*. This lets `app install` target any reachable cluster β€” one made by `openframe cluster create`, or by the user directly. ## Core Components -| Package | Path | Responsibility | +| Component | Path | Responsibility | |---|---|---| -| **Root Command** | `cmd/root.go` | Cobra root; wires subcommands, global flags (`--verbose`, `--silent`), version info, WSL launcher | -| **Bootstrap Command** | `cmd/bootstrap/` | Orchestrates `cluster create` + `app install` as a single user-facing workflow | -| **Cluster Commands** | `cmd/cluster/` | Cobra subcommands: create, delete, list, status, cleanup | -| **App Commands** | `cmd/app/` | Cobra subcommands: install, upgrade, status, access, uninstall | -| **Prerequisites Command** | `cmd/prerequisites/` | Exposes `check` / `install` for Docker, k3d, Helm | -| **Update Command** | `cmd/update/` | Self-update, rollback, update-check with cosign signature verification | -| **Bootstrap Service** | `internal/bootstrap/` | Coordinates cluster creation then chart installation end-to-end | -| **Cluster Service** | `internal/cluster/service.go` | Lifecycle operations (create, delete, list, status, cleanup) via the provider interface | -| **K3D Provider** | `internal/cluster/providers/k3d/` | K3D-specific cluster creation and management | -| **Cluster Provider Interface** | `internal/cluster/provider/` | Unified `Provider` interface; K3D satisfies it today | -| **Chart Services** | `internal/chart/services/` | High-level install workflow: prerequisites β†’ ArgoCD β†’ app-of-apps β†’ wait | -| **ArgoCD Provider** | `internal/chart/providers/argocd/` | Install, wait, refresh/sync, application management via native client-go dynamic client | -| **Helm Provider** | `internal/chart/providers/helm/` | Helm CLI wrapper; ArgoCD and app-of-apps installation | -| **Git Provider** | `internal/chart/providers/git/` | Shallow clone of chart repository using go-git (no `git` binary) | -| **App Status Service** | `internal/app/status/` | Aggregates cluster health + ArgoCD app status into a unified Report | -| **App Uninstall Service** | `internal/app/uninstall/` | Removes ArgoCD applications and Helm releases safely | -| **k8s Package** | `internal/k8s/` | Kubeconfig context loading, `rest.Config` construction, cluster health/resource checks | -| **Prerequisites Framework** | `internal/prerequisites/` | OS-aware check + auto-install runner (macOS/Linux auto-installs, Windows shows docs) | -| **Executor** | `internal/shared/executor/` | Command execution abstraction (real + mock); records argv for security testing | -| **Self-Update** | `internal/shared/selfupdate/` | GitHub release fetch, cosign signature verification, binary swap, rollback | -| **Download** | `internal/shared/download/` | Verified binary downloads (SHA256 + pinned versions) for k3d, mkcert, Helm | -| **Redact** | `internal/shared/redact/` | Secret redaction from log/debug output | -| **WSL Launcher** | `internal/shared/wsllauncher/` | Re-runs the CLI inside WSL2 on Windows; auto-installs the Linux binary | -| **Platform** | `internal/platform/` | Host OS detection, per-tool install hints, WSL guidance errors | -| **Shared UI** | `internal/shared/ui/` | Logo, prompts, silent mode, status colors, selection menus (pterm) | -| **Shared Config** | `internal/shared/config/` | `EnvBool`, TLS config for local clusters, system service | -| **Shared Errors** | `internal/shared/errors/` | Error types, friendly hints, retry policies, `AlreadyHandledError` sentinel | - ---- +| Root command | `cmd/root.go` | Cobra root, version metadata, global flags (`--silent`, `--verbose`, `--plain`), pinned-dependency reporting | +| Bootstrap | `cmd/bootstrap/`, `internal/bootstrap/` | One-shot `cluster create` + `app install` with a staged progress tracker | +| Cluster commands | `cmd/cluster/` | `create`, `delete`, `list`, `status`, `use`, `cleanup` subcommands | +| Cluster service | `internal/cluster/service.go` | Cluster lifecycle orchestration, provider dispatch, existing-cluster reuse logic | +| Cluster providers | `internal/cluster/providers/{k3d,eks,gke}` | Backend-specific cluster create/delete/status via Docker/k3d or Terraform | +| Cluster discovery | `internal/cluster/discovery/` | Finds cloud clusters outside the openframe registry (GKE/EKS), gcloud/AWS auth flows | +| Cluster prerequisites | `internal/cluster/prerequisites/` | Type-aware tool gates (Docker/k3d/helm for k3d; terraform+CLI for EKS/GKE) | +| App commands | `cmd/app/` | `install`, `upgrade`, `status`, `access`, `uninstall` subcommands | +| Chart services | `internal/chart/services/` | Orchestrates ArgoCD + app-of-apps install, validation, retries | +| ArgoCD provider | `internal/chart/providers/argocd/` | ArgoCD Helm install, application listing/sync, admin password, wait logic | +| Helm provider | `internal/chart/providers/helm/` | Helm CLI wrapper for install/upgrade/uninstall | +| Git provider | `internal/chart/providers/git/` | Clones the app-of-apps chart repository at a given ref | +| App status | `internal/app/status/` | Aggregates cluster health + ArgoCD app sync/health into a `Report` | +| App status TUI | `internal/app/status/tui/` | Interactive k9s-style bubbletea view for navigating/syncing apps | +| App uninstall | `internal/app/uninstall/` | Removes ArgoCD applications and Helm releases, keeping the cluster | +| Prerequisites framework | `internal/prerequisites/` | OS-aware `Prerequisite`/`Set`/`Runner` abstraction (auto-install on macOS/Linux, docs-only on Windows) | +| k8s access | `internal/k8s/` | Kubeconfig context resolution, `rest.Config` building, cluster health/resource checks | +| Platform hints | `internal/platform/` | Per-OS install guidance, Windows/WSL cluster-access error messaging | +| Shared executor | `internal/shared/executor/` | `CommandExecutor` abstraction (real + mock) for all shelled-out commands | +| Shared errors | `internal/shared/errors/` | Structured error handling, retry policy, friendly hints | +| Shared UI | `internal/shared/ui/` | Logo, spinners, prompts, glyphs, GitHub Actions annotations, silent/plain modes | +| Shared download | `internal/shared/download/` | Checksum-verified pinned-tool downloads (k3d, helm, mkcert, terraform, infracost) | +| Self-update | `internal/shared/selfupdate/` | Checks/applies CLI updates, cosign signature + checksum verification, rollback | +| WSL launcher | `internal/shared/wsllauncher/` | Forwards the native Windows binary into WSL2 for cluster operations | ## Data Flow: Bootstrap Sequence -The `openframe bootstrap` command is the primary user workflow. This sequence diagram shows all the moving parts: - ```mermaid sequenceDiagram participant User - participant CLI as "openframe bootstrap" - participant BSvc as "bootstrap.Service" - participant CSvc as "cluster.Service" - participant K3D as "K3D Provider" - participant ChSvc as "chart/services" - participant Helm as "HelmManager" - participant Git as "git.Repository" - participant ArgoCD as "argocd.Manager" - participant K8s as "Kubernetes API" - - User->>CLI: openframe bootstrap [name] - CLI->>BSvc: Execute(cmd, args) - BSvc->>ChSvc: ValidateHelmValuesFile() - ChSvc-->>BSvc: OK - - BSvc->>CSvc: CreateClusterWithPrerequisites(ctx, name) - CSvc->>K3D: CreateCluster(ctx, config) - K3D-->>CSvc: rest.Config - CSvc-->>BSvc: rest.Config - - BSvc->>ChSvc: InstallChartsWithConfigContext(ctx, req) - ChSvc->>Helm: InstallArgoCDWithProgress(ctx, cfg) - Helm->>K8s: helm upgrade --install argo-cd - K8s-->>Helm: OK - - ChSvc->>Git: CloneChartRepository(ctx, appConfig) - Git-->>ChSvc: CloneResult{tempDir, chartPath} - - ChSvc->>Helm: InstallAppOfAppsFromLocal(ctx, cfg) - Helm->>K8s: helm upgrade --install app-of-apps - K8s-->>Helm: OK - - ChSvc->>ArgoCD: WaitForApplications(ctx, cfg) - loop Every 2s until ready or timeout - ArgoCD->>K8s: List Applications - K8s-->>ArgoCD: Application list - ArgoCD->>ArgoCD: assessApplications() - end - ArgoCD-->>ChSvc: All Healthy+Synced - - ChSvc-->>BSvc: OK - BSvc-->>User: Bootstrap complete + participant CLI as cmd/bootstrap + participant Boot as internal/bootstrap.Service + participant Cluster as internal/cluster.ClusterService + participant K3d as k3d provider + participant Chart as chart/services (Installer) + participant ArgoCD as ArgoCD provider + participant K8s as Kubernetes API + + User->>CLI: openframe bootstrap + CLI->>Boot: Execute(cmd, args) + Boot->>Chart: ValidateHelmValuesFile() + Boot->>Cluster: CreateCluster(config) + Cluster->>K3d: CreateCluster(ctx, config) + K3d->>K8s: provision cluster (Docker) + K3d-->>Cluster: rest.Config + Cluster-->>Boot: rest.Config + Boot->>Chart: InstallChartsWithConfigContext(req) + Chart->>ArgoCD: Install(ctx, config) + ArgoCD->>K8s: helm install argocd + Chart->>Chart: AppOfApps.Install (git clone + helm) + Chart->>ArgoCD: WaitForApplications(ctx, config) + ArgoCD->>K8s: poll Application CRs + K8s-->>ArgoCD: sync/health status + ArgoCD-->>Chart: ready + Chart-->>Boot: success + Boot-->>User: summary card (stages, timings, access hints) ``` ---- - -## Data Flow: App Install / Upgrade +## Data Flow: App Status Aggregation ```mermaid sequenceDiagram participant User - participant AppCmd as "cmd/app/install" - participant Target as "app/target.Selector" - participant K8sPkg as "k8s package" - participant ChSvc as "chart/services" - participant ArgoProv as "argocd.Manager" - participant HelmProv as "helm.HelmManager" - - User->>AppCmd: openframe app install - AppCmd->>Target: Select(ctx) - Target->>K8sPkg: LoadContexts(kubeconfigPath) - K8sPkg-->>Target: ContextInfo list - Target->>User: Prompt: select context - User-->>Target: k3d-openframe-dev - Target->>K8sPkg: CheckResources(ctx, requirements) - K8sPkg-->>Target: Resources sufficient - Target-->>AppCmd: SelectResult{Config, Context} - - AppCmd->>ChSvc: InstallChartsWithConfigContext(ctx, req) - ChSvc->>ArgoProv: Install ArgoCD - ArgoProv-->>ChSvc: ArgoCD installed - ChSvc->>HelmProv: InstallAppOfAppsFromLocal(ctx, cfg) - HelmProv-->>ChSvc: app-of-apps installed - ChSvc->>ArgoProv: WaitForApplications(ctx, cfg) - ArgoProv-->>ChSvc: All apps Healthy+Synced - ChSvc-->>AppCmd: OK - AppCmd-->>User: SUCCESS + participant CLI as cmd/app.status + participant Svc as app/status.Service + participant Accessor as k8s.Accessor + participant ArgoCDMgr as argocd.Manager + participant K8s as Kubernetes API + + User->>CLI: openframe app status --watch + CLI->>Svc: Report(ctx, verbose) + Svc->>Accessor: CheckHealth(ctx) + Accessor->>K8s: list nodes + K8s-->>Accessor: node conditions + Svc->>ArgoCDMgr: ListApplications(ctx, verbose) + ArgoCDMgr->>K8s: list Application CRs + K8s-->>ArgoCDMgr: applications + Svc->>ArgoCDMgr: AdminPassword(ctx) + ArgoCDMgr->>K8s: read argocd-initial-admin-secret + Svc-->>CLI: Report{Health, Apps, Synced, Healthy} + CLI-->>User: table + readiness summary ``` ---- - ## Key Design Decisions -### 1. Provider Interface Pattern - -The `cluster.Provider` interface allows the CLI to support multiple cluster backends (K3D today, potentially Kind or cloud providers in the future): - -```go -// internal/cluster/provider/provider.go -type Provider interface { - CreateCluster(ctx context.Context, cfg models.ClusterConfig) (*rest.Config, error) - DeleteCluster(ctx context.Context, name string, clusterType models.ClusterType, force bool) error - ListClusters(ctx context.Context) ([]models.ClusterInfo, error) - GetClusterStatus(ctx context.Context, name string) (*models.ClusterStatus, error) -} -``` - -### 2. CommandExecutor Abstraction - -All external binary invocations (k3d, helm) go through the `CommandExecutor` interface, enabling complete mock substitution in unit tests: - -```go -// Real execution -exec := executor.NewRealCommandExecutor(false, true) -result, err := exec.Execute(ctx, "k3d", "cluster", "list") - -// Test mock -mock := executor.MockCommandExecutor{} -mock.SetResponse("k3d cluster list", &executor.CommandResult{Stdout: `[]`}) -``` - -### 3. GitOps via ArgoCD App-of-Apps - -Platform deployment uses the ArgoCD [App of Apps pattern](https://argo-cd.readthedocs.io/en/stable/operator-manual/cluster-bootstrapping/). The CLI installs a single "app-of-apps" Helm chart that ArgoCD then uses to deploy and manage all child applications from the `openframe-oss-tenant` repository. - -### 4. Secret Redaction at the Executor Layer - -All output from external commands passes through `redact.Redact()` before being displayed or logged. Secrets registered via `redact.RegisterSecret()` and URL-embedded credentials are automatically scrubbed with `***`. - -### 5. AlreadyHandledError Sentinel - -To avoid double-printing errors, the `AlreadyHandledError` sentinel is used throughout the codebase. When a command has already displayed its error to the user, it wraps the error as `AlreadyHandledError` β€” the main entry point then silently exits with the appropriate code. - -### 6. Interactive + Non-Interactive Modes - -Every wizard checks `ui.IsNonInteractive()` before prompting. Non-interactive mode is triggered by `--non-interactive`, piped stdin, or `--output json/yaml`. This makes every command safe for CI/CD pipelines without special handling. - ---- - -## Configuration File: openframe-helm-values.yaml - -The bootstrap wizard generates a `openframe-helm-values.yaml` configuration file. Before any cluster creation, the CLI validates this file via a "preflight" check β€” the cheapest possible gate to catch errors before expensive cluster operations begin. - -```mermaid -graph LR - A["User runs bootstrap"] --> B["Validate openframe-helm-values.yaml"] - B --> C{"Valid?"} - C -->|Yes| D["Create K3D cluster"] - C -->|No| E["Error: fix your values file"] - D --> F["Install ArgoCD"] - F --> G["Deploy app-of-apps"] - G --> H["Wait for healthy"] -``` - ---- - -## Upgrade Modes - -The `openframe app upgrade` command has two distinct modes: - -| Mode | Flag | Description | -|---|---|---| -| **Change-Ref (Mode 1)** | `--ref ` | Updates the git ref in ArgoCD, triggers re-sync to new version | -| **Force-Sync (Mode 2)** | `--force-sync` | Forces ArgoCD to re-sync the current ref without changing the version | - ---- +- **Two separate cluster concerns.** `internal/cluster` (creation/provisioning) is kept distinct from `internal/k8s` (read/inspect access to an already-reachable cluster), so `app install`/`app status` can target any cluster regardless of who created it. +- **Provider abstraction over cluster backends.** `internal/cluster/provider` defines unified `Provider`/`Planner` interfaces so k3d (Docker-based), EKS, and GKE (both Terraform-based) can be dispatched uniformly from `ClusterService`. +- **Everything shells out through one executor.** All external tool invocations (Docker, k3d, Helm, Terraform, gcloud, aws) go through `internal/shared/executor.CommandExecutor`, which has a real implementation and a `MockCommandExecutor` for tests β€” enabling fully offline unit testing of orchestration logic. +- **No unverified downloads.** Prerequisite tool binaries (k3d, Helm, mkcert, Terraform, infracost) are fetched via `internal/shared/download`, which pins exact versions and verifies SHA256 checksums before atomically installing β€” replacing unsafe `curl | bash` patterns. +- **OS-aware prerequisite handling.** `internal/prerequisites.Runner` auto-installs missing tools on macOS/Linux but only prints documentation links on Windows, where Docker/k3d-based cluster operations are instead forwarded into WSL2 via `internal/shared/wsllauncher`. +- **Native Kubernetes API access.** The CLI uses `client-go` directly (`internal/k8s`) rather than shelling out to `kubectl`, giving structured error handling and avoiding a `kubectl` dependency for read/status operations. +- **Interactive and automatable by design.** Every workflow that has an interactive wizard (`huh`-based prompts) also has an equivalent set of non-interactive flags (`--skip-wizard`, `--non-interactive`) so the same commands work in CI/CD. -## Further Reading +## Dependencies -- [Reference Architecture Documentation](../../reference/architecture/overview.md) β€” Full generated documentation with all component details -- [openframe-oss-tenant](https://github.com/flamingo-stack/openframe-oss-tenant) β€” The external OpenFrame platform chart repository +OpenFrame CLI is a **service** published to the `go` ecosystem as `github.com/flamingo-stack/openframe-cli`. Per the ecosystem graph, it has no recorded upstream dependencies on other repositories in this organization, and no recorded downstream consumers β€” it is a leaf/terminal artifact in the internal dependency graph. Its functional dependencies are external, third-party Go modules (Cobra, client-go, pterm, huh, bubbletea, sigstore-go) and external CLI tools invoked via the shared executor (Docker, k3d, Helm, Terraform, gcloud, aws). diff --git a/docs/development/security/README.md b/docs/development/security/README.md index ef317b71..4ed0ef35 100644 --- a/docs/development/security/README.md +++ b/docs/development/security/README.md @@ -1,250 +1,87 @@ -# Security Guidelines +# Security Best Practices -This document describes the security patterns, practices, and mitigations built into the OpenFrame CLI, along with guidelines for contributors to maintain these standards. +OpenFrame CLI is a privileged tool: it shells out to Docker, cloud CLIs, and Terraform, downloads and executes third-party binaries, and handles ArgoCD admin credentials. This page covers the security patterns already established in the codebase and what to follow when extending it. ---- +## Authentication and Authorization Patterns -## Authentication and Authorization +OpenFrame CLI does not implement its own authentication system β€” it delegates to the credential/context mechanisms of the tools and clusters it orchestrates: -### Kubernetes Authentication +- **Kubernetes access** is resolved through kubeconfig contexts (`internal/k8s/contexts.go`, `internal/k8s/restconfig.go`), either the current context or an explicit `--context` flag. There are no OpenFrame-specific credentials stored for cluster access. +- **ArgoCD admin credentials** are read directly from the cluster's `argocd-initial-admin-secret` (via `internal/chart/providers/argocd`) β€” never generated, stored, or transmitted by the CLI itself. `openframe app access` reads and displays them; it does not create them. +- **Cloud provider authentication** (AWS/GCP) reuses the user's existing AWS CLI / gcloud CLI configuration and credential chain; the CLI does not manage its own cloud credentials. +- **CLI self-update authenticity** is verified via Sigstore/cosign keyless signatures (`internal/shared/selfupdate/cosign.go`). The OIDC issuer is pinned to `https://token.actions.githubusercontent.com` and the certificate SAN is pinned to this repository's `release.yml` workflow β€” signatures from any other repository, workflow, or issuer are rejected. -The CLI authenticates to Kubernetes clusters using standard kubeconfig files. The `internal/k8s` package handles context loading and `rest.Config` construction: +## Secure Handling of Credentials and Secrets -```go -// Contexts are loaded from the standard kubeconfig path (~/.kube/config) -// or from the KUBECONFIG environment variable. -// rest.Config is constructed per-operation, not stored globally. -``` - -**Guidelines:** -- Never hardcode kubeconfig paths β€” always resolve via `clientcmd.BuildConfigFromFlags` -- Use the `Accessor` type for cluster health checks rather than raw API calls -- Always pass `rest.Config` through function arguments, not global variables - -### GitHub API Authentication - -The self-update and download subsystems authenticate with GitHub using tokens: +- **Never print secrets in the clear where avoidable.** `internal/shared/redact` provides `RegisterSecret()`/`Redact()` to scrub known sensitive values (and any `user:pass@` URL-embedded credentials) from log/debug output before it reaches the terminal. +- **ArgoCD passwords are redacted in error/debug paths.** When adding new logging around ArgoCD or Helm operations that might include secret values, register those values with `redact.RegisterSecret()` first. +- **No secrets are persisted to disk by the CLI.** Kubeconfig and cloud CLI credential files are managed by their respective tools (kubectl, aws, gcloud), not by OpenFrame CLI. -| Variable | Priority | Description | -|---|---|---| -| `OPENFRAME_GITHUB_TOKEN` | High | OpenFrame-specific token (takes precedence) | -| `GITHUB_TOKEN` | Standard | Standard GitHub Actions token | +> When writing new code that handles a password, token, or API key, register it with `internal/shared/redact.RegisterSecret()` immediately after it's obtained, before it can appear in any log line, error message, or `--verbose` output. -**Guidelines:** -- Never log tokens, even at debug level β€” they are registered with `redact.RegisterSecret()` at startup -- Always pass tokens through environment variables, never as command-line arguments (visible in `ps` output) +## Verified, Integrity-Checked Downloads -### ArgoCD Authentication +A core security principle in this codebase: **no unsafe `curl | bash` style installs.** -ArgoCD is managed via the native Kubernetes dynamic client (client-go) rather than the ArgoCD HTTP API. This means: -- No ArgoCD API tokens are ever stored or transmitted -- All operations go through Kubernetes RBAC via the kubeconfig credentials -- The CLI never calls ArgoCD's REST API directly - ---- - -## Secret Redaction - -All potentially sensitive values must be registered with the `redact` package before any logging or command execution: - -```go -import "github.com/flamingo-stack/openframe-cli/internal/shared/redact" - -// Register a secret for automatic scrubbing -redact.RegisterSecret(githubToken) -redact.RegisterSecret(registryPassword) - -// All log output and command strings are automatically scrubbed -// redact.Redact("helm upgrade --set auth.token=mysecret") -// β†’ "helm upgrade --set auth.token=***" -``` - -**Key behaviors:** -- Longer secrets are replaced before shorter ones to prevent partial unmasking -- URL-embedded credentials (`user:pass@host`) are scrubbed unconditionally without explicit registration -- The redaction is thread-safe via `sync.RWMutex` -- In tests, call `redact.ClearSecrets()` in teardown to prevent cross-test contamination - -**Contribution rule:** Any value read from environment variables, configuration files, or user prompts that could be a credential **must** be passed through `redact.RegisterSecret()` before being used in any executor call or log statement. - ---- +- `internal/shared/download` (`verify.go`) implements `Downloader.FetchVerified`/`InstallVerified*`, which: + - Pins every prerequisite tool (k3d, Helm, mkcert, Terraform, infracost) to an exact version and per-platform SHA256 checksum (`PinnedTool`/`PinnedAsset`). + - Verifies `sha256(data)` against the expected digest before any bytes are trusted. + - Writes files atomically (temp file + rename) so a failed/interrupted download never leaves a partial or unverified binary in place. + - Caps downloads (512 MiB) and archive member extraction (200 MiB) to guard against decompression bombs. +- This pattern replaced earlier `curl | bash`-style installers as part of a security audit (see notes in the k3d installer). **Any new prerequisite installer should use this same verified-download path**, not a raw shell pipe to an installer script. +- CLI self-updates go through the same discipline, plus cosign signature verification (see above) β€” a checksum match alone is not sufficient for self-update binaries. ## Input Validation and Sanitization -### Cluster Name Validation +- **Cluster names are validated** before reaching any shell-out (`internal/cluster/models.ValidateClusterName`), enforcing DNS-1123-like rules (max 63 chars, alphanumeric/hyphen, must start/end alphanumeric). This is enforced consistently at command boundaries (e.g., `bootstrap`, `cluster create`) so unsafe input never reaches downstream tool invocations. +- **Flag combinations are validated up front** (`internal/cluster/models.ValidateCreateFlags` and friends) β€” e.g., rejecting `--project` with `--type eks`, or requiring `--region`/`--project` for GKE when `--skip-wizard` is set β€” so ambiguous or cross-provider flag combinations are rejected before any cloud resources are touched. +- **All external commands use structured argv, not shell strings.** The `CommandExecutor` abstraction (`internal/shared/executor`) executes commands with discrete argument slices rather than interpolating user input into a shell string, which prevents shell-injection via crafted flag values. -Cluster names are validated against RFC1123 rules at the command boundary before reaching any shell-out: +## Testing for Shell-Injection and Command Safety -```go -// Validation is enforced in cmd/bootstrap/bootstrap.go and cmd/cluster/create.go -// before any subprocess execution β€” prevents injection via cluster names -if err := clustermodels.ValidateClusterName(name); err != nil { - return err -} -``` - -This ensures that a cluster name like `; rm -rf /` cannot reach the k3d subprocess. - -### Helm Values Validation - -The `openframe-helm-values.yaml` file is validated via a "preflight" check **before** cluster creation β€” the cheapest gate in the pipeline: +The test mock executor (`internal/shared/executor.MockCommandExecutor`) exposes `Commands()`, returning structured `RecordedCommand{Args, Env, Stdin}` records specifically so tests can assert no shell metacharacters (e.g., `$(...)`) were ever passed as a literal argument: ```go -// internal/chart/services/preflight.go -if err := services.ValidateHelmValuesFile(); err != nil { - // Fails fast before any expensive cluster operations - return err +for _, cmd := range exec.Commands() { + for _, arg := range cmd.Args { + if strings.Contains(arg, "$(") { + t.Errorf("shell injection in argv: %q", arg) + } + } } ``` -**Guidelines:** -- All user-supplied YAML/flag values must be validated before being passed to external processes -- Use structured types with validation tags rather than raw string interpolation into shell commands -- Never construct shell commands via string concatenation β€” use argv arrays via the `CommandExecutor` interface - -### Command Injection Prevention - -The `CommandExecutor` interface uses `os/exec` with argv arrays (not shell invocation): - -```go -// SAFE: argv array β€” no shell injection possible -result, err := exec.Execute(ctx, "k3d", "cluster", "list", "--output", "json") - -// NEVER do this β€” shell injection risk: -// exec.Execute(ctx, "sh", "-c", "k3d cluster list --output " + userInput) -``` - -**Contribution rule:** Never pass user input to `sh -c` or any shell interpreter. Always use direct `os/exec` with separate argument lists. - ---- - -## Self-Update Security - -The self-update mechanism uses [Sigstore/cosign](https://docs.sigstore.dev/cosign/overview/) for supply chain security: - -```mermaid -graph LR - A["openframe update"] --> B["Fetch latest release from GitHub"] - B --> C["Download checksums.txt + bundle.json"] - C --> D["Verify cosign signature"] - D --> E{"Signature valid?"} - E -->|Yes| F["Download binary archive"] - E -->|No| G["REJECT β€” abort update"] - F --> H["Verify SHA256 checksum"] - H --> I["Smoke-test new binary"] - I --> J["Atomic binary swap"] - J --> K[".bak rollback saved"] -``` - -**Pinned identity checks:** -- OIDC Issuer: `https://token.actions.githubusercontent.com` (GitHub Actions only) -- SAN Regex: Matches only `flamingo-stack/openframe-cli`'s `release.yml` workflow on `main` or tag refs -- Signatures from any other repository, workflow, or issuer are **rejected** - -**Emergency escape hatch** (for testing/development only β€” never in production): - -```bash -export OPENFRAME_UPDATE_INSECURE_SKIP_VERIFY=1 -``` - -> **Warning:** Setting `OPENFRAME_UPDATE_INSECURE_SKIP_VERIFY=1` disables all cryptographic verification. Only use this in isolated development environments. - ---- - -## Binary Download Security - -All binary downloads (k3d, mkcert, Helm) use pinned versions and SHA256 checksum verification: - -```go -// internal/shared/download/pins.go -// Each tool has a pinned version and expected SHA256 checksum -// Downloads are rejected if the checksum doesn't match -``` - -**Guidelines:** -- Never download binaries without checksum verification -- Pin versions explicitly β€” never download "latest" without verification -- Use HTTPS for all downloads - ---- - -## WSL Security Considerations - -On Windows, the CLI forwards execution into WSL2: - -```go -// Only forward if ShouldForward() returns true -// ShouldForward() returns false if: -// - running on Linux (prevents infinite recursion) -// - OPENFRAME_NO_WSL_FORWARD=1 is set -if wsllauncher.ShouldForward() { - code, err := wsllauncher.Forward(version, os.Args[1:]) - os.Exit(code) -} -``` - -Environment variables `GITHUB_TOKEN` and `OPENFRAME_GITHUB_TOKEN` are forwarded into WSL via `WSLENV` β€” ensure these are not set to high-privilege tokens in shared environments. - ---- - -## Environment Variables and Secrets Management - -### Principles - -1. **Never log secrets** β€” Register all credentials with `redact.RegisterSecret()` immediately on ingestion -2. **Never pass secrets as CLI flags** β€” Flags appear in process lists (`ps aux`). Use environment variables -3. **Never embed secrets in source code** β€” Use environment variables or external secret managers -4. **Rotate regularly** β€” GitHub tokens used for `OPENFRAME_GITHUB_TOKEN` should be scoped to the minimum required permissions - -### Recommended Token Scopes - -For `OPENFRAME_GITHUB_TOKEN` / `GITHUB_TOKEN`: - -| Scope | Required? | Reason | -|---|---|---| -| `read:packages` | Optional | Accessing private container images | -| `repo` (public read) | No | Public repos are accessible without auth | -| No special scopes | Sufficient | For rate-limit bypass only (public repos) | - ---- +> Prefer `Commands()` (structured argv) over `GetExecutedCommands()` (flattened strings) whenever a test's purpose is a security assertion β€” a flattened log cannot distinguish a literal `$(x)` argument from a shell-constructed string. ## Common Vulnerabilities and Mitigations -| Vulnerability | Mitigation | +| Risk | Mitigation in this codebase | |---|---| -| **Command injection** | `os/exec` with argv arrays; cluster name RFC1123 validation | -| **Secret leakage in logs** | `redact` package with automatic URL credential scrubbing | -| **Malicious update binary** | Cosign signature verification against pinned GitHub Actions identity | -| **Checksum bypass** | SHA256 verification before any binary execution | -| **Stale kubeconfig** | Context validated before use; `Accessor.Reachable()` check | -| **YAML injection** | Structured Helm values parsing, not raw string interpolation | -| **Token exposure in env** | Tokens forwarded via `WSLENV` mechanism, not command arguments | +| Unsafe binary installs (`curl \| bash`) | Replaced with pinned-version, SHA256-verified downloads (`internal/shared/download`) | +| Tampered/malicious self-update binary | Sigstore/cosign signature verification pinned to this repo's release workflow (`internal/shared/selfupdate/cosign.go`) | +| Shell injection via cluster names/flags | Strict cluster-name validation + structured argv execution (no shell string interpolation) | +| Secrets leaking into logs/`--verbose` output | Centralized `internal/shared/redact` registration/scrubbing | +| Decompression bombs from downloaded archives | Size-capped extraction (200 MiB) in `internal/shared/download` | +| Partial/corrupted files from interrupted downloads | Atomic write (temp file + rename) in `writeFileAtomic` | ---- +## Emergency Escape Hatches (Use With Caution) -## Security Testing +`OPENFRAME_UPDATE_INSECURE_SKIP_VERIFY=1` bypasses cosign signature verification during self-update. This exists only as an emergency fallback (e.g., trust-root fetch outage) β€” it must never be recommended in normal documentation, scripts, or defaults, and should not be set in CI pipelines that fetch untrusted releases. -The `MockCommandExecutor` records all argv arrays for security assertions: +## Secrets and Environment Variables in CI/Release -```go -mock := executor.MockCommandExecutor{} -// After execution: -calls := mock.RecordedCalls() -for _, call := range calls { - // Assert no user input leaked into command args without validation - assert.NotContains(t, call.Args, userInput) -} -``` +The release signing pipeline (`scripts/sign-binary.sh`) is a good reference for how secrets should be handled in automation: -**Security test checklist for new commands:** -- [ ] User-supplied cluster names are validated via `ValidateClusterName` -- [ ] Any new credential/token is registered with `redact.RegisterSecret()` -- [ ] External commands use argv arrays, not shell strings -- [ ] New YAML/JSON input is validated via structured types before use -- [ ] Sensitive flags are not printed in error messages +- It is a strict no-op unless `OPENFRAME_SIGN=1` is explicitly set by the release workflow β€” local builds and CI compile checks never attempt to sign or touch signing secrets. +- macOS signing/notarization and Windows Authenticode signing (Azure Trusted Signing) both require their credentials (Apple ID/team, Azure tenant/client secret) to be provided as environment variables sourced from the CI secret store β€” never hard-coded, and each `: "${VAR:?}"` guard fails fast if a required secret is missing rather than silently proceeding. +- Tokens (e.g., the Azure AAD token) are fetched fresh per invocation rather than cached long-lived, limiting the blast radius of a leaked token. ---- +## Code Review Guidelines -## Reporting Security Issues +When reviewing changes touching security-sensitive areas, confirm: -Please report security vulnerabilities via the [OpenMSP Slack community](https://www.openmsp.ai/) using a direct message to the maintainers rather than public channels. Do not open public GitHub issues for security vulnerabilities. +- [ ] New secrets/credentials are registered with `redact.RegisterSecret()` before any logging path can reach them. +- [ ] New external tool installers use `internal/shared/download`'s verified-download path, not a raw shell pipe. +- [ ] New shelled-out commands go through `CommandExecutor` with argv slices, not interpolated shell strings. +- [ ] New user-supplied identifiers (names, refs, paths) are validated before being passed to any external command. +- [ ] Nothing writes cloud or cluster credentials to disk outside of the standard tool-managed locations (kubeconfig, AWS/gcloud config). diff --git a/docs/development/setup/environment.md b/docs/development/setup/environment.md index bc3dd5d4..ecedd7e8 100644 --- a/docs/development/setup/environment.md +++ b/docs/development/setup/environment.md @@ -1,224 +1,49 @@ # Development Environment Setup -This guide covers setting up a development environment for contributing to the OpenFrame CLI. +This page covers the tools and editor setup recommended for working on OpenFrame CLI. ---- +## Required Development Tools -## Required Tools +| Tool | Purpose | +|---|---| +| Go | Primary language toolchain β€” the entire CLI is a Go module (`github.com/flamingo-stack/openframe-cli`) | +| Git | Source control; also required at runtime by `internal/chart/providers/git` to clone the app-of-apps chart repo | +| Docker | Required to exercise `k3d`-backed cluster code paths locally | +| k3d | Required to run/test local cluster provisioning end-to-end | +| Helm | Required to run/test ArgoCD and app-of-apps install/upgrade code paths | +| Terraform (>= 1.15.0) | Required to exercise EKS/GKE provider code paths | -| Tool | Version | Purpose | -|---|---|---| -| **Go** | 1.21+ | Primary language runtime and toolchain | -| **Git** | 2.30+ | Version control | -| **Docker** | 24.x+ | Container runtime (required for integration tests) | -| **k3d** | 5.x+ | Local Kubernetes clusters (integration tests) | -| **Helm** | 3.x+ | Kubernetes package manager (integration tests) | -| **Make** | Any | Build automation (if Makefile is present) | +> You can install most of these automatically using the CLI's own tooling once you have a first build: `openframe prerequisites install --type k3d` (or `eks`/`gke`). ---- +## Recommended IDE Setup -## Installing Go +Any editor with solid Go tooling works well for this codebase. Recommended setup: -### macOS +- **VS Code** with the official Go extension (`golang.go`) β€” provides `gopls`-powered autocomplete, go-to-definition, and inline test running. +- **GoLand / IntelliJ with the Go plugin** β€” strong refactoring and debugging support for larger Go codebases. -```bash -# Using Homebrew -brew install go - -# Verify -go version -``` - -### Linux - -```bash -# Download the latest Go release -curl -OL https://go.dev/dl/go1.22.0.linux-amd64.tar.gz -sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz - -# Add to PATH (add to ~/.bashrc or ~/.zshrc) -export PATH=$PATH:/usr/local/go/bin - -# Verify -go version -``` - -### Windows (WSL2) - -Follow the Linux instructions inside your WSL2 terminal. - ---- - -## IDE Recommendations - -### Visual Studio Code (Recommended) - -VS Code with the Go extension provides the best development experience for this project. - -**Install the Go extension:** - -```bash -code --install-extension golang.go -``` - -**Recommended VS Code extensions:** - -| Extension | ID | Purpose | -|---|---|---| -| Go | `golang.go` | Go language support, debugging, testing | -| GitLens | `eamodio.gitlens` | Enhanced Git integration | -| YAML | `redhat.vscode-yaml` | YAML editing for Helm values | -| Docker | `ms-azuretools.vscode-docker` | Docker integration | -| Markdown All in One | `yzhang.markdown-all-in-one` | Documentation editing | - -**Recommended `settings.json` for Go development:** - -```json -{ - "go.useLanguageServer": true, - "go.lintTool": "golangci-lint", - "go.lintOnSave": "package", - "go.formatTool": "goimports", - "go.testFlags": ["-v", "-race"], - "[go]": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" - } - } -} -``` - -### GoLand (JetBrains) - -GoLand offers excellent Go support with built-in refactoring tools. No additional plugins required β€” all Go features are built in. - -### Neovim / Vim - -Use `gopls` (Go language server) via `nvim-lspconfig`: - -```bash -go install golang.org/x/tools/gopls@latest -``` - ---- - -## Go Environment Configuration - -### Verify GOPATH and module mode - -```bash -go env GOPATH -go env GOMODCACHE -go env GOFLAGS -``` - -The project uses Go modules (`go.mod`), so `GOFLAGS` should not set `-mod=vendor` unless you're working with a vendor directory. - -### Configure GOPRIVATE (if needed) - -If your environment restricts access to the Flamingo private modules, configure: - -```bash -go env -w GOPRIVATE=github.com/flamingo-stack -``` - ---- - -## Linting and Code Quality Tools - -Install the Go linting toolchain used in the project: - -```bash -# golangci-lint (recommended) -curl -sSfL https://raw.githubusercontent.com/golangci-lint/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin - -# goimports (import management + formatting) -go install golang.org/x/tools/cmd/goimports@latest - -# staticcheck (additional static analysis) -go install honnef.co/go/tools/cmd/staticcheck@latest -``` +Useful editor features/extensions for this project: -Verify: - -```bash -golangci-lint --version -goimports -h -staticcheck -version -``` - ---- +- Go language server (`gopls`) for navigation across the many `internal/` packages (`cluster`, `chart`, `shared`, `k8s`). +- `gofmt`/`goimports` on save, to match the existing formatting conventions. +- A Cobra/CLI-aware snippet or outline view helps when navigating the many `cmd/*` subcommand files. ## Environment Variables for Development -| Variable | Description | Example | -|---|---|---| -| `OPENFRAME_GITHUB_TOKEN` | GitHub token for API calls (avoids rate limits) | Your personal access token | -| `GOFLAGS` | Go build flags | `-v` for verbose builds | -| `OPENFRAME_UPDATE_INSECURE_SKIP_VERIFY` | Skip cosign verification (dev/testing only) | `1` | - -Set these in your shell profile (`~/.bashrc`, `~/.zshrc`, etc.): - -```bash -export OPENFRAME_GITHUB_TOKEN="your-github-token-here" -``` - ---- +| Variable | Purpose | +|---|---| +| `OPENFRAME_UPDATE_INSECURE_SKIP_VERIFY` | Bypasses cosign signature verification in `internal/shared/selfupdate` β€” useful only when testing the self-update flow against unsigned local builds | +| `OPENFRAME_AUTO_UPDATE` | Set to `1` to exercise the opt-in automatic update-check code path | -## Pre-Commit Hooks (Optional) - -Setting up pre-commit hooks ensures code quality before every commit: - -```bash -# Install pre-commit -pip install pre-commit -# or: brew install pre-commit - -# Install hooks (from repo root) -pre-commit install -``` - -Alternatively, add a manual hook to `.git/hooks/pre-commit`: - -```bash -#!/bin/sh -set -e -go vet ./... -goimports -l . -``` - ---- +Beyond these, the CLI reads standard cloud provider environment/config (AWS CLI config/credentials, gcloud CLI config) when exercising EKS/GKE provider code β€” no OpenFrame-specific cloud credentials are required. ## Verifying Your Setup -Run these commands from the repository root to confirm your environment is ready: +Once your toolchain is installed, confirm the module builds and its own prerequisite checks pass: ```bash -# Verify Go version -go version - -# Download dependencies -go mod download - -# Verify all dependencies resolve -go mod verify - -# Build the binary -go build -o openframe . - -# Run unit tests -go test ./... - -# Run vet +go build ./... go vet ./... ``` -A successful run of all the above indicates a correctly configured development environment. - ---- - -## Next Steps - -- Follow the [Local Development Guide](local-development.md) to clone, build, and run the CLI -- Review the [Architecture Overview](../architecture/README.md) to understand the codebase +Continue to [Local Development](local-development.md) for clone, build, and run instructions. diff --git a/docs/development/setup/local-development.md b/docs/development/setup/local-development.md index 4fb3b714..d383cd0e 100644 --- a/docs/development/setup/local-development.md +++ b/docs/development/setup/local-development.md @@ -1,216 +1,79 @@ -# Local Development Guide +# Local Development -Clone, build, run, test, and debug OpenFrame CLI locally. - -## Prerequisites - -- **[Environment Setup](environment.md)** - Go toolchain, editor, and Kubernetes tools +This guide walks through cloning, building, running, and debugging OpenFrame CLI locally. ## Clone the Repository -Fork on GitHub (recommended for contributors), then: - -```bash -git clone https://github.com/YOUR-USERNAME/openframe-cli.git -cd openframe-cli -git remote add upstream https://github.com/flamingo-stack/openframe-cli.git -``` - -Or clone directly for read-only use: - ```bash git clone https://github.com/flamingo-stack/openframe-cli.git cd openframe-cli ``` -## Project Structure - -```text -openframe-cli/ -β”œβ”€β”€ main.go # Entry point -β”œβ”€β”€ Makefile # build / test / lint targets -β”œβ”€β”€ cmd/ # Command definitions: bootstrap, cluster, app, prerequisites, update, root.go -β”œβ”€β”€ internal/ # Private packages: bootstrap, cluster, chart, app, k8s, platform, prerequisites, shared -β”œβ”€β”€ tests/ # integration/ and testutil/ -└── docs/ # Documentation -``` - -Unit tests are colocated as `*_test.go` next to the code they cover. +## Build -## Build and Run +The CLI's entry point is `main.go` at the repository root (`package main`), which calls `cmd.Execute()`. ```bash -# Build for your current platform (produces openframe--) -make build - -# Cross-compile all six release platforms (matches .goreleaser.yml) -make build-all +# Build a local binary +go build -o build/openframe . -# Or build directly -go build -o openframe . -./openframe --version -``` - -Run without building during development: - -```bash +# Or run directly without a persistent binary go run . --help -go run . cluster status -go run . app status ``` -## Run Tests +Version metadata (`version`, `commit`, `date`) is normally injected at release build time via `-ldflags -X`. For local dev builds without ldflags, `cmd.resolveVersionInfo` falls back to Go's embedded VCS build info, so `openframe --version` still reports a real commit/date instead of placeholder values. -```bash -make test # unit + integration -make test-unit # ./cmd/... ./internal/... -make test-race # unit tests with the race detector (needs CGO) -make test-integration # ./tests/integration/... - -# Or with go directly -go test ./... -go test -run TestClusterCreate ./internal/cluster/... -go test -cover ./... -``` +## Running Locally -Integration tests may require a running cluster: +Once built, run subcommands exactly as an end user would: ```bash -k3d cluster create openframe-test -go test ./tests/integration/... -k3d cluster delete openframe-test +./build/openframe --help +./build/openframe prerequisites check +./build/openframe cluster create --skip-wizard +./build/openframe bootstrap --non-interactive ``` -## Lint and Format +Use `--verbose` on any command to see detailed logs, including shelled-out command output (e.g., ArgoCD sync progress, Terraform plan output): ```bash -make fmt # gofmt -w over the tree -make vet # go vet ./... -make lint # golangci-lint run ./... -make tidy # fail if `go mod tidy` would change go.mod/go.sum +./build/openframe cluster create --verbose ``` -These mirror the CI gates β€” run them before pushing. - -## Development Workflow - -```bash -# Sync with upstream -git fetch upstream && git checkout main && git merge upstream/main +Use `--silent` to suppress non-essential UI (spinners, logo) or `--plain` for non-ANSI output suitable for logs/CI. -# Create a branch, make changes, then before committing: -make fmt vet tidy -make test -make lint +## Iterating Quickly -# Commit (conventional commits) and push -git commit -m "feat(cluster): add support for custom node labels" -git push origin feature/your-feature-name -``` - -## Debugging - -### VS Code - -Use the launch configurations from [Environment Setup](environment.md), set breakpoints, and press F5. - -### Delve +For fast iteration without constantly re-running `go build`, use `go run`: ```bash -go install github.com/go-delve/delve/cmd/dlv@latest - -dlv debug . -- bootstrap --verbose --non-interactive -dlv test ./internal/bootstrap/ +go run . cluster status ``` -## Manually Testing Your Changes +The integration test harness (`tests/integration/common`) builds the binary once into `build/openframe` and skips rebuilds when the binary is newer than `main.go` β€” the same pattern works well for manual iteration: rebuild only when source changes. -The CLI's top-level commands are `bootstrap`, `cluster`, `app`, `prerequisites`, and `update`. +## Debugging -```bash -# Prerequisites -go run . prerequisites check - -# Cluster lifecycle -go run . cluster create test-cluster -go run . cluster status test-cluster -go run . cluster list -go run . cluster delete test-cluster - -# App-of-apps: clones openframe-oss-tenant and installs ArgoCD + the app-of-apps chart. -# --non-interactive reuses the existing openframe-helm-values.yaml. -go run . app install --non-interactive -go run . app status -go run . app access - -# Full bootstrap (cluster + app-of-apps) -go run . bootstrap --non-interactive -``` +Since this is a standard Go CLI built with Cobra, you can debug it with any Go-compatible debugger: -Verify against a real cluster: +- **Delve** (`dlv`): ```bash -kubectl get pods --all-namespaces -kubectl get applications -n argocd +dlv debug . -- cluster create --skip-wizard --verbose ``` -### Overriding ArgoCD chart values +- **VS Code**: create a `launch.json` configuration of type `go`, with `program` set to the repository root and `args` set to your desired subcommand (e.g., `["app", "status", "--verbose"]`). -The CLI installs ArgoCD from a built-in baseline (embedded -`internal/chart/providers/argocd/argocd-values.yaml`), which is separate from -the app-of-apps values. To change an ArgoCD chart value without rebuilding the -CLI, add a top-level `argocd:` section to `openframe-helm-values.yaml`: - -```yaml -# openframe-helm-values.yaml -repository: - branch: main # (app-of-apps settings, as before) - -argocd: # deep-merged over the built-in ArgoCD baseline - dex: - enabled: true # e.g. re-enable dex (disabled by default) - server: - replicas: 2 -``` +Because most external interactions (Docker, k3d, Helm, Terraform, cloud CLIs) go through the `internal/shared/executor.CommandExecutor` abstraction, you can also write unit tests against a `MockCommandExecutor` to reproduce a failure without needing a real cluster β€” see the [Testing](../testing/README.md) guide. -Only the `argocd:` subtree is applied to the ArgoCD install β€” the rest of the -file targets the app-of-apps chart, and keeping them separate stops secrets -(e.g. the docker registry password) from leaking into the ArgoCD release. The -merge follows Helm semantics (maps merge, scalars/lists replace), and the CLI -prints a warning listing the keys you overrode, since a bad override can break -the ArgoCD install. Without an `argocd:` section the baseline is used unchanged. +## Working Without a Real Cluster -## Cross-platform Builds +Many code paths can be exercised without a live cluster or cloud account by using the test utilities in `tests/testutil` (e.g., `CreateStandardTestFlags()`, which wires up a `MockCommandExecutor` with canned k3d responses). This is the fastest way to iterate on flag parsing, validation, and orchestration logic. -`make build` puts the current-platform binary into `build/`; `make build-all` -cross-compiles every release platform there. By hand: +For true end-to-end verification, run against a real local cluster: ```bash -GOOS=linux GOARCH=amd64 go build -o build/openframe-linux-amd64 . -GOOS=darwin GOARCH=arm64 go build -o build/openframe-darwin-arm64 . -GOOS=windows GOARCH=amd64 go build -o build/openframe-windows-amd64.exe . +./build/openframe bootstrap my-dev-cluster +./build/openframe app status --watch +./build/openframe cluster delete my-dev-cluster ``` - -On Windows the CLI forwards into WSL2 and runs the Linux binary; that launch is handled by `internal/shared/wsllauncher`. - -## Troubleshooting - -```bash -# Module issues -go clean -modcache && go mod tidy - -# Build cache -go clean -cache - -# Kubernetes context -kubectl config current-context -kubectl config use-context k3d-openframe-local -``` - -## Next Steps - -- **[Architecture Overview](../architecture/README.md)** - Understand the system design - -## Getting Help - -Search existing GitHub issues, or ask in the [OpenMSP community](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA). diff --git a/docs/development/testing/README.md b/docs/development/testing/README.md index bc9b7290..ba132414 100644 --- a/docs/development/testing/README.md +++ b/docs/development/testing/README.md @@ -1,358 +1,134 @@ -# Testing Guide +# Testing -OpenFrame CLI uses a layered testing approach: fast unit tests with mock executors, integration tests against real CLI binaries, and shared test utilities that eliminate boilerplate. - ---- +OpenFrame CLI has a layered testing strategy: fast, offline unit tests that mock all external tools, and integration tests that exercise the real, built binary (optionally against real Docker/k3d/kubectl/Helm). ## Test Structure and Organization -```text -tests/ -β”œβ”€β”€ integration/ -β”‚ β”œβ”€β”€ common/ -β”‚ β”‚ β”œβ”€β”€ cli_runner.go # Build + execute CLI binary, capture output -β”‚ β”‚ β”œβ”€β”€ cluster_management.go # Helpers for cluster lifecycle in tests -β”‚ β”‚ └── dependencies.go # Dependency setup utilities -β”‚ └── ... # Integration test files -└── testutil/ - β”œβ”€β”€ setup.go # Test mode init, mock executor, flag containers - β”œβ”€β”€ patterns.go # Standard command test patterns (Structure/Flags/CLI/Execution) - β”œβ”€β”€ assertions.go # Custom assertion helpers - β”œβ”€β”€ cluster.go # Cluster-specific test helpers - β”œβ”€β”€ command_assertions.go # CLI output assertion utilities - β”œβ”€β”€ flag_contract.go # Flag contract validation helpers - └── utilities.go # General test utilities -``` +| Location | Purpose | +|---|---| +| `tests/testutil/` | Shared unit-test helpers: mock executors, flag containers, command-structure assertions, cluster fixtures | +| `tests/integration/common/` | Integration-test harness: builds/runs the real CLI binary, checks for real external dependencies | +| `*_test.go` files alongside source (e.g., `cmd/cluster/`, `internal/cluster/`) | Package-local unit tests | -Unit tests live alongside the source code they test (e.g., `internal/cluster/service_test.go`). +Key shared utilities: ---- +- **`testutil.InitializeTestMode()`** β€” enables test-safe UI rendering (`ui.TestMode = true`), avoiding TTY-dependent behavior in tests. +- **`testutil.CreateStandardTestFlags()`** β€” builds a `*cluster.FlagContainer` pre-wired with a `MockCommandExecutor` and canned k3d responses (empty cluster list, "not found" on `k3d cluster get`) β€” the fastest path for unit-testing cluster command logic. +- **`testutil.CreateIntegrationTestFlags()`** β€” builds a `FlagContainer` using real dependencies (the real k3d manager is resolved at runtime), for use in environments where k3d/Docker are actually installed. +- **`testutil.TestClusterCommand(t, name, newCmdFn, setup, teardown)`** β€” a standardized pattern that runs four sub-tests automatically (`Structure`, `Flags`, `CLI`, `Execution`) against any cluster subcommand. +- **`common.RequireClusterDependencies(t)` / `RequireK8sDependencies(t)` / `RequireAllDependencies(t)`** β€” gracefully skip (not fail) integration tests when Docker, k3d, kubectl, or Helm aren't available on the host. +- **`common.InitializeCLI()` / `CleanupCLI()` / `RunCLI(args...)`** β€” build the real `openframe` binary into `build/openframe` (with mod-time caching) and execute it as a subprocess, capturing stdout/stderr/exit code for black-box assertions. ## Running Tests -### Unit Tests +Run all unit tests: ```bash -# Run all unit tests go test ./... +``` -# Run with verbose output -go test -v ./... - -# Run with race detector (recommended) -go test -race ./... +Run tests for a specific package: -# Run tests for a specific package +```bash go test ./internal/cluster/... -go test ./cmd/bootstrap/... - -# Run a specific test by name -go test -run TestBootstrapService ./internal/bootstrap/... +go test ./cmd/cluster/... ``` -### Integration Tests - -Integration tests require Docker, k3d, and Helm to be installed and running: +Run with verbose output: ```bash -# Run all integration tests (longer timeout required) -go test ./tests/integration/... -v -timeout 30m - -# Run a specific integration test -go test ./tests/integration/... -run TestClusterCreate -v -timeout 10m +go test -v ./... ``` -> **Resource requirement:** Integration tests provision real K3D clusters. Ensure at least 24 GB RAM and 50 GB disk space are available. - -### Coverage +Run integration tests (these build and execute the real binary, and may skip themselves if required tools like Docker/k3d aren't present): ```bash -# Generate coverage profile -go test -coverprofile=coverage.out ./... - -# View coverage in browser -go tool cover -html=coverage.out - -# View coverage in terminal -go tool cover -func=coverage.out | tail -1 +go test ./tests/integration/... ``` ---- - -## Test Utilities +> Integration tests use `common.RequireClusterDependencies(t)` and similar guards to skip gracefully rather than fail when Docker/k3d/kubectl/Helm aren't installed on the CI/dev machine β€” check the skip message if a test doesn't run as expected. -### Initializing Test Mode - -Always call `testutil.InitializeTestMode()` in test setups to enable safe UI rendering (prevents pterm from trying to write to a non-TTY): - -```go -func TestMain(m *testing.M) { - testutil.InitializeTestMode() - os.Exit(m.Run()) -} -``` +## Writing New Tests -### Mock Command Executor +### Unit tests for command logic -The `MockCommandExecutor` replaces real shell-outs with configurable stubs, enabling fully isolated unit tests: +Prefer mocked execution so tests run fast and offline: ```go -func TestCreateCluster(t *testing.T) { +func TestClusterCreate(t *testing.T) { testutil.InitializeTestMode() + flags := testutil.CreateStandardTestFlags() - mock := testutil.NewTestMockExecutor() - mock.SetResponse("k3d cluster create", &executor.CommandResult{ - ExitCode: 0, - Stdout: `{"name": "test-cluster"}`, - }) - - // Inject mock into the service under test - svc := cluster.NewClusterService(mock) - err := svc.CreateCluster(context.Background(), "test-cluster") - assert.NoError(t, err) - - // Verify the right command was called - calls := mock.RecordedCalls() - assert.Contains(t, calls[0].Args, "create") + // flags.Executor is a MockCommandExecutor β€” no real Docker/k3d required + result, err := runCreate(flags) + // assert on result/err } ``` -### Standard Flag Containers - -Use `CreateStandardTestFlags()` for unit tests (mock dependencies) and `CreateIntegrationTestFlags()` for integration tests (real dependencies): - -```go -// Unit test β€” mock executor, no live cluster needed -flags := testutil.CreateStandardTestFlags() - -// Integration test β€” real executor, requires k3d -flags := testutil.CreateIntegrationTestFlags() -``` - -`CreateStandardTestFlags()` pre-configures common mock responses: -- `k3d cluster list` β†’ empty array `[]` -- `k3d cluster get` β†’ not found - ---- - -## Writing Unit Tests +### Standardized command structure tests -### Testing a Cobra Command - -Use `testutil.TestClusterCommand` to run the four standard sub-tests for any cluster command: +For any new Cobra subcommand under `cmd/cluster/`, add a `TestClusterCommand` invocation to get structure/flags/CLI/execution coverage for free: ```go -package create_test - -import ( - "testing" - "github.com/flamingo-stack/openframe-cli/tests/testutil" -) - func TestCreateCommand(t *testing.T) { testutil.TestClusterCommand( t, - "create", // command name - NewCreateCommand, // func() *cobra.Command - func() { // setup - testutil.InitializeTestMode() - }, - func() {}, // teardown + "create", + NewCreateCommand, + func() { /* setup mocks */ }, + func() { /* teardown */ }, ) } ``` -This runs four sub-tests automatically: +### Security-sensitive assertions -| Sub-test | What it checks | -|---|---| -| `Structure` | Name, short/long descriptions, `RunE` presence | -| `Flags` | `--help` succeeds, unknown flags return error | -| `CLI` | Argument count validation via `cmd.Args` | -| `Execution` | `--dry-run` behavior (if registered), `--help` always succeeds | - -### Testing Business Logic (Service Layer) - -```go -func TestChartServiceInstall(t *testing.T) { - testutil.InitializeTestMode() - - mock := testutil.NewTestMockExecutor() - // Configure mock responses for helm commands - mock.SetResponse("helm upgrade --install argo-cd", &executor.CommandResult{ - ExitCode: 0, - Stdout: "Release \"argo-cd\" has been upgraded.", - }) - - svc := chart.NewChartService(mock, fakeK8sClient) - err := svc.InstallArgoCD(context.Background(), cfg) - assert.NoError(t, err) -} -``` - -### Testing Error Paths +When a change touches command construction (especially anything derived from user input), assert against the mock's structured argv rather than flattened strings, to catch shell-injection-style bugs: ```go -func TestClusterCreateFailure(t *testing.T) { - testutil.InitializeTestMode() - mock := testutil.NewTestMockExecutor() - - // Simulate k3d failure - mock.SetResponse("k3d cluster create", &executor.CommandResult{ - ExitCode: 1, - Stderr: "cluster already exists", - }) - - svc := cluster.NewClusterService(mock) - err := svc.CreateCluster(context.Background(), "existing-cluster") - - assert.Error(t, err) - assert.Contains(t, err.Error(), "already exists") +exec := executor.NewMockCommandExecutor() +// ... exercise code under test ... +for _, cmd := range exec.Commands() { + for _, arg := range cmd.Args { + if strings.Contains(arg, "$(") { + t.Errorf("shell injection in argv: %q", arg) + } + } } ``` ---- - -## Writing Integration Tests - -Integration tests use the `common.CLIRunner` to build and execute the real binary: +### Integration tests against the real binary ```go -package integration_test - -import ( - "log" - "os" - "strings" - "testing" - "github.com/flamingo-stack/openframe-cli/tests/integration/common" -) - func TestMain(m *testing.M) { if err := common.InitializeCLI(); err != nil { - log.Fatalf("CLI build failed: %v", err) + log.Fatalf("setup failed: %v", err) } defer common.CleanupCLI() os.Exit(m.Run()) } func TestClusterList(t *testing.T) { - result := common.RunCLI("cluster", "list", "--output", "json") + common.RequireClusterDependencies(t) + result := common.RunCLI("cluster", "list") if result.Failed() { - t.Fatalf("cluster list failed: %s", result.ErrorMessage()) - } - - // Verify JSON output - if !strings.HasPrefix(strings.TrimSpace(result.Stdout), "[") { - t.Errorf("expected JSON array output, got: %s", result.Stdout) + t.Fatalf("expected success, got: %s", result.ErrorMessage()) } } ``` -### CLIResult Methods - -| Method | Description | -|---|---| -| `result.Success()` | `true` when exit code is 0 and no error | -| `result.Failed()` | Inverse of `Success()` | -| `result.Output()` | Concatenates stdout + stderr | -| `result.ErrorMessage()` | Extracts first `Error: ...` line from stderr | -| `result.Stdout` | Raw stdout string | -| `result.Stderr` | Raw stderr string | -| `result.ExitCode` | Integer exit code | - -### CLI Binary Caching - -`InitializeCLI()` builds the binary to `build/openframe` and caches it by comparing mod times against `main.go`. Subsequent test runs skip the rebuild if the binary is newer than the source β€” significantly speeding up iterative testing. - ---- - -## Test Patterns and Conventions - -### Table-Driven Tests - -Prefer table-driven tests for commands with multiple argument/flag combinations: - -```go -func TestClusterNameValidation(t *testing.T) { - tests := []struct { - name string - clusterName string - wantError bool - }{ - {"valid name", "openframe-dev", false}, - {"too short", "ab", true}, - {"uppercase", "MyCluster", true}, - {"injection attempt", "test;rm -rf /", true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := models.ValidateClusterName(tt.clusterName) - if tt.wantError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} -``` - -### Redaction Cleanup in Tests - -When testing code that uses `redact.RegisterSecret()`, always clean up in teardown: - -```go -func TestWithSecret(t *testing.T) { - defer redact.ClearSecrets() - - redact.RegisterSecret("test-token") - // ... test code -} -``` - -### Non-Interactive Mode in Tests - -Set non-interactive mode to prevent tests from blocking on prompts: - -```go -func TestNonInteractiveBehavior(t *testing.T) { - // Option 1: Use the --non-interactive flag in integration tests - result := common.RunCLI("bootstrap", "--non-interactive") - - // Option 2: Set UI test mode (unit tests) - testutil.InitializeTestMode() // sets ui.TestMode = true -} -``` - ---- - -## Coverage Requirements - -| Package Type | Target Coverage | -|---|---| -| Core services (`internal/`) | β‰₯ 80% | -| Command layer (`cmd/`) | β‰₯ 70% | -| Provider implementations | β‰₯ 75% | -| Shared utilities | β‰₯ 85% | - -Run coverage and check targets: - -```bash -go test -coverprofile=coverage.out ./... -go tool cover -func=coverage.out | grep -v "100.0%" -``` - ---- +## Coverage Expectations -## CI Test Environment +There is no single global coverage gate documented in the codebase; instead, the project relies on: -The GitHub Actions CI pipeline runs: +- **Mocked unit tests** covering command structure, flag validation, and orchestration logic across `cmd/` and `internal/` packages (fast, run on every change). +- **Integration tests** that exercise the actual compiled binary end-to-end, gated behind dependency checks so they only run where Docker/k3d/kubectl/Helm are genuinely available. +- **Standardized per-command tests** (`TestClusterCommand`) ensure every cluster subcommand at minimum has consistent Structure/Flags/CLI/Execution coverage β€” new subcommands should adopt this pattern rather than hand-rolling equivalent checks. -1. **Unit tests** with race detector on every push -2. **Integration tests** on PRs targeting `main` -3. **Coverage reporting** on every PR +When adding a new command or provider, aim to cover: -Integration tests in CI use a matrix of Go versions and operating systems. Ensure your tests pass on both Linux and macOS. +1. Flag/argument validation (valid, invalid, and edge-case inputs). +2. The mocked "happy path" orchestration logic. +3. At least one error path (e.g., a failing mocked command) and how the CLI surfaces it. +4. If touching shelled-out commands: an argv-level assertion that user input can't be interpreted as shell syntax. diff --git a/docs/diagrams/architecture/README.md b/docs/diagrams/architecture/README.md index d2887df1..85bef4d1 100644 --- a/docs/diagrams/architecture/README.md +++ b/docs/diagrams/architecture/README.md @@ -4,10 +4,10 @@ This directory contains Mermaid diagrams generated from architecture analysis. ## Diagrams -- **[High-Level System Design](./high-level-system-design.mmd)** - `.mmd` file -- **[Dependency Flowchart](./dependency-flowchart.mmd)** - `.mmd` file -- **[Bootstrap Sequence Diagram](./bootstrap-sequence-diagram.mmd)** - `.mmd` file -- **[App Install / Upgrade Data Flow](./app-install-upgrade-data-flow.mmd)** - `.mmd` file +- **[Architecture Diagram](./architecture-diagram.mmd)** - `.mmd` file +- **[Dependency Diagram](./dependency-diagram.mmd)** - `.mmd` file +- **[Bootstrap Sequence](./bootstrap-sequence.mmd)** - `.mmd` file +- **[App Status Aggregation](./app-status-aggregation.mmd)** - `.mmd` file ## Viewing Diagrams diff --git a/docs/diagrams/architecture/app-install-upgrade-data-flow.mmd b/docs/diagrams/architecture/app-install-upgrade-data-flow.mmd deleted file mode 100644 index 2f26c570..00000000 --- a/docs/diagrams/architecture/app-install-upgrade-data-flow.mmd +++ /dev/null @@ -1,28 +0,0 @@ -sequenceDiagram - participant User - participant AppCmd as "cmd/app/install" - participant Target as "app/target.Selector" - participant K8sPkg as "k8s package" - participant ChSvc as "chart/services" - participant ArgoProv as "argocd.Manager" - participant HelmProv as "helm.HelmManager" - - User->>AppCmd: openframe app install [--context k3d-dev] - AppCmd->>Target: Select(ctx) [if no --context] - Target->>K8sPkg: LoadContexts(kubeconfigPath) - K8sPkg-->>Target: []ContextInfo - Target->>User: Prompt: select context - User-->>Target: k3d-openframe-dev - Target->>K8sPkg: CheckResources(ctx, requirements) - K8sPkg-->>Target: Resources, sufficient=true - Target-->>AppCmd: SelectResult{Config, Context} - - AppCmd->>ChSvc: InstallChartsWithConfigContext(ctx, req) - ChSvc->>ArgoProv: Install(ctx, cfg) - ArgoProv-->>ChSvc: ArgoCD installed - ChSvc->>HelmProv: InstallAppOfAppsFromLocal(ctx, cfg) - HelmProv-->>ChSvc: app-of-apps installed - ChSvc->>ArgoProv: WaitForApplications(ctx, cfg) - ArgoProv-->>ChSvc: All apps Healthy+Synced - ChSvc-->>AppCmd: OK - AppCmd-->>User: SUCCESS diff --git a/docs/diagrams/architecture/app-status-aggregation.mmd b/docs/diagrams/architecture/app-status-aggregation.mmd new file mode 100644 index 00000000..1cba7e9d --- /dev/null +++ b/docs/diagrams/architecture/app-status-aggregation.mmd @@ -0,0 +1,20 @@ +sequenceDiagram + participant User + participant CLI as cmd/app.status + participant Svc as app/status.Service + participant Accessor as k8s.Accessor + participant ArgoCDMgr as argocd.Manager + participant K8s as Kubernetes API + + User->>CLI: openframe app status --watch + CLI->>Svc: Report(ctx, verbose) + Svc->>Accessor: CheckHealth(ctx) + Accessor->>K8s: list nodes + K8s-->>Accessor: node conditions + Svc->>ArgoCDMgr: ListApplications(ctx, verbose) + ArgoCDMgr->>K8s: list Application CRs + K8s-->>ArgoCDMgr: applications + Svc->>ArgoCDMgr: AdminPassword(ctx) + ArgoCDMgr->>K8s: read argocd-initial-admin-secret + Svc-->>CLI: Report{Health, Apps, Synced, Healthy} + CLI-->>User: table + readiness summary diff --git a/docs/diagrams/architecture/architecture-diagram.mmd b/docs/diagrams/architecture/architecture-diagram.mmd new file mode 100644 index 00000000..c2626e68 --- /dev/null +++ b/docs/diagrams/architecture/architecture-diagram.mmd @@ -0,0 +1,57 @@ +graph TB + subgraph "CLI Layer (cmd/)" + Bootstrap[bootstrap] + Cluster[cluster] + App[app] + Prereq[prerequisites] + Update[update] + end + + subgraph "Domain Services (internal/)" + ClusterSvc["cluster.ClusterService"] + ChartSvc["chart/services.ChartService"] + AppStatus["app/status.Service"] + AppUninstall["app/uninstall.Service"] + PrereqFw["prerequisites.Runner"] + SelfUpdate["selfupdate.Updater"] + end + + subgraph "Providers" + K3d["cluster/providers/k3d"] + EKS["cluster/providers/eks (terraform)"] + GKE["cluster/providers/gke (terraform)"] + ArgoCD["chart/providers/argocd"] + Helm["chart/providers/helm"] + Git["chart/providers/git"] + end + + subgraph "External Systems" + Docker[(Docker)] + K8sAPI[(Kubernetes API)] + CloudAPI[(GCP / AWS APIs)] + GitHub[(GitHub Releases)] + end + + Bootstrap --> ClusterSvc + Bootstrap --> ChartSvc + Cluster --> ClusterSvc + App --> ChartSvc + App --> AppStatus + App --> AppUninstall + Prereq --> PrereqFw + Update --> SelfUpdate + + ClusterSvc --> K3d + ClusterSvc --> EKS + ClusterSvc --> GKE + ChartSvc --> ArgoCD + ChartSvc --> Helm + ChartSvc --> Git + AppStatus --> ArgoCD + + K3d --> Docker + EKS --> CloudAPI + GKE --> CloudAPI + ArgoCD --> K8sAPI + Helm --> K8sAPI + SelfUpdate --> GitHub diff --git a/docs/diagrams/architecture/bootstrap-sequence-diagram.mmd b/docs/diagrams/architecture/bootstrap-sequence-diagram.mmd deleted file mode 100644 index 5fda484b..00000000 --- a/docs/diagrams/architecture/bootstrap-sequence-diagram.mmd +++ /dev/null @@ -1,47 +0,0 @@ -sequenceDiagram - participant User - participant CLI as "openframe bootstrap" - participant BSvc as "bootstrap.Service" - participant CSvc as "cluster.Service" - participant K3D as "K3D Provider" - participant ChSvc as "chart/services" - participant Helm as "HelmManager" - participant Git as "git.Repository" - participant ArgoCD as "argocd.Manager" - participant K8s as "Kubernetes API" - - User->>CLI: openframe bootstrap [name] - CLI->>BSvc: Execute(cmd, args) - BSvc->>ChSvc: ValidateHelmValuesFile() - ChSvc-->>BSvc: OK / error - - BSvc->>CSvc: CreateClusterWithPrerequisites(ctx, name) - CSvc->>K3D: CreateCluster(ctx, config) - K3D-->>CSvc: rest.Config - CSvc-->>BSvc: rest.Config - - BSvc->>ChSvc: InstallChartsWithConfigContext(ctx, req) - ChSvc->>ChSvc: CheckAndInstallPrerequisites() - ChSvc->>Helm: InstallArgoCDWithProgress(ctx, cfg) - Helm->>K8s: helm upgrade --install argo-cd - K8s-->>Helm: OK - Helm->>K8s: waitForArgoCDDeployments() - K8s-->>Helm: Deployments ready - - ChSvc->>Git: CloneChartRepository(ctx, appConfig) - Git-->>ChSvc: CloneResult{tempDir, chartPath} - - ChSvc->>Helm: InstallAppOfAppsFromLocal(ctx, cfg) - Helm->>K8s: helm upgrade --install app-of-apps - K8s-->>Helm: OK - - ChSvc->>ArgoCD: WaitForApplications(ctx, cfg) - loop Every 2s until ready or timeout - ArgoCD->>K8s: List Applications (dynamic client) - K8s-->>ArgoCD: Application list - ArgoCD->>ArgoCD: assessApplications() - end - ArgoCD-->>ChSvc: All Healthy+Synced - - ChSvc-->>BSvc: OK - BSvc-->>User: Bootstrap complete diff --git a/docs/diagrams/architecture/bootstrap-sequence.mmd b/docs/diagrams/architecture/bootstrap-sequence.mmd new file mode 100644 index 00000000..771e1099 --- /dev/null +++ b/docs/diagrams/architecture/bootstrap-sequence.mmd @@ -0,0 +1,28 @@ +sequenceDiagram + participant User + participant CLI as cmd/bootstrap + participant Boot as internal/bootstrap.Service + participant Cluster as internal/cluster.ClusterService + participant K3d as k3d provider + participant Chart as chart/services (Installer) + participant ArgoCD as ArgoCD provider + participant K8s as Kubernetes API + + User->>CLI: openframe bootstrap + CLI->>Boot: Execute(cmd, args) + Boot->>Chart: ValidateHelmValuesFile() + Boot->>Cluster: CreateCluster(config) + Cluster->>K3d: CreateCluster(ctx, config) + K3d->>K8s: provision cluster (Docker) + K3d-->>Cluster: rest.Config + Cluster-->>Boot: rest.Config + Boot->>Chart: InstallChartsWithConfigContext(req) + Chart->>ArgoCD: Install(ctx, config) + ArgoCD->>K8s: helm install argocd + Chart->>Chart: AppOfApps.Install (git clone + helm) + Chart->>ArgoCD: WaitForApplications(ctx, config) + ArgoCD->>K8s: poll Application CRs + K8s-->>ArgoCD: sync/health status + ArgoCD-->>Chart: ready + Chart-->>Boot: success + Boot-->>User: summary card (stages, timings, access hints) diff --git a/docs/diagrams/architecture/dependency-diagram.mmd b/docs/diagrams/architecture/dependency-diagram.mmd new file mode 100644 index 00000000..fdff1e9b --- /dev/null +++ b/docs/diagrams/architecture/dependency-diagram.mmd @@ -0,0 +1,69 @@ +graph TB + subgraph cmd + CmdCluster[cmd/cluster] + CmdApp[cmd/app] + CmdBootstrap[cmd/bootstrap] + CmdPrereq[cmd/prerequisites] + CmdUpdate[cmd/update] + end + + subgraph internal_cluster["internal/cluster"] + ClusterService[service.go] + ClusterProvider[provider] + ClusterModels[models] + end + + subgraph internal_chart["internal/chart"] + ChartService[services] + ChartArgoCD[providers/argocd] + ChartHelm[providers/helm] + ChartGit[providers/git] + end + + subgraph internal_app["internal/app"] + AppStatus[status] + AppUninstall[uninstall] + end + + subgraph internal_shared["internal/shared"] + Executor[executor] + Errors[errors] + UI[ui] + Download[download] + SelfUpdate[selfupdate] + end + + subgraph internal_k8s["internal/k8s"] + K8sAccess[accessor / restconfig / contexts] + end + + CmdBootstrap --> ClusterService + CmdBootstrap --> ChartService + CmdCluster --> ClusterService + CmdApp --> ChartService + CmdApp --> AppStatus + CmdApp --> AppUninstall + CmdApp --> K8sAccess + CmdPrereq --> internal_cluster + CmdUpdate --> SelfUpdate + + ClusterService --> ClusterProvider + ClusterService --> ClusterModels + ClusterProvider --> Executor + + ChartService --> ChartArgoCD + ChartService --> ChartHelm + ChartService --> ChartGit + ChartArgoCD --> K8sAccess + ChartHelm --> K8sAccess + + AppStatus --> ChartArgoCD + AppStatus --> K8sAccess + AppUninstall --> ChartArgoCD + AppUninstall --> ChartHelm + + ClusterService --> Errors + ChartService --> Errors + CmdCluster --> UI + CmdApp --> UI + ClusterProvider --> Download diff --git a/docs/diagrams/architecture/dependency-flowchart.mmd b/docs/diagrams/architecture/dependency-flowchart.mmd deleted file mode 100644 index 714b7f72..00000000 --- a/docs/diagrams/architecture/dependency-flowchart.mmd +++ /dev/null @@ -1,62 +0,0 @@ -graph LR - subgraph Commands["cmd/"] - bootstrap["bootstrap"] - cluster_cmd["cluster/*"] - app_cmd["app/*"] - prereq_cmd["prerequisites"] - update_cmd["update"] - end - - subgraph Services["internal/"] - bsvc["bootstrap.Service"] - csvc["cluster.ClusterService"] - chsvc["chart/services.ChartService"] - appsvc["app/status + uninstall"] - prefw["prerequisites.Runner"] - supdater["selfupdate.Updater"] - end - - subgraph Providers["Providers"] - k3dp["cluster/providers/k3d"] - argop["chart/providers/argocd.Manager"] - helmp["chart/providers/helm.HelmManager"] - gitp["chart/providers/git.Repository"] - end - - subgraph Infra["Shared Infrastructure"] - exec["executor.CommandExecutor"] - k8spkg["k8s (rest.Config, Accessor)"] - dlpkg["download.Downloader"] - uipkg["shared/ui"] - errpkg["shared/errors"] - redactpkg["shared/redact"] - end - - bootstrap --> bsvc - cluster_cmd --> csvc - app_cmd --> chsvc - app_cmd --> appsvc - prereq_cmd --> prefw - update_cmd --> supdater - - bsvc --> csvc - bsvc --> chsvc - - csvc --> k3dp - chsvc --> argop - chsvc --> helmp - chsvc --> gitp - appsvc --> argop - - k3dp --> exec - helmp --> exec - argop --> k8spkg - helmp --> k8spkg - - prefw --> dlpkg - supdater --> dlpkg - - exec --> redactpkg - errpkg --> uipkg - chsvc --> errpkg - csvc --> errpkg diff --git a/docs/diagrams/architecture/high-level-system-design.mmd b/docs/diagrams/architecture/high-level-system-design.mmd deleted file mode 100644 index 46e33a66..00000000 --- a/docs/diagrams/architecture/high-level-system-design.mmd +++ /dev/null @@ -1,73 +0,0 @@ -graph TB - subgraph CLI["CLI Entry Point"] - main["main.go"] - root["cmd/root.go"] - end - - subgraph Commands["Command Layer"] - bootstrap["cmd/bootstrap"] - cluster_cmd["cmd/cluster"] - app_cmd["cmd/app"] - prereq_cmd["cmd/prerequisites"] - update_cmd["cmd/update"] - end - - subgraph Core["Core Services"] - bootstrap_svc["internal/bootstrap"] - cluster_svc["internal/cluster"] - chart_svc["internal/chart/services"] - prereq_fw["internal/prerequisites"] - selfupdate["internal/shared/selfupdate"] - end - - subgraph Providers["Providers"] - k3d_prov["cluster/providers/k3d"] - argocd_prov["chart/providers/argocd"] - helm_prov["chart/providers/helm"] - git_prov["chart/providers/git"] - end - - subgraph Shared["Shared Infrastructure"] - executor["internal/shared/executor"] - k8s["internal/k8s"] - download["internal/shared/download"] - ui["internal/shared/ui"] - redact["internal/shared/redact"] - errors["internal/shared/errors"] - end - - subgraph External["External Tools & APIs"] - k3d_tool["K3D CLI"] - helm_tool["Helm CLI"] - argocd_cr["ArgoCD CRDs"] - github["GitHub API"] - git_repo["Git Repositories"] - end - - main --> root - root --> Commands - bootstrap_cmd --> bootstrap_svc - cluster_cmd --> cluster_svc - app_cmd --> chart_svc - prereq_cmd --> prereq_fw - update_cmd --> selfupdate - - bootstrap_svc --> cluster_svc - bootstrap_svc --> chart_svc - - cluster_svc --> k3d_prov - chart_svc --> argocd_prov - chart_svc --> helm_prov - chart_svc --> git_prov - - k3d_prov --> executor - helm_prov --> executor - argocd_prov --> k8s - helm_prov --> k8s - - executor --> k3d_tool - executor --> helm_tool - argocd_prov --> argocd_cr - git_prov --> git_repo - selfupdate --> github - download --> github diff --git a/docs/getting-started/first-steps.md b/docs/getting-started/first-steps.md index 00b41ee5..cd7e7979 100644 --- a/docs/getting-started/first-steps.md +++ b/docs/getting-started/first-steps.md @@ -1,224 +1,97 @@ # First Steps -You've successfully bootstrapped an OpenFrame environment. Here are the first 5 things to explore and configure to get the most out of your installation. +You've installed OpenFrame CLI and completed the [quick start](quick-start.md). Here's what to do next to get comfortable with day-to-day usage. ---- +## 1. Check the platform's status -## 1. Verify Your Environment - -Start by confirming the state of your cluster and platform: +Get a snapshot of cluster health and ArgoCD application readiness: ```bash -# Check cluster status -openframe cluster status - -# Check application status openframe app status ``` -### Cluster Status Output +For a live-refreshing view that polls every few seconds: -```text -NAME STATUS NODES VERSION -openframe-dev running 3 v1.29.x +```bash +openframe app status --watch ``` -### App Status Output - -The app status command aggregates both Kubernetes cluster health and ArgoCD application sync state into a single unified report. You'll see each deployed application with its sync and health status. +For an interactive, k9s-style dashboard where you can navigate applications, inspect details, and trigger syncs: ```bash -# For machine-readable output (e.g., in scripts) -openframe cluster list --output json -openframe cluster status --output yaml +openframe app status --interactive ``` ---- +> `--watch` and `--interactive` require an interactive terminal and cannot be combined with `--output`/`--plain` modes. -## 2. Access the OpenFrame Platform +## 2. Retrieve ArgoCD access credentials -Get access information for the deployed OpenFrame services: +To sign in to the ArgoCD UI and manage the platform visually: ```bash openframe app access ``` -This command displays the URLs and connection details for the OpenFrame platform running in your local cluster. - -> **Tip:** Bookmark the displayed URLs for quick access to the OpenFrame web interface and ArgoCD dashboard. +This prints the admin username/password and port-forward instructions, e.g.: ---- +```text +ArgoCD access + Username: admin + Password: +Open the ArgoCD UI: + 1. kubectl port-forward -n argocd svc/argocd-server 8080:443 + 2. open https://localhost:8080 +``` -## 3. Explore the Cluster Commands +## 3. Inspect your cluster(s) -The `cluster` command group (also aliased as `k`) manages your Kubernetes cluster lifecycle: +List and inspect the clusters OpenFrame CLI manages: ```bash # List all managed clusters openframe cluster list -# Get detailed status of a cluster -openframe cluster status - -# Create an additional cluster with a custom name -openframe cluster create my-second-cluster - -# Delete a cluster -openframe cluster delete my-second-cluster - -# Reclaim disk space by pruning unused container images on cluster nodes -openframe cluster cleanup -``` - -> **Shorthand:** `openframe k list` is equivalent to `openframe cluster list`. - ---- - -## 4. Explore the App Commands - -The `app` command group manages the OpenFrame platform deployment: +# Include externally-discovered cloud clusters (GKE/EKS) not created via this CLI +openframe cluster list --all -```bash -# Install OpenFrame on an existing cluster -openframe app install - -# Check application status -openframe app status - -# Upgrade to a different OpenFrame version/branch -openframe app upgrade - -# Uninstall OpenFrame from a cluster -openframe app uninstall +# Detailed cluster status (nodes, health) +openframe cluster status -# Show access details -openframe app access +# Switch kubectl context to a specific cluster +openframe cluster use ``` -### Upgrading OpenFrame +## 4. Explore configuration options -There are two upgrade modes: +If you skipped the interactive wizard during bootstrap, explore the flags available for a full cluster + install workflow: ```bash -# Mode 1: Switch to a different git ref (branch, tag, or commit) -openframe app upgrade --ref v2.0.0 - -# Mode 2: Force re-sync of the current ref -openframe app upgrade --force-sync +openframe cluster create --help +openframe app install --help ``` ---- +Key things worth trying: -## 5. Keep the CLI Up to Date +- `openframe cluster create my-gke --type gke --project my-project --region us-central1 --skip-wizard` β€” provision a cloud cluster non-interactively. +- `openframe app install --ref v1.4.0` β€” install a specific OpenFrame release ref instead of the default branch. +- `openframe app upgrade --ref v1.4.1` β€” move an existing install to a different git ref, or `--sync` to force a re-sync at the current ref. -OpenFrame CLI includes a built-in self-update mechanism with cryptographic verification: +## 5. Keep the CLI itself up to date ```bash -# Check if an update is available -openframe update --check +# Check for a newer CLI release without installing it +openframe update check -# Apply the latest update +# Update to the latest release openframe update -# Roll back to the previous version if needed -openframe update --rollback -``` - -> **Security note:** All updates are verified using [Sigstore/cosign](https://docs.sigstore.dev/cosign/overview/) against the official GitHub Actions release workflow. Only binaries produced by the `flamingo-stack/openframe-cli` release pipeline are accepted. - ---- - -## Initial Configuration: The Helm Values File - -When you ran `openframe bootstrap`, a configuration file called `openframe-helm-values.yaml` was created in your working directory. This file controls the OpenFrame platform deployment: - -```bash -# View the generated configuration -cat openframe-helm-values.yaml -``` - -Key configurable areas include: - -| Section | Description | -|---|---| -| `branch` | The OpenFrame git ref (branch, tag) to deploy | -| `docker` | Container registry settings | -| `ingress` | Ingress hostname and TLS configuration | -| `argocd` | ArgoCD Helm value overrides | - -To apply changes to an existing deployment: - -```bash -openframe app upgrade -``` - ---- - -## Verbose and Silent Modes - -Control the CLI's output verbosity: - -```bash -# Show detailed debug output (ArgoCD sync events, Helm operations, etc.) -openframe bootstrap --verbose - -# Suppress all non-error output (perfect for scripts) -openframe bootstrap --silent - -# Machine-readable output for cluster commands -openframe cluster list --output json +# Roll back to the previously installed binary +openframe update rollback ``` ---- - -## Running in CI/CD - -For automated pipelines, use `--non-interactive` to skip all prompts: - -```bash -# Full non-interactive bootstrap -openframe bootstrap --non-interactive - -# With a specific cluster name -openframe bootstrap --non-interactive my-ci-cluster -``` - -The CLI reads from an existing `openframe-helm-values.yaml` file in the current directory when running non-interactively. - ---- - -## Getting Help - -Every command has built-in help: - -```bash -# General help -openframe --help - -# Help for a specific command -openframe bootstrap --help -openframe cluster create --help -openframe app install --help -openframe update --help -``` - ---- - -## Community & Support - -- **OpenMSP Slack:** [https://www.openmsp.ai/](https://www.openmsp.ai/) β€” Join for help, discussions, and announcements -- **Slack invite:** [https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) -- **OpenFrame platform repo:** [https://github.com/flamingo-stack/openframe-oss-tenant](https://github.com/flamingo-stack/openframe-oss-tenant) -- **CLI releases:** [https://github.com/flamingo-stack/openframe-cli/releases](https://github.com/flamingo-stack/openframe-cli/releases) - ---- - -## Summary: First Steps Checklist +## Where to Get Help -- [ ] Verified cluster status with `openframe cluster status` -- [ ] Checked app status with `openframe app status` -- [ ] Accessed the platform with `openframe app access` -- [ ] Explored `openframe cluster --help` and `openframe app --help` -- [ ] Reviewed `openframe-helm-values.yaml` configuration file -- [ ] Ran `openframe update --check` to see if a newer version is available -- [ ] Joined the [OpenMSP Slack](https://www.openmsp.ai/) community +- Run `openframe --help` or `openframe --help` at any point β€” every command has detailed built-in help text and usage examples. +- Run `openframe prerequisites check --type ` if something isn't working β€” most failures are missing/misconfigured local tooling. +- For questions, discussion, and support, the project is community-supported via the **OpenMSP Slack community**: [join here](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) or visit [openmsp.ai](https://www.openmsp.ai/). +- The OpenFrame platform code that this CLI deploys lives in [`flamingo-stack/openframe-oss-tenant`](https://github.com/flamingo-stack/openframe-oss-tenant); its documentation is at [github.com/flamingo-stack/openframe-oss-tenant/tree/main/docs](https://github.com/flamingo-stack/openframe-oss-tenant/tree/main/docs). diff --git a/docs/getting-started/introduction.md b/docs/getting-started/introduction.md index bd9d1db2..8c587ef9 100644 --- a/docs/getting-started/introduction.md +++ b/docs/getting-started/introduction.md @@ -1,141 +1,74 @@ # Introduction to OpenFrame CLI -**OpenFrame CLI** is a modern, interactive command-line tool written in Go that bootstraps and manages OpenFrame Kubernetes environments. With a single `openframe` binary, you can provision local K3D clusters, install the full OpenFrame platform stack via ArgoCD GitOps, and manage the entire lifecycle of your deployment β€” all through both guided interactive wizards and fully scriptable non-interactive modes. - -> **OpenFrame** is the unified platform from [Flamingo](https://flamingo.run) that integrates multiple MSP tools into a single AI-driven interface, automating IT support operations across the stack. Learn more at [openframe.ai](https://openframe.ai). - ---- - ## What is OpenFrame CLI? -OpenFrame CLI is your single entry point for: - -- **Bootstrapping** a fully functional OpenFrame environment from scratch in minutes -- **Managing Kubernetes clusters** (K3D) β€” create, delete, list, inspect, and clean up -- **Deploying and upgrading** the OpenFrame platform chart via ArgoCD GitOps -- **Checking and installing prerequisites** automatically (Docker, k3d, Helm) -- **Self-updating** to the latest version with cryptographic signature verification +**OpenFrame CLI** (`openframe`) is a modern, interactive command-line tool for provisioning Kubernetes clusters β€” locally via k3d or in the cloud via GKE/EKS (using Terraform) β€” and deploying the [OpenFrame](https://openframe.ai) platform onto them using ArgoCD's app-of-apps pattern. -It replaces manual shell scripts and disparate tooling with a cohesive, type-safe Go binary that provides real-time progress feedback, friendly error messages, and deep automation support. +It is the primary bootstrap and lifecycle-management tool for [OpenFrame](https://www.flamingo.run/openframe), the unified, AI-driven MSP platform built by [Flamingo](https://flamingo.run). OpenFrame CLI handles the full lifecycle of an OpenFrame deployment: checking prerequisites, provisioning a cluster, installing the platform, monitoring status, upgrading, and tearing down β€” with both fully interactive wizards and non-interactive flags for CI/automation. ---- +> **Note:** OpenFrame CLI is one component of the broader OpenFrame ecosystem. The main platform code lives in a separate repository, [`flamingo-stack/openframe-oss-tenant`](https://github.com/flamingo-stack/openframe-oss-tenant), which this CLI deploys and manages. ## Key Features -| Feature | Description | -|---|---| -| **One-command bootstrap** | `openframe bootstrap` provisions a cluster and deploys the full platform in one step | -| **Interactive wizards** | Step-by-step guided prompts for new users β€” no YAML editing required | -| **Non-interactive / CI mode** | `--non-interactive` flag makes every command scriptable for pipelines | -| **ArgoCD GitOps integration** | Platform deployment is fully GitOps-driven using the `openframe-oss-tenant` chart | -| **Auto-prerequisite management** | Detects and installs Docker, k3d, and Helm automatically on macOS/Linux | -| **Cosign signature verification** | All self-updates are cryptographically verified against the official release workflow | -| **WSL2 support on Windows** | Transparently re-executes inside WSL2 β€” no manual Linux setup needed | -| **Secret redaction** | Credentials and tokens are automatically scrubbed from all debug output | -| **Machine-readable output** | `--output json/yaml` for clean scripted consumption | +- **One-command bootstrap** β€” `openframe bootstrap` creates a local k3d cluster and installs the entire OpenFrame platform (ArgoCD + app-of-apps) in a single step. +- **Multi-provider cluster support** β€” Provision clusters locally with k3d (Docker-based, Kubernetes-in-Docker) or in the cloud with GKE (Google) and EKS (AWS), all through Terraform under the hood. +- **Platform lifecycle management** β€” Install, upgrade, monitor status, and uninstall the OpenFrame platform via ArgoCD, without touching the underlying cluster. +- **Interactive and CI-friendly** β€” Every workflow supports an interactive wizard (prompts, spinners, cost estimates) as well as `--non-interactive`/`--skip-wizard` flags for automation pipelines. +- **Built-in prerequisites management** β€” Detects missing tools (Docker, k3d, Helm, Terraform, gcloud, AWS CLI) and can auto-install them on macOS/Linux. +- **Live status dashboard** β€” An interactive, k9s-style terminal UI (`openframe app status --interactive`) for inspecting ArgoCD application health and triggering syncs. +- **Secure by default** β€” All tool binaries are downloaded with pinned versions and SHA256 checksum verification (no `curl | bash`), and CLI self-updates are verified with Sigstore/cosign signatures. +- **Self-updating** β€” `openframe update` checks for, downloads, verifies, and applies new CLI releases, with rollback support. ---- - -## Target Audience +## Who is this for? OpenFrame CLI is designed for: -- **MSP technicians and operators** setting up OpenFrame environments -- **DevOps engineers** automating OpenFrame deployment in CI/CD pipelines -- **Developers** contributing to or extending the OpenFrame platform -- **System administrators** managing the lifecycle of OpenFrame Kubernetes clusters - ---- +- **MSP technicians and platform operators** who need to stand up an OpenFrame environment quickly, whether for local evaluation or production cloud deployment. +- **DevOps/Platform engineers** integrating OpenFrame provisioning into CI/CD pipelines using the CLI's non-interactive flags and JSON/YAML output modes. +- **Contributors to the OpenFrame ecosystem** who need a reliable way to spin up disposable test clusters and platform installs. -## High-Level Architecture +## How it fits together ```mermaid graph TB - subgraph User["User Interface"] - cli["openframe binary"] - wizard["Interactive Wizard"] - flags["--flag automation"] + subgraph "CLI Layer" + Bootstrap[bootstrap] + Cluster[cluster] + App[app] + Prereq[prerequisites] + Update[update] end - subgraph Commands["Command Layer"] - bootstrap["bootstrap"] - cluster["cluster (create/delete/list/status)"] - app["app (install/upgrade/status/uninstall)"] - prereq["prerequisites (check/install)"] - update["update (self-update/rollback)"] + subgraph "Providers" + K3d["k3d (local)"] + EKS["EKS (Terraform)"] + GKE["GKE (Terraform)"] + ArgoCD["ArgoCD"] end - subgraph Platform["OpenFrame Platform"] - k3d["K3D Kubernetes Cluster"] - argocd["ArgoCD GitOps Engine"] - openframe["OpenFrame OSS Tenant Chart"] + subgraph "External Systems" + Docker[(Docker)] + K8sAPI[(Kubernetes API)] + CloudAPI[(GCP / AWS APIs)] end - cli --> Commands - wizard --> Commands - flags --> Commands - bootstrap --> k3d - bootstrap --> argocd - argocd --> openframe - cluster --> k3d - app --> argocd + Bootstrap --> Cluster + Bootstrap --> App + Cluster --> K3d + Cluster --> EKS + Cluster --> GKE + App --> ArgoCD + + K3d --> Docker + EKS --> CloudAPI + GKE --> CloudAPI + ArgoCD --> K8sAPI ``` ---- - -## How It Works - -The CLI follows a layered architecture: - -1. **Command Layer** (`cmd/`) β€” Cobra-based subcommands with flag parsing and interactive wizards -2. **Service Layer** (`internal/*/service.go`) β€” Business logic orchestration -3. **Provider Layer** (`internal/*/providers/`) β€” K3D, ArgoCD, Helm, and Git integrations -4. **Shared Infrastructure** β€” Executor, k8s client, UI rendering, error handling, and secret redaction - -The **bootstrap** workflow ties it all together: - -```mermaid -sequenceDiagram - participant User - participant CLI as "openframe bootstrap" - participant K3D as "K3D Cluster" - participant ArgoCD as "ArgoCD" - participant OpenFrame as "OpenFrame Platform" - - User->>CLI: openframe bootstrap - CLI->>CLI: Validate prerequisites - CLI->>K3D: Create local cluster - K3D-->>CLI: Cluster ready - CLI->>ArgoCD: Install via Helm - ArgoCD-->>CLI: ArgoCD ready - CLI->>OpenFrame: Deploy app-of-apps chart - OpenFrame-->>CLI: All apps Healthy + Synced - CLI-->>User: Bootstrap complete! -``` - ---- - -## External Repository - -The OpenFrame platform configuration (Helm charts, values) lives in a separate repository: - -- **openframe-oss-tenant**: [https://github.com/flamingo-stack/openframe-oss-tenant](https://github.com/flamingo-stack/openframe-oss-tenant) -- Documentation: [https://github.com/flamingo-stack/openframe-oss-tenant/tree/main/docs](https://github.com/flamingo-stack/openframe-oss-tenant/tree/main/docs) - ---- - -## Community & Support - -Join the OpenMSP Slack community for questions, discussions, and support: - -https://www.openmsp.ai/ - -[![OpenMSP Slack](https://img.shields.io/badge/Slack-OpenMSP-blue)](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) - ---- - ## Next Steps -- Follow the [Prerequisites Guide](prerequisites.md) to prepare your environment -- Jump straight to the [Quick Start Guide](quick-start.md) for a 5-minute setup -- Read the [First Steps Guide](first-steps.md) to explore key features after installation +To get up and running with OpenFrame CLI, continue with: + +- The prerequisites guide, to confirm your system is ready +- The quick-start guide, for a 5-minute local install +- The first-steps guide, to explore key commands after installation diff --git a/docs/getting-started/prerequisites.md b/docs/getting-started/prerequisites.md index ab431ccc..d0e2b4b8 100644 --- a/docs/getting-started/prerequisites.md +++ b/docs/getting-started/prerequisites.md @@ -1,186 +1,70 @@ # Prerequisites -Before installing and using the OpenFrame CLI, ensure your environment meets the following requirements. The CLI can automatically check and install most prerequisites on macOS and Linux β€” on Windows, you will be guided to the relevant documentation. - ---- - -## System Requirements - -| Resource | Minimum | Recommended | -|---|---|---| -| **RAM** | 24 GB | 32 GB | -| **CPU Cores** | 6 cores | 12 cores | -| **Disk Space** | 50 GB free | 100 GB free | -| **Operating System** | macOS, Linux, Windows (WSL2) | macOS or Linux | - -> **Windows users:** The OpenFrame CLI runs natively on Windows but automatically forwards all operations into WSL2 (Windows Subsystem for Linux). You must have WSL2 installed and a Linux distro configured. The CLI will auto-install itself inside WSL when first run. - ---- +Before installing and using OpenFrame CLI, make sure your system meets the requirements below. OpenFrame CLI ships with a built-in `prerequisites` command that can check (and, on macOS/Linux, auto-install) most of these tools for you. ## Required Software -| Tool | Minimum Version | Purpose | Auto-Installed? | -|---|---|---|---| -| **Docker** | 24.x or newer | Container runtime for K3D clusters | βœ… macOS/Linux | -| **k3d** | 5.x or newer | Lightweight K3D cluster manager | βœ… macOS/Linux | -| **Helm** | 3.x or newer | Kubernetes package manager | βœ… macOS/Linux | -| **kubectl** | 1.28+ | Kubernetes CLI (optional β€” CLI uses client-go directly) | ❌ Manual | -| **WSL2** *(Windows only)* | Windows 10/11 | Linux environment on Windows | ❌ Manual | - -> **Note:** Docker, k3d, and Helm can be installed automatically by running `openframe prerequisites install`. On Windows, the CLI will display documentation links for each missing tool. +The exact tool set depends on which cluster type you plan to use (`k3d` for local, `eks` for AWS, `gke` for GCP). ---- - -## Operating System Details - -### macOS - -- macOS 12 (Monterey) or newer recommended -- [Docker Desktop for Mac](https://docs.docker.com/desktop/mac/install/) or [OrbStack](https://orbstack.dev/) required -- Homebrew is recommended for manual tool management - -### Linux +| Tool | Required for | Notes | +|---|---|---| +| Docker | `k3d` (local clusters) | Must be **installed and running** β€” the daemon, not just the CLI | +| k3d | `k3d` (local clusters) | Kubernetes-in-Docker; auto-installed via Homebrew (macOS) or a pinned, checksum-verified binary (Linux) | +| Helm | `k3d`, `eks`, `gke` | Used to install ArgoCD and the app-of-apps chart | +| Terraform | `eks`, `gke` (cloud clusters) | Minimum version `>= 1.15.0`; always installed as a pinned, SHA256-verified binary (not via package managers) | +| AWS CLI | `eks` | Required for identity/credential resolution against AWS | +| gcloud CLI + `gke-gcloud-auth-plugin` | `gke` | Required for GCP authentication and `kubectl` credential plugin support | +| infracost (optional) | cloud cluster cost estimates | Optional; if missing, a generic pricing hint is shown instead | -- Ubuntu 20.04+, Debian 11+, Fedora 36+, or any modern distribution -- Docker Engine (not just the CLI) must be running -- User must be in the `docker` group or have `sudo` access +> On **Windows**, native auto-install is not supported for local (`k3d`) prerequisites β€” the CLI forwards k3d/Docker-based cluster operations into WSL2 (Ubuntu). Cloud cluster provisioning (EKS/GKE via Terraform) works natively on Windows. -### Windows (via WSL2) +## System Requirements -- Windows 10 version 2004+ or Windows 11 -- WSL2 enabled: run `wsl --install` in PowerShell as Administrator -- A Linux distro installed (Ubuntu recommended): `wsl --install -d Ubuntu` -- Docker Desktop for Windows with WSL2 backend enabled +| Resource | Minimum | Recommended | +|---|---|---| +| RAM | 24 GB | 32 GB | +| CPU Cores | 6 | 12 | +| Disk Space | 50 GB | 100 GB | ---- +> These figures reflect running a full local OpenFrame platform install (ArgoCD + app-of-apps) inside a k3d cluster on your machine. Cloud cluster deployments (EKS/GKE) shift most resource consumption to the cloud provider, but the CLI host still needs enough local resources to run Docker, Terraform, and Helm operations. -## Account & Access Requirements +## Account / Access Requirements -| Requirement | Details | +| Cluster type | Access needed | |---|---| -| **GitHub Access** | Required for downloading the `openframe-oss-tenant` chart and for self-updates | -| **GitHub Token** *(optional)* | Set `OPENFRAME_GITHUB_TOKEN` or `GITHUB_TOKEN` to avoid rate limiting | -| **Internet Access** | Required for downloading charts, container images, and updates | - ---- +| `k3d` (local) | None β€” runs entirely on your local Docker daemon | +| `eks` | An AWS account with credentials configured (via the AWS CLI / environment) and permissions to create EKS clusters, VPCs, and related IAM resources | +| `gke` | A GCP project with billing enabled and permissions to create GKE clusters and related networking resources | ## Environment Variables -The following environment variables are recognized by the CLI: - -| Variable | Required | Description | +| Variable | Purpose | Required | |---|---|---| -| `OPENFRAME_GITHUB_TOKEN` | Optional | GitHub personal access token (avoids API rate limits) | -| `GITHUB_TOKEN` | Optional | Standard GitHub token (also accepted) | -| `OPENFRAME_WSL_DISTRO` | Windows only | Target WSL distro name (default: WSL default distro) | -| `OPENFRAME_NO_WSL_FORWARD` | Windows only | Disable WSL forwarding (unsupported; use at your own risk) | -| `OPENFRAME_UPDATE_INSECURE_SKIP_VERIFY` | Emergency only | Skip cosign signature verification during updates | -| `KUBECONFIG` | Optional | Path to kubeconfig file (default: `~/.kube/config`) | +| `OPENFRAME_UPDATE_INSECURE_SKIP_VERIFY` | Escape hatch to bypass cosign signature verification during self-update | No β€” not recommended, emergency use only | +| `OPENFRAME_AUTO_UPDATE` | Set to `1` to opt in to automatic daily update checks (skipped in CI/non-interactive shells) | No | ---- +Cloud credentials themselves (AWS/GCP) are picked up from your existing AWS CLI / gcloud CLI configuration rather than dedicated OpenFrame-specific environment variables. ## Verification Commands -Run these commands to verify your environment is ready before installing OpenFrame CLI: - -### Check Docker - -```bash -docker --version -docker ps -``` - -Expected output: Docker version and an empty container list (confirms Docker is running). - -### Check k3d - -```bash -k3d version -``` - -Expected output: `k3d version vX.Y.Z` - -### Check Helm - -```bash -helm version -``` - -Expected output: `version.BuildInfo{Version:"vX.Y.Z", ...}` - -### Check available memory - -```bash -# macOS -sysctl -n hw.memsize | awk '{print $1/1024/1024/1024 " GB"}' - -# Linux -free -h -``` - -Ensure at least 24 GB RAM is available. - -### Check disk space - -```bash -df -h . -``` - -Ensure at least 50 GB free on the relevant partition. - -### Run CLI prerequisite check (after installing OpenFrame CLI) +Once OpenFrame CLI is installed, verify your environment is ready before creating a cluster: ```bash +# Check prerequisites for the default (local k3d) cluster type openframe prerequisites check -``` - -This is the most comprehensive check β€” the CLI will display exactly what is missing and how to fix it. - ---- - -## Windows-Specific Setup -
-Expand Windows WSL2 Setup Steps +# Check prerequisites for an EKS cluster +openframe prerequisites check --type eks -**Step 1: Enable WSL2** +# Check prerequisites for a GKE cluster +openframe prerequisites check --type gke -Open PowerShell as Administrator and run: - -```bash -wsl --install -``` - -**Step 2: Install Ubuntu distro** - -```bash -wsl --install -d Ubuntu -``` - -**Step 3: Install Docker Desktop** - -Download and install [Docker Desktop for Windows](https://docs.docker.com/desktop/windows/install/). In Docker Desktop settings, enable: -- **WSL2 backend** (Settings β†’ General β†’ Use WSL2 based engine) -- **Ubuntu integration** (Settings β†’ Resources β†’ WSL Integration β†’ Ubuntu) - -**Step 4: Set WSL2 as default (if needed)** - -```bash -wsl --set-default-version 2 -wsl --set-default Ubuntu +# Auto-install missing tools where supported (macOS/Linux) +openframe prerequisites install --type k3d ``` -**Step 5: Download the Windows CLI binary** - -Download from: https://github.com/flamingo-stack/openframe-cli/releases/latest/download/openframe-cli_windows_amd64.zip - -Extract and run the `.exe` β€” the CLI will automatically forward into WSL2. - -
- ---- +If any tool is missing, `prerequisites check` returns a non-zero exit code and prints the exact `install --type ...` command to fix it, along with the reason (e.g., "installed but not running" for Docker) and a documentation link for manual setup where auto-install isn't available. ## Next Steps -- Proceed to the [Quick Start Guide](quick-start.md) to install and run OpenFrame CLI -- Return to the [Introduction](introduction.md) for a feature overview +Once your environment passes the prerequisites check, continue to the quick-start guide to create your first cluster and install the OpenFrame platform. diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index 0c778e41..83743c88 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -1,204 +1,93 @@ -# Quick Start Guide +# Quick Start -Get OpenFrame up and running in under 5 minutes. This guide covers the fastest path to a working OpenFrame environment. +This guide gets you from zero to a running local OpenFrame platform in about 5 minutes, using OpenFrame CLI's one-command `bootstrap` workflow. ---- +## TL;DR Installation -## TL;DR β€” 5-Minute Setup +### Windows -```bash -# 1. Download the CLI for your platform (see below) -# 2. Check prerequisites -openframe prerequisites check +Download the AMD64 build directly: -# 3. Bootstrap a full OpenFrame environment -openframe bootstrap +```text +https://github.com/flamingo-stack/openframe-cli/releases/latest/download/openframe-cli_windows_amd64.zip ``` -That's it. The `bootstrap` command creates a local K3D cluster, installs ArgoCD, deploys the full OpenFrame platform, and waits for everything to become healthy. +Unzip the archive and run the `openframe` executable the same way you would run any other installer/binary on your system. ---- +### macOS / Linux -## Step 1: Download the OpenFrame CLI +Download the platform-appropriate archive from the [Releases page](https://github.com/flamingo-stack/openframe-cli/releases/latest), unzip it, and place the `openframe` binary somewhere on your `$PATH` (e.g. `/usr/local/bin`). -Choose your platform: - -### macOS (Apple Silicon / Intel) +If you have a Go toolchain available, you can alternatively install directly from source: ```bash -# Apple Silicon (M1/M2/M3) -curl -L https://github.com/flamingo-stack/openframe-cli/releases/latest/download/openframe-cli_darwin_arm64.tar.gz | tar xz -sudo mv openframe /usr/local/bin/ - -# Intel -curl -L https://github.com/flamingo-stack/openframe-cli/releases/latest/download/openframe-cli_darwin_amd64.tar.gz | tar xz -sudo mv openframe /usr/local/bin/ +go install github.com/flamingo-stack/openframe-cli@latest ``` -### Linux (amd64) - -```bash -curl -L https://github.com/flamingo-stack/openframe-cli/releases/latest/download/openframe-cli_linux_amd64.tar.gz | tar xz -sudo mv openframe /usr/local/bin/ -``` - -### Windows (amd64) - -Download: https://github.com/flamingo-stack/openframe-cli/releases/latest/download/openframe-cli_windows_amd64.zip - -Extract the ZIP archive and run the `openframe.exe` β€” it will automatically forward commands into WSL2. - -### Browse All Releases - -Visit [https://github.com/flamingo-stack/openframe-cli/releases](https://github.com/flamingo-stack/openframe-cli/releases) for all available platform binaries. - ---- - -## Step 2: Verify Installation +### Verify the install ```bash openframe --version ``` -Expected output: +## Hello World: Your First Cluster + Platform Install -```text -openframe version v1.x.x (abc1234) built on 2024-xx-xx -``` - ---- - -## Step 3: Check Prerequisites +Once the binary is on your `$PATH`, verify prerequisites and bootstrap a local environment in one step: ```bash +# 1. Check that Docker/k3d/helm are ready (auto-installs on macOS/Linux where possible) openframe prerequisites check -``` -The CLI will inspect your environment and report the status of required tools: - -```text -βœ“ Docker - running -βœ“ k3d - v5.x.x -βœ“ Helm - v3.x.x -``` - -If any prerequisites are missing, install them automatically: - -```bash -openframe prerequisites install -``` - -> **Windows users:** Auto-install is not supported on native Windows. The CLI will display documentation links for each missing tool instead. - ---- - -## Step 4: Bootstrap OpenFrame - -Run the interactive bootstrap wizard: - -```bash +# 2. Bootstrap: creates a local k3d cluster AND installs the OpenFrame platform openframe bootstrap ``` -The wizard will guide you through: +`openframe bootstrap` runs interactively by default β€” it will: -1. **Cluster name** β€” the name for your local K3D cluster (default: `openframe-dev`) -2. **Configuration mode** β€” default settings or interactive customization -3. **Branch/version** β€” which OpenFrame release to deploy +1. Validate (or prompt for) a cluster name. +2. Create a local k3d cluster (Docker-backed Kubernetes-in-Docker). +3. Install ArgoCD via Helm. +4. Install the app-of-apps chart, which deploys the OpenFrame platform components. +5. Wait for all ArgoCD applications to reach a synced/healthy state. +6. Print a summary card with stage timings and access instructions. -To use all defaults without prompts (e.g. in CI): +For CI/automation, run it non-interactively (reusing an existing `openframe-helm-values.yaml`): ```bash openframe bootstrap --non-interactive ``` -### Expected Output +## Expected Output + +After a successful bootstrap, you should see a summary similar to: ```text - ___ ___ - / _ \ _ __ ___ _ __| _|_ __ __ _ _ __ ___ ___ -| | | | '_ \ / _ \ '_ \ |_| '__/ _` | '_ ` _ \ / _ \ -| |_| | |_) | __/ | | | _| | | (_| | | | | | | __/ - \___/| .__/ \___|_| |_|_| |_| \__,_|_| |_| |_|\___| - |_| - -βœ“ Prerequisites validated -βœ“ Creating cluster: openframe-dev -βœ“ Cluster ready -βœ“ Installing ArgoCD -βœ“ ArgoCD ready -βœ“ Deploying OpenFrame platform -βœ“ Waiting for applications... -βœ“ All applications Healthy + Synced - -Bootstrap complete! πŸŽ‰ +βœ“ Cluster created (k3d) +βœ“ ArgoCD installed +βœ“ app-of-apps synced and healthy +Bootstrap complete in Xm Ys + +ArgoCD access: + Username: admin + Password: ``` ---- - -## Step 5: Check Status - -After bootstrapping, verify everything is running: +Confirm everything is healthy: ```bash openframe app status ``` -```bash -openframe cluster status -``` - ---- +This reports cluster reachability plus the sync/health state of every ArgoCD-managed application, and a readiness summary. -## What Was Installed? - -After a successful `openframe bootstrap`, you have: - -| Component | Description | -|---|---| -| **K3D cluster** | A local lightweight Kubernetes cluster named `openframe-dev` | -| **ArgoCD** | GitOps continuous delivery engine managing your platform | -| **OpenFrame platform** | The full OSS tenant chart from [openframe-oss-tenant](https://github.com/flamingo-stack/openframe-oss-tenant) | - ---- - -## Common Next Actions - -After bootstrap completes, you may want to: +To view ArgoCD admin credentials and UI access instructions at any time: ```bash -# View all available commands -openframe --help - -# Check cluster list -openframe cluster list - -# Get access information openframe app access - -# Upgrade to a new OpenFrame version -openframe app upgrade - -# Keep the CLI itself up to date -openframe update ``` ---- - -## Troubleshooting Quick Fixes - -| Problem | Solution | -|---|---| -| `docker: command not found` | Install Docker: `openframe prerequisites install` | -| `connection refused` | Check `openframe cluster status` β€” the cluster may not be running | -| `context deadline exceeded` | Network/resource issue; wait and retry, or check system resources | -| Missing `openframe-helm-values.yaml` | The bootstrap wizard will create it for you in non-interactive mode | -| Permission denied on binary | `chmod +x /usr/local/bin/openframe` | - ---- - ## Next Steps -- Read the [First Steps Guide](first-steps.md) for what to explore after your first bootstrap -- Review the [Prerequisites Guide](prerequisites.md) if you encounter environment issues -- Visit the [OpenMSP community](https://www.openmsp.ai/) for help and discussion +- Follow the [First Steps guide](first-steps.md) to explore the platform, check status interactively, and learn common day-2 commands. +- Review the [Prerequisites guide](prerequisites.md) if any tool checks failed during bootstrap. +- Read the [Introduction](introduction.md) for a broader overview of what OpenFrame CLI manages. diff --git a/docs/reference/architecture/ecosystem.md b/docs/reference/architecture/ecosystem.md new file mode 100644 index 00000000..b39c2712 --- /dev/null +++ b/docs/reference/architecture/ecosystem.md @@ -0,0 +1,42 @@ +# Ecosystem: OpenFrame CLI + +> Generated from the repository graph by the Flamingo documentation pipeline. Do not edit by hand: it is rebuilt from the manifests and sources on every merge. + +## Role + +OpenFrame CLI is a **service** (go). + +## Published artifacts + +| Ecosystem | Artifact | Version | +|---|---|---| +| go | `github.com/flamingo-stack/openframe-cli` | n/a | + +## Upstream dependencies + +None recorded. + +## Downstream consumers + +None recorded. + +## Graph + +```mermaid +flowchart LR + r_77c173026005492ab4ebe180["OpenFrame CLI"] + class r_77c173026005492ab4ebe180 roleService + classDef roleLibrary stroke-width:2px + classDef roleService stroke-width:2px + classDef roleApp stroke-width:2px + classDef roleInfra stroke-dasharray:4 2 + classDef roleFork stroke-dasharray:2 2 + classDef roleUnknown stroke-dasharray:1 3 + classDef stateStale stroke-dasharray:6 3,stroke-width:3px + classDef stateMissing stroke-dasharray:2 4,stroke-width:1px +``` + +## Index state + +- Snapshot: `cdf742dbf1d7` on `main`, indexed 2026-09-18T00:57:58.033+00:00 +- Coverage: full (every other managed repository has a fresh graph, so a symbol with no consumer here has no consumer in the org) diff --git a/docs/reference/architecture/overview.md b/docs/reference/architecture/overview.md index 47484830..7f7beb0b 100644 --- a/docs/reference/architecture/overview.md +++ b/docs/reference/architecture/overview.md @@ -1,473 +1,321 @@ # openframe-cli Module Documentation -# OpenFrame CLI β€” Architecture Documentation +# OpenFrame CLI ## Overview -OpenFrame CLI is a modern, interactive command-line tool written in Go that bootstraps and manages OpenFrame Kubernetes environments. It orchestrates the full lifecycle of local K3D clusters, installs the OpenFrame platform via ArgoCD GitOps (using the public `openframe-oss-tenant` chart), and provides developer utilities β€” all from a single `openframe` binary with both interactive wizards and fully scriptable non-interactive modes. +OpenFrame CLI is a modern, interactive command-line tool for provisioning Kubernetes clusters (local k3d, or cloud GKE/EKS via Terraform) and deploying the OpenFrame platform onto them via ArgoCD's app-of-apps pattern. It manages the full lifecycle β€” prerequisites, cluster creation, chart installation, status monitoring, upgrades, and teardown β€” with both fully interactive wizards and non-interactive flags for CI/automation. ---- +The CLI is written in Go using Cobra for command routing and integrates directly with Kubernetes via `client-go`, shelling out to Helm, k3d, Terraform, and cloud provider CLIs (gcloud, aws) as needed. ## Architecture -### High-Level System Design +OpenFrame CLI is organized around three core abstractions β€” **cluster** (provisioning), **app** (platform deployment via ArgoCD), and **prerequisites** (tool verification/installation) β€” plus supporting shared infrastructure for UI, execution, and self-update. + +### Architecture Diagram ```mermaid graph TB - subgraph CLI["CLI Entry Point"] - main["main.go"] - root["cmd/root.go"] - end - - subgraph Commands["Command Layer"] - bootstrap["cmd/bootstrap"] - cluster_cmd["cmd/cluster"] - app_cmd["cmd/app"] - prereq_cmd["cmd/prerequisites"] - update_cmd["cmd/update"] - end - - subgraph Core["Core Services"] - bootstrap_svc["internal/bootstrap"] - cluster_svc["internal/cluster"] - chart_svc["internal/chart/services"] - prereq_fw["internal/prerequisites"] - selfupdate["internal/shared/selfupdate"] + subgraph "CLI Layer (cmd/)" + Bootstrap[bootstrap] + Cluster[cluster] + App[app] + Prereq[prerequisites] + Update[update] end - subgraph Providers["Providers"] - k3d_prov["cluster/providers/k3d"] - argocd_prov["chart/providers/argocd"] - helm_prov["chart/providers/helm"] - git_prov["chart/providers/git"] + subgraph "Domain Services (internal/)" + ClusterSvc["cluster.ClusterService"] + ChartSvc["chart/services.ChartService"] + AppStatus["app/status.Service"] + AppUninstall["app/uninstall.Service"] + PrereqFw["prerequisites.Runner"] + SelfUpdate["selfupdate.Updater"] end - subgraph Shared["Shared Infrastructure"] - executor["internal/shared/executor"] - k8s["internal/k8s"] - download["internal/shared/download"] - ui["internal/shared/ui"] - redact["internal/shared/redact"] - errors["internal/shared/errors"] + subgraph "Providers" + K3d["cluster/providers/k3d"] + EKS["cluster/providers/eks (terraform)"] + GKE["cluster/providers/gke (terraform)"] + ArgoCD["chart/providers/argocd"] + Helm["chart/providers/helm"] + Git["chart/providers/git"] end - subgraph External["External Tools & APIs"] - k3d_tool["K3D CLI"] - helm_tool["Helm CLI"] - argocd_cr["ArgoCD CRDs"] - github["GitHub API"] - git_repo["Git Repositories"] + subgraph "External Systems" + Docker[(Docker)] + K8sAPI[(Kubernetes API)] + CloudAPI[(GCP / AWS APIs)] + GitHub[(GitHub Releases)] end - main --> root - root --> Commands - bootstrap_cmd --> bootstrap_svc - cluster_cmd --> cluster_svc - app_cmd --> chart_svc - prereq_cmd --> prereq_fw - update_cmd --> selfupdate - - bootstrap_svc --> cluster_svc - bootstrap_svc --> chart_svc - - cluster_svc --> k3d_prov - chart_svc --> argocd_prov - chart_svc --> helm_prov - chart_svc --> git_prov - - k3d_prov --> executor - helm_prov --> executor - argocd_prov --> k8s - helm_prov --> k8s - - executor --> k3d_tool - executor --> helm_tool - argocd_prov --> argocd_cr - git_prov --> git_repo - selfupdate --> github - download --> github + Bootstrap --> ClusterSvc + Bootstrap --> ChartSvc + Cluster --> ClusterSvc + App --> ChartSvc + App --> AppStatus + App --> AppUninstall + Prereq --> PrereqFw + Update --> SelfUpdate + + ClusterSvc --> K3d + ClusterSvc --> EKS + ClusterSvc --> GKE + ChartSvc --> ArgoCD + ChartSvc --> Helm + ChartSvc --> Git + AppStatus --> ArgoCD + + K3d --> Docker + EKS --> CloudAPI + GKE --> CloudAPI + ArgoCD --> K8sAPI + Helm --> K8sAPI + SelfUpdate --> GitHub ``` ---- +The `internal/k8s` package deliberately isolates read/inspect access to an *existing* cluster (contexts, health, resources) from `internal/cluster`, which handles cluster *creation*. This lets `app install` target any reachable cluster β€” one made by `openframe cluster create`, or by the user directly. ## Core Components -| Package | Path | Responsibility | +| Component | Path | Responsibility | |---|---|---| -| **Root Command** | `cmd/root.go` | Cobra root; wires subcommands, global flags (`--verbose`, `--silent`), version info, WSL launcher | -| **Bootstrap Command** | `cmd/bootstrap/` | Orchestrates `cluster create` + `app install` as a single user-facing workflow | -| **Cluster Commands** | `cmd/cluster/` | Cobra subcommands: create, delete, list, status, cleanup | -| **App Commands** | `cmd/app/` | Cobra subcommands: install, upgrade, status, access, uninstall | -| **Prerequisites Command** | `cmd/prerequisites/` | Exposes `check` / `install` for Docker, k3d, helm | -| **Update Command** | `cmd/update/` | Self-update, rollback, update-check with cosign signature verification | -| **Bootstrap Service** | `internal/bootstrap/` | Coordinates cluster creation then chart installation end-to-end | -| **Cluster Service** | `internal/cluster/service.go` | Lifecycle operations (create, delete, list, status, cleanup) via the provider interface | -| **K3D Provider** | `internal/cluster/providers/k3d/` | K3D-specific cluster creation and management | -| **Cluster Provider Interface** | `internal/cluster/provider/` | Unified `Provider` interface; K3D satisfies it today | -| **Chart Services** | `internal/chart/services/` | High-level install workflow: prerequisites β†’ ArgoCD β†’ app-of-apps β†’ wait | -| **ArgoCD Provider** | `internal/chart/providers/argocd/` | Install, wait, refresh/sync, application management via native client-go dynamic client | -| **Helm Provider** | `internal/chart/providers/helm/` | Helm CLI wrapper; ArgoCD and app-of-apps installation | -| **Git Provider** | `internal/chart/providers/git/` | Shallow clone of chart repository using go-git (no `git` binary) | -| **App Status Service** | `internal/app/status/` | Aggregates cluster health + ArgoCD app status into a unified Report | -| **App Uninstall Service** | `internal/app/uninstall/` | Removes ArgoCD applications and Helm releases safely | -| **App Target Selector** | `internal/app/target/` | Interactive/non-interactive kube-context selection with resource check | -| **k8s Package** | `internal/k8s/` | Kubeconfig context loading, `rest.Config` construction, cluster health/resource checks | -| **Prerequisites Framework** | `internal/prerequisites/` | OS-aware check + auto-install runner (macOS/Linux auto-installs, Windows shows docs) | -| **Cluster Prerequisites** | `internal/cluster/prerequisites/` | Docker, k3d, helm prerequisite definitions and installer | -| **Chart Prerequisites** | `internal/chart/prerequisites/` | Helm, mkcert/certificates, memory prerequisite definitions | -| **Executor** | `internal/shared/executor/` | Command execution abstraction (real + mock); records argv for security testing | -| **Self-Update** | `internal/shared/selfupdate/` | GitHub release fetch, cosign signature verification, binary swap, rollback | -| **Download** | `internal/shared/download/` | Verified binary downloads (SHA256 + pinned versions) for k3d, mkcert, helm | -| **Redact** | `internal/shared/redact/` | Secret redaction from log/debug output | -| **WSL Launcher** | `internal/shared/wsllauncher/` | Re-runs the CLI inside WSL on Windows; auto-installs the Linux binary | -| **Platform** | `internal/platform/` | Host OS detection, per-tool install hints, WSL guidance errors | -| **Shared UI** | `internal/shared/ui/` | Logo, prompts, silent mode, status colors, selection menus | -| **Shared Config** | `internal/shared/config/` | `EnvBool`, TLS config for local clusters, system service | -| **Shared Errors** | `internal/shared/errors/` | Error types, friendly hints, retry policies, `AlreadyHandledError` sentinel | - ---- +| Root command | `cmd/root.go` | Cobra root, version metadata, global flags (`--silent`, `--verbose`, `--plain`), pinned-dependency reporting | +| Bootstrap | `cmd/bootstrap/`, `internal/bootstrap/` | One-shot `cluster create` + `app install` with a staged progress tracker | +| Cluster commands | `cmd/cluster/` | `create`, `delete`, `list`, `status`, `use`, `cleanup` subcommands | +| Cluster service | `internal/cluster/service.go` | Cluster lifecycle orchestration, provider dispatch, existing-cluster reuse logic | +| Cluster providers | `internal/cluster/providers/{k3d,eks,gke}` | Backend-specific cluster create/delete/status via Docker/k3d or Terraform | +| Cluster discovery | `internal/cluster/discovery/` | Finds cloud clusters outside the openframe registry (GKE/EKS), gcloud/AWS auth flows | +| Cluster prerequisites | `internal/cluster/prerequisites/` | Type-aware tool gates (Docker/k3d/helm for k3d; terraform+CLI for EKS/GKE) | +| App commands | `cmd/app/` | `install`, `upgrade`, `status`, `access`, `uninstall` subcommands | +| Chart services | `internal/chart/services/` | Orchestrates ArgoCD + app-of-apps install, validation, retries | +| ArgoCD provider | `internal/chart/providers/argocd/` | ArgoCD Helm install, application listing/sync, admin password, wait logic | +| Helm provider | `internal/chart/providers/helm/` | Helm CLI wrapper for install/upgrade/uninstall | +| Git provider | `internal/chart/providers/git/` | Clones the app-of-apps chart repository at a given ref | +| App status | `internal/app/status/` | Aggregates cluster health + ArgoCD app sync/health into a `Report` | +| App status TUI | `internal/app/status/tui/` | Interactive k9s-style bubbletea view for navigating/syncing apps | +| App uninstall | `internal/app/uninstall/` | Removes ArgoCD applications and Helm releases, keeping the cluster | +| Prerequisites framework | `internal/prerequisites/` | OS-aware `Prerequisite`/`Set`/`Runner` abstraction (auto-install on macOS/Linux, docs-only on Windows) | +| k8s access | `internal/k8s/` | Kubeconfig context resolution, `rest.Config` building, cluster health/resource checks | +| Platform hints | `internal/platform/` | Per-OS install guidance, Windows/WSL cluster-access error messaging | +| Shared executor | `internal/shared/executor/` | `CommandExecutor` abstraction (real + mock) for all shelled-out commands | +| Shared errors | `internal/shared/errors/` | Structured error handling, retry policy, friendly hints | +| Shared UI | `internal/shared/ui/` | Logo, spinners, prompts, glyphs, GitHub Actions annotations, silent/plain modes | +| Shared download | `internal/shared/download/` | Checksum-verified pinned-tool downloads (k3d, helm, mkcert, terraform, infracost) | +| Self-update | `internal/shared/selfupdate/` | Checks/applies CLI updates, cosign signature + checksum verification, rollback | +| WSL launcher | `internal/shared/wsllauncher/` | Forwards the native Windows binary into WSL2 for cluster operations | ## Component Relationships -### Dependency Flowchart +### Dependency Diagram ```mermaid -graph LR - subgraph Commands["cmd/"] - bootstrap["bootstrap"] - cluster_cmd["cluster/*"] - app_cmd["app/*"] - prereq_cmd["prerequisites"] - update_cmd["update"] +graph TB + subgraph cmd + CmdCluster[cmd/cluster] + CmdApp[cmd/app] + CmdBootstrap[cmd/bootstrap] + CmdPrereq[cmd/prerequisites] + CmdUpdate[cmd/update] end - subgraph Services["internal/"] - bsvc["bootstrap.Service"] - csvc["cluster.ClusterService"] - chsvc["chart/services.ChartService"] - appsvc["app/status + uninstall"] - prefw["prerequisites.Runner"] - supdater["selfupdate.Updater"] + subgraph internal_cluster["internal/cluster"] + ClusterService[service.go] + ClusterProvider[provider] + ClusterModels[models] end - subgraph Providers["Providers"] - k3dp["cluster/providers/k3d"] - argop["chart/providers/argocd.Manager"] - helmp["chart/providers/helm.HelmManager"] - gitp["chart/providers/git.Repository"] + subgraph internal_chart["internal/chart"] + ChartService[services] + ChartArgoCD[providers/argocd] + ChartHelm[providers/helm] + ChartGit[providers/git] end - subgraph Infra["Shared Infrastructure"] - exec["executor.CommandExecutor"] - k8spkg["k8s (rest.Config, Accessor)"] - dlpkg["download.Downloader"] - uipkg["shared/ui"] - errpkg["shared/errors"] - redactpkg["shared/redact"] + subgraph internal_app["internal/app"] + AppStatus[status] + AppUninstall[uninstall] end - bootstrap --> bsvc - cluster_cmd --> csvc - app_cmd --> chsvc - app_cmd --> appsvc - prereq_cmd --> prefw - update_cmd --> supdater - - bsvc --> csvc - bsvc --> chsvc - - csvc --> k3dp - chsvc --> argop - chsvc --> helmp - chsvc --> gitp - appsvc --> argop - - k3dp --> exec - helmp --> exec - argop --> k8spkg - helmp --> k8spkg - - prefw --> dlpkg - supdater --> dlpkg - - exec --> redactpkg - errpkg --> uipkg - chsvc --> errpkg - csvc --> errpkg -``` + subgraph internal_shared["internal/shared"] + Executor[executor] + Errors[errors] + UI[ui] + Download[download] + SelfUpdate[selfupdate] + end ---- + subgraph internal_k8s["internal/k8s"] + K8sAccess[accessor / restconfig / contexts] + end + + CmdBootstrap --> ClusterService + CmdBootstrap --> ChartService + CmdCluster --> ClusterService + CmdApp --> ChartService + CmdApp --> AppStatus + CmdApp --> AppUninstall + CmdApp --> K8sAccess + CmdPrereq --> internal_cluster + CmdUpdate --> SelfUpdate + + ClusterService --> ClusterProvider + ClusterService --> ClusterModels + ClusterProvider --> Executor + + ChartService --> ChartArgoCD + ChartService --> ChartHelm + ChartService --> ChartGit + ChartArgoCD --> K8sAccess + ChartHelm --> K8sAccess + + AppStatus --> ChartArgoCD + AppStatus --> K8sAccess + AppUninstall --> ChartArgoCD + AppUninstall --> ChartHelm + + ClusterService --> Errors + ChartService --> Errors + CmdCluster --> UI + CmdApp --> UI + ClusterProvider --> Download +``` ## Data Flow -### Bootstrap Sequence Diagram +### Bootstrap Sequence ```mermaid sequenceDiagram participant User - participant CLI as "openframe bootstrap" - participant BSvc as "bootstrap.Service" - participant CSvc as "cluster.Service" - participant K3D as "K3D Provider" - participant ChSvc as "chart/services" - participant Helm as "HelmManager" - participant Git as "git.Repository" - participant ArgoCD as "argocd.Manager" - participant K8s as "Kubernetes API" - - User->>CLI: openframe bootstrap [name] - CLI->>BSvc: Execute(cmd, args) - BSvc->>ChSvc: ValidateHelmValuesFile() - ChSvc-->>BSvc: OK / error - - BSvc->>CSvc: CreateClusterWithPrerequisites(ctx, name) - CSvc->>K3D: CreateCluster(ctx, config) - K3D-->>CSvc: rest.Config - CSvc-->>BSvc: rest.Config - - BSvc->>ChSvc: InstallChartsWithConfigContext(ctx, req) - ChSvc->>ChSvc: CheckAndInstallPrerequisites() - ChSvc->>Helm: InstallArgoCDWithProgress(ctx, cfg) - Helm->>K8s: helm upgrade --install argo-cd - K8s-->>Helm: OK - Helm->>K8s: waitForArgoCDDeployments() - K8s-->>Helm: Deployments ready - - ChSvc->>Git: CloneChartRepository(ctx, appConfig) - Git-->>ChSvc: CloneResult{tempDir, chartPath} - - ChSvc->>Helm: InstallAppOfAppsFromLocal(ctx, cfg) - Helm->>K8s: helm upgrade --install app-of-apps - K8s-->>Helm: OK - - ChSvc->>ArgoCD: WaitForApplications(ctx, cfg) - loop Every 2s until ready or timeout - ArgoCD->>K8s: List Applications (dynamic client) - K8s-->>ArgoCD: Application list - ArgoCD->>ArgoCD: assessApplications() - end - ArgoCD-->>ChSvc: All Healthy+Synced - - ChSvc-->>BSvc: OK - BSvc-->>User: Bootstrap complete + participant CLI as cmd/bootstrap + participant Boot as internal/bootstrap.Service + participant Cluster as internal/cluster.ClusterService + participant K3d as k3d provider + participant Chart as chart/services (Installer) + participant ArgoCD as ArgoCD provider + participant K8s as Kubernetes API + + User->>CLI: openframe bootstrap + CLI->>Boot: Execute(cmd, args) + Boot->>Chart: ValidateHelmValuesFile() + Boot->>Cluster: CreateCluster(config) + Cluster->>K3d: CreateCluster(ctx, config) + K3d->>K8s: provision cluster (Docker) + K3d-->>Cluster: rest.Config + Cluster-->>Boot: rest.Config + Boot->>Chart: InstallChartsWithConfigContext(req) + Chart->>ArgoCD: Install(ctx, config) + ArgoCD->>K8s: helm install argocd + Chart->>Chart: AppOfApps.Install (git clone + helm) + Chart->>ArgoCD: WaitForApplications(ctx, config) + ArgoCD->>K8s: poll Application CRs + K8s-->>ArgoCD: sync/health status + ArgoCD-->>Chart: ready + Chart-->>Boot: success + Boot-->>User: summary card (stages, timings, access hints) ``` -### App Install / Upgrade Data Flow +### App Status Aggregation ```mermaid sequenceDiagram participant User - participant AppCmd as "cmd/app/install" - participant Target as "app/target.Selector" - participant K8sPkg as "k8s package" - participant ChSvc as "chart/services" - participant ArgoProv as "argocd.Manager" - participant HelmProv as "helm.HelmManager" - - User->>AppCmd: openframe app install [--context k3d-dev] - AppCmd->>Target: Select(ctx) [if no --context] - Target->>K8sPkg: LoadContexts(kubeconfigPath) - K8sPkg-->>Target: []ContextInfo - Target->>User: Prompt: select context - User-->>Target: k3d-openframe-dev - Target->>K8sPkg: CheckResources(ctx, requirements) - K8sPkg-->>Target: Resources, sufficient=true - Target-->>AppCmd: SelectResult{Config, Context} - - AppCmd->>ChSvc: InstallChartsWithConfigContext(ctx, req) - ChSvc->>ArgoProv: Install(ctx, cfg) - ArgoProv-->>ChSvc: ArgoCD installed - ChSvc->>HelmProv: InstallAppOfAppsFromLocal(ctx, cfg) - HelmProv-->>ChSvc: app-of-apps installed - ChSvc->>ArgoProv: WaitForApplications(ctx, cfg) - ArgoProv-->>ChSvc: All apps Healthy+Synced - ChSvc-->>AppCmd: OK - AppCmd-->>User: SUCCESS + participant CLI as cmd/app.status + participant Svc as app/status.Service + participant Accessor as k8s.Accessor + participant ArgoCDMgr as argocd.Manager + participant K8s as Kubernetes API + + User->>CLI: openframe app status --watch + CLI->>Svc: Report(ctx, verbose) + Svc->>Accessor: CheckHealth(ctx) + Accessor->>K8s: list nodes + K8s-->>Accessor: node conditions + Svc->>ArgoCDMgr: ListApplications(ctx, verbose) + ArgoCDMgr->>K8s: list Application CRs + K8s-->>ArgoCDMgr: applications + Svc->>ArgoCDMgr: AdminPassword(ctx) + ArgoCDMgr->>K8s: read argocd-initial-admin-secret + Svc-->>CLI: Report{Health, Apps, Synced, Healthy} + CLI-->>User: table + readiness summary ``` ---- - ## Key Files | File | Purpose | |---|---| -| [`main.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/main.go) | Entry point; exits with child process exit code for automation fidelity | -| [`cmd/root.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/cmd/root.go) | Root Cobra command; wires all subcommands, persistent flags, version info, WSL launcher | -| [`cmd/bootstrap/bootstrap.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/cmd/bootstrap/bootstrap.go) | `openframe bootstrap` command: validates cluster name, delegates to bootstrap service | -| [`cmd/app/install.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/cmd/app/install.go) | `openframe app install`: flag parsing, context/target selection, request assembly | -| [`cmd/app/upgrade.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/cmd/app/upgrade.go) | `openframe app upgrade`: two modes (change-ref Mode 1, force-sync Mode 2) | -| [`cmd/update/update.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/cmd/update/update.go) | `openframe update`: self-update with cosign verification, rollback, update-check | -| [`internal/bootstrap/service.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/bootstrap/service.go) | Orchestrates pre-flight β†’ cluster create β†’ chart install end-to-end | -| [`internal/cluster/service.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/cluster/service.go) | `ClusterService`: lifecycle operations, `ApplicationCleaner` interface injection | -| [`internal/cluster/provider/provider.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/cluster/provider/provider.go) | `Provider` interface; compile-time assertion that K3D satisfies it | -| [`internal/chart/services/chart_service.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/chart/services/chart_service.go) | `ChartService`: top-level install orchestration, HelmManager wiring | -| [`internal/chart/services/preflight.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/chart/services/preflight.go) | Pre-flights `openframe-helm-values.yaml` before any cluster work begins | -| [`internal/chart/providers/argocd/applications.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/chart/providers/argocd/applications.go) | `Manager`: native client-go dynamic client for ArgoCD Application CRDs | -| [`internal/chart/providers/argocd/wait.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/chart/providers/argocd/wait.go) | `WaitForApplications`: stabilization loop, stall detection, repo-server recovery | -| [`internal/chart/providers/argocd/sync.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/chart/providers/argocd/sync.go) | `RefreshAndSync`: hard refresh + sync patches via dynamic client; group-ordered child sync | -| [`internal/chart/providers/argocd/values.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/chart/providers/argocd/values.go) | Embedded ArgoCD Helm values; deep-merge with user `argocd:` overrides; pre-flight validation | -| [`internal/chart/providers/argocd/stall.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/chart/providers/argocd/stall.go) | Per-application stall tracker; detects OutOfSync stragglers after ref changes | -| [`internal/chart/providers/argocd/fatalmanifest.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/chart/providers/argocd/fatalmanifest.go) | Fail-fast for deterministic manifest errors (missing chart path) | -| [`internal/chart/providers/argocd/refassert.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/chart/providers/argocd/refassert.go) | Verifies deployed git ref matches the requested ref; catches silent V3 failures | -| [`internal/chart/providers/helm/manager.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/chart/providers/helm/manager.go) | `HelmManager`: helm CLI execution, Kubernetes client for workload verification | -| [`internal/chart/providers/git/repository.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/chart/providers/git/repository.go) | go-git shallow clone; branchβ†’tag fallback; credential isolation | -| [`internal/k8s/accessor.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/k8s/accessor.go) | `Accessor`: cluster health (reachable, nodes ready) and resource sufficiency checks | -| [`internal/k8s/contexts.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/k8s/contexts.go) | Kubeconfig context loading, `ResolveContextForCluster` for k3d naming convention | -| [`internal/prerequisites/runner.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/prerequisites/runner.go) | OS-aware `Runner`: auto-installs on macOS/Linux, shows docs on Windows | -| [`internal/shared/executor/executor.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/shared/executor/executor.go) | `RealCommandExecutor`: runs external binaries; captures stderr for error enrichment | -| [`internal/shared/executor/mock.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/shared/executor/mock.go) | `MockCommandExecutor`: structured argv recording for security tests | -| [`internal/shared/selfupdate/update.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/shared/selfupdate/update.go) | Core update logic: GitHub release fetch, version comparison, binary swap | -| [`internal/shared/selfupdate/cosign.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/shared/selfupdate/cosign.go) | Sigstore/cosign signature verification against pinned GitHub Actions identity | -| [`internal/shared/download/pins.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/shared/download/pins.go) | Pinned tool versions + SHA256 for k3d, mkcert, helm; verified download infra | -| [`internal/shared/config/transport.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/shared/config/transport.go) | `ApplyInsecureTLSConfig`: bypasses TLS only for local/loopback clusters | -| [`internal/shared/errors/errors.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/shared/errors/errors.go) | `HandleGlobalError`, `AlreadyHandledError` sentinel, typed error handlers | -| [`internal/shared/errors/friendly.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/shared/errors/friendly.go) | `friendlyHint`: maps low-level errors to actionable user guidance | -| [`internal/shared/ui/silent.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/shared/ui/silent.go) | `SetSilent`: routes all non-error pterm printers to `io.Discard` | -| [`internal/shared/redact/redact.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/shared/redact/redact.go) | `Redact`: removes registered secrets and URL-embedded credentials from output | -| [`internal/shared/wsllauncher/launcher.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/shared/wsllauncher/launcher.go) | `Forward`: re-runs the whole CLI inside WSL on Windows; installs Linux binary if missing | -| [`internal/app/status/status.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/app/status/status.go) | `Report`: aggregates cluster health, ArgoCD app sync/health, admin password | -| [`internal/app/uninstall/uninstall.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/internal/app/uninstall/uninstall.go) | Removes Applications, Helm releases, and optionally the `argocd` namespace safely | -| [`tests/testutil/command_assertions.go`](https://github.com/flamingo-stack/openframe-cli/blob/main/tests/testutil/command_assertions.go) | Security-spec assertions on `RecordedCommand` (no secrets in argv, no shell injection) | - ---- +| `main.go` | Entry point; exit-code fidelity for automation, error sentinel handling | +| `cmd/root.go` | Root Cobra command, version resolution, pinned dependency reporting | +| `cmd/bootstrap/bootstrap.go` | `bootstrap` command definition and cluster-name validation | +| `internal/bootstrap/service.go` | Stage-tracked bootstrap orchestration (validate β†’ create β†’ install) | +| `cmd/cluster/cluster.go` | Cluster command group and prerequisite-gate dispatch logic | +| `internal/cluster/service.go` | `ClusterService` β€” create/reuse/detect-type logic across providers | +| `internal/cluster/provider/provider.go` | Unified `Provider`/`Planner` interfaces for k3d/EKS/GKE backends | +| `cmd/app/install.go` | `app install` β€” resolves target cluster context, builds `InstallationRequest` | +| `cmd/app/upgrade.go` | `app upgrade` β€” dual-mode (change ref vs. force re-sync) | +| `internal/chart/services/chart_service.go` | Core install orchestration, retry policy, file cleanup | +| `internal/chart/services/installer.go` | Sequences ArgoCD install β†’ app-of-apps install β†’ wait-for-apps | +| `internal/app/status/status.go` | `Report`/`Summary` aggregation logic for platform readiness | +| `internal/app/status/tui/tui.go` | Bubbletea interactive status dashboard | +| `internal/k8s/restconfig.go`, `internal/k8s/contexts.go` | Kubeconfig context resolution shared across app/cluster commands | +| `internal/prerequisites/runner.go` | OS-aware check/install runner for tool prerequisites | +| `internal/shared/selfupdate/update.go` | Version comparison, checksum + cosign-verified binary replacement | +| `internal/shared/download/verify.go` | Verified-download substrate (SHA256-checked pinned tool binaries) | +| `internal/shared/wsllauncher/launcher.go` | Forwards the native Windows CLI into WSL2 for cluster access | ## Dependencies -The project uses these key Go library dependencies: - -| Library | How It Is Used | -|---|---| -| **github.com/spf13/cobra** | CLI framework for all commands, flags, help generation, and completion | -| **github.com/pterm/pterm** | Rich terminal UI: spinners, tables, boxes, interactive prompts, color output | -| **github.com/charmbracelet/huh** | Interactive selection menus (with `/` filtering) and text input prompts in wizards | -| **github.com/charmbracelet/bubbletea** | TUI runtime: powers huh prompts and the interactive `app status` view | -| **k8s.io/client-go** | Native Kubernetes API access: kubeconfig loading, rest.Config, typed clients | -| **k8s.io/apimachinery** | Kubernetes API types, GVR definitions for ArgoCD Application CRDs | -| **k8s.io/apiextensions-apiserver** | CRD client for checking/managing ArgoCD CRD installation | -| **sigs.k8s.io/yaml** | YAML marshaling/unmarshaling (round-trips through JSON for consistent field names) | -| **github.com/go-git/go-git/v5** | Pure-Go git clone for the app-of-apps chart repository (no `git` binary required) | -| **github.com/sigstore/sigstore-go** | Cosign bundle parsing and Sigstore trust-root verification for self-update | -| **golang.org/x/mod/semver** | Semantic version comparison for self-update logic | -| **golang.org/x/term** | Terminal detection (`IsTerminal`) for non-interactive mode detection | -| **github.com/elastic/go-sysinfo** | Cross-platform total RAM query (no shell-outs to `sysctl`/`/proc`) | -| **k8s.io/apimachinery/pkg/util/wait** | `PollUntilContextTimeout` for resilient workload readiness polling | - ---- - -## CLI Commands - -### Global Flags - -| Flag | Description | -|---|---| -| `--verbose`, `-v` | Enable verbose/debug output | -| `--silent` | Suppress all output except errors | -| `--version` | Print version, commit, and build date | - -### Command Reference - -#### `openframe bootstrap` - -Creates a K3D cluster and installs the OpenFrame platform in a single step. - -```bash -openframe bootstrap # Interactive mode -openframe bootstrap my-cluster # Named cluster -openframe bootstrap --non-interactive # CI/CD mode (uses existing openframe-helm-values.yaml) -openframe bootstrap --verbose # Show detailed ArgoCD sync progress -``` - -#### `openframe cluster` - -| Subcommand | Description | Example | -|---|---|---| -| `create [NAME]` | Create a K3D cluster (wizard or flags) | `openframe cluster create dev --skip-wizard --nodes 1` | -| `delete [NAME]` | Delete a cluster and its resources | `openframe cluster delete dev --force` | -| `list` | List all managed clusters | `openframe cluster list -o json` | -| `status [NAME]` | Show detailed cluster status | `openframe cluster status dev -o yaml` | -| `cleanup [NAME]` | Prune unused container images from cluster nodes | `openframe cluster cleanup dev --force` | - -**`cluster create` flags:** - -```bash -openframe cluster create # Interactive wizard -openframe cluster create my-cluster # Named with wizard -openframe cluster create --skip-wizard # Defaults (k3d, 3 nodes) -openframe cluster create --type k3d --nodes 1 --skip-wizard -``` +OpenFrame CLI is a **service** published to the `go` ecosystem as `github.com/flamingo-stack/openframe-cli`. Per the ecosystem graph, it has no recorded upstream dependencies on other repositories in this organization's graph, and no recorded downstream consumers β€” it is a leaf/terminal artifact in the internal dependency graph. -#### `openframe app` +Its functional dependencies are external, third-party Go modules and CLI tools rather than sibling repositories in this ecosystem: -| Subcommand | Description | Example | -|---|---|---| -| `install [cluster]` | Install ArgoCD + app-of-apps | `openframe app install -c k3d-dev` | -| `upgrade [cluster]` | Re-sync or change git ref | `openframe app upgrade --ref v1.3.0` | -| `status` | Show platform readiness | `openframe app status -c k3d-dev -o json` | -| `access` | Print ArgoCD credentials | `openframe app access -c k3d-dev` | -| `uninstall` | Remove app (keep cluster) | `openframe app uninstall -c k3d-dev --yes` | +- **Cobra** (`spf13/cobra`) β€” command routing and flag parsing for the entire `cmd/` tree. +- **client-go** (`k8s.io/client-go`) β€” native Kubernetes API access (`internal/k8s`, ArgoCD/Helm providers), replacing shelling out to `kubectl`. +- **pterm** β€” all terminal rendering: tables, spinners, boxes, colored status printers (`internal/shared/ui`). +- **huh** (`charmbracelet/huh`) and **bubbletea** (`charmbracelet/bubbletea`) β€” interactive prompts/wizards and the `app status --interactive` TUI. +- **sigstore-go** (`sigstore/sigstore-go`) β€” cosign keyless signature verification for self-update integrity (`internal/shared/selfupdate/cosign.go`). +- **golang.org/x/mod/semver** β€” version comparison for self-update and auto-update logic. +- **External CLI tools invoked via `internal/shared/executor`**: Docker, k3d, Helm, Terraform, gcloud, aws β€” all shelled out through the `CommandExecutor` abstraction rather than linked as libraries, with binaries themselves verified and pinned via `internal/shared/download` (k3d, Helm, mkcert, Terraform, infracost). -**`app install` flags:** +Because the ecosystem graph shows no other repository depends on `openframe-cli`, its `internal/` packages are considered private implementation detail β€” there is no public Go API surface intended for import by other modules. -```bash -openframe app install # Interactive context picker -openframe app install -c k3d-openframe-dev # Explicit context -openframe app install --non-interactive # CI (reuse existing values file) -openframe app install --ref 1.0.48 # Deploy specific tag -openframe app install --dry-run # Preview only -``` - -**`app upgrade` modes:** - -```bash -openframe app upgrade # Force re-sync current ref (Mode 2) -openframe app upgrade --sync --prune # Re-sync + delete removed resources -openframe app upgrade --ref v1.4.0 # Change to new release tag (Mode 1) -openframe app upgrade -c k3d-dev --ref main # Target explicit context + ref -``` - -#### `openframe prerequisites` - -```bash -openframe prerequisites check # Report status, no changes -openframe prerequisites install # Install missing tools (macOS/Linux) -``` - -#### `openframe update` - -```bash -openframe update # Update to latest release -openframe update v1.4.0 # Switch to specific version (up or down) -openframe update check # Report availability only -openframe update check -o json # Machine-readable check -openframe update rollback # Revert to previous version (offline) -``` - -### Non-Interactive / CI Usage +## CLI Commands -Every destructive or interactive command supports scripted operation: +| Command | Description | +|---|---| +| `openframe bootstrap [cluster-name]` | Create a k3d cluster and install the OpenFrame platform in one step | +| `openframe cluster create [NAME]` | Create a k3d, GKE, or EKS cluster (interactive wizard or `--skip-wizard`) | +| `openframe cluster list` | List managed clusters (`--all` to include discovered external cloud clusters) | +| `openframe cluster status [NAME]` | Show cluster health, nodes, and status (supports `-o json\|yaml`) | +| `openframe cluster delete [NAME]` | Delete a cluster (typed-name confirmation required for cloud clusters) | +| `openframe cluster use [NAME]` | Switch kubectl context (and gcloud config, for GKE) to a cluster | +| `openframe cluster cleanup [NAME]` | Prune unused container images from cluster nodes | +| `openframe app install [cluster-name]` | Install ArgoCD and the app-of-apps onto a cluster | +| `openframe app upgrade [cluster-name]` | Change the deployed git ref (`--ref`) or force a re-sync (`--sync`) | +| `openframe app status` | Report platform readiness (`--watch`, `--interactive`, `-o json\|yaml`) | +| `openframe app access` | Print ArgoCD admin credentials and UI access instructions | +| `openframe app uninstall` | Remove ArgoCD + apps, keeping the cluster intact | +| `openframe prerequisites check\|install` | Verify or install required tools (`--type k3d\|eks\|gke`) | +| `openframe update [version]` | Update the CLI (`check`, `rollback` subcommands available) | + +### Usage Examples ```bash -# Fully non-interactive bootstrap -openframe bootstrap my-cluster --non-interactive - -# Force-delete without confirmation -openframe cluster delete my-cluster --force +# Local development +openframe bootstrap +openframe cluster status -# Install without prompts, output JSON for parsing -openframe app install -c k3d-dev --non-interactive -openframe app status -c k3d-dev -o json +# Cloud cluster (billed resources) +openframe cluster create my-gke --type gke --project my-project --region us-central1 --skip-wizard -# Uninstall without confirmation +# Platform lifecycle +openframe app install -c k3d-dev +openframe app status -c k3d-dev --watch +openframe app upgrade -c k3d-dev --ref v1.4.0 openframe app uninstall -c k3d-dev --yes -``` - -Non-interactive mode is also engaged automatically when `CI`, `GITHUB_ACTIONS`, `GITLAB_CI`, or `CIRCLECI` environment variables are set, or when `stdin` is not a terminal. ---- - -## Community and Support - -- **OpenMSP Slack**: [Join the community](https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA) β€” primary support channel -- **Releases**: [https://github.com/flamingo-stack/openframe-cli/releases](https://github.com/flamingo-stack/openframe-cli/releases) -- **OpenFrame Platform**: [https://openframe.ai](https://openframe.ai) -- **Flamingo**: [https://flamingo.run](https://flamingo.run) +# Keeping the CLI current +openframe update check +openframe update rollback +``` diff --git a/docs/reference/terminal-output.md b/docs/reference/terminal-output.md deleted file mode 100644 index 93caeaa4..00000000 --- a/docs/reference/terminal-output.md +++ /dev/null @@ -1,107 +0,0 @@ -# Terminal output reference - -The CLI picks an output mode per command from the terminal it runs in and the -flags it was given. Exactly one "live" surface is ever active at a time; every -other consumer gets sequential, log-friendly lines carrying the same -information. - -## Output modes - -| Mode | When | What you see | -|------|------|--------------| -| Live | interactive terminal, no `--verbose`/`--plain`/`--silent` | animated spinners, the in-place application dashboard, download progress bars | -| Sequential | redirected output, CI, `--plain`, `--verbose` | timestamped log lines: heartbeats with ready-deltas, stage lines, download begin/done announces | -| Silent | `--silent` | errors only | -| Machine | `-o json` / `-o yaml` | data on stdout, human warnings on stderr | - -## Live surfaces - -- **Stage checklist** (`bootstrap`) β€” each stage prints `β—‰ [2/3] Create - cluster`, closes with `βœ”`/`βœ–` and its duration, and the run ends with a - summary card: cluster + kube-context, per-stage timings, next commands. -- **Application dashboard** (install/bootstrap wait) β€” an in-place block with - an animated header, elapsed time, a progress bar (`14/17 ready`), and the - not-ready applications colored by health (red `Degraded`, yellow - `Progressing`), capped at 8 with `+N more`. One-off events (stall hints, - repo-server recovery notices) pin under the block as notes. The success - line reports which applications took longest to become ready. -- **`app status --watch`** β€” the platform status re-rendered in place every - 3 s; a failing poll shows its error inside the view and keeps watching. -- **`app status --interactive`** β€” a k9s-style TUI over the ArgoCD - applications: arrows/`j`/`k` navigate, `enter` opens the app detail (repo, - path, target ref, revision, conditions, operation state), `s` triggers a - per-app sync, `r` refreshes, `q` quits. Auto-refreshes every 3 s. -- **Download progress** β€” a self-rewriting line with a bar, percentage, and - speed for verified tool downloads. -- **Desktop notification** β€” long operations (bootstrap, install) emit an - OSC 9 notification plus a terminal bell on completion or failure, for the - user who switched to another window. - -`--watch` and `--interactive` require an interactive terminal and reject -machine output and `--plain`. - -## Sequential mode - -Where the live surfaces cannot run (redirected output, CI, `--plain`, -`--verbose`), the same information arrives as self-sufficient log lines: - -- **Wait heartbeat** (every 30 s, 10 s under `--verbose`): - `[12:34:05] apps 14/17 ready (+2 since last check) Β· elapsed 12m30s` with a - `pending: tenant(Progressing), gateway(Degraded)` detail line. A `+0` delta - makes a stall visible without diffing counts. -- **Interruption state** β€” Ctrl+C or a cancelled CI job during the wait - records `interrupted at 14/17 applications ready Β· pending: …` before the - cancellation error. -- **Download announces** β€” `Downloading helm-v3.16.2.tar.gz (52 MB)...` and - `Downloaded … in 4.2s` replace the live bar. -- **Phase heartbeats** β€” output-less blocking operations (`helm --wait`) - emit `