Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Apollo CLI

中文

apollo-cli is the standalone Rust repository for the apollo command-line interface.

This repository currently covers the first Apollo CLI v0 slices:

  • top-level v0 command routing
  • global flag parsing
  • output formatting foundation
  • non-secret profile configuration
  • runtime context resolution
  • credential-store abstraction and auth commands
  • conservative redaction foundation
  • representative Apollo Portal OpenAPI calls under /openapi/v1/*
  • raw OpenAPI passthrough through apollo api
  • confirmation protection for mutating commands
  • local developer workflow and CI entrypoints

The package is intentionally self-contained and movable. It lives in this standalone repository for review, but avoids repo-specific assumptions so the crate can be split, vendored, or republished without Apollo server-side changes.

It does not implement generated OpenAPI SDK bindings, agent sessions, MCP, or server-side schema changes in this slice.

Command groups

  • auth
  • profile
  • app
  • env
  • namespace
  • config
  • release
  • api

Representative v0 commands:

apollo init
apollo profile add dev
apollo profile add prod --use
apollo app list
apollo app get sample-app
apollo env list --app sample-app
apollo namespace list --env DEV --app sample-app
apollo namespace get --env DEV --app sample-app application
apollo namespace create --env DEV --app sample-app application --comment "app settings" --yes
apollo config list --env DEV --app sample-app
apollo config get --env DEV --app sample-app timeout
apollo config set --env DEV --app sample-app timeout 3000 --type 1 --yes
apollo config delete --env DEV --app sample-app timeout --yes
apollo config diff --env DEV --app sample-app --target-env FAT
apollo config apply --env DEV --app sample-app --target-env FAT --yes
apollo release list --env DEV --app sample-app
apollo release create --env DEV --app sample-app --title "release title" --yes
apollo release rollback --env DEV 123 --yes
apollo api get /openapi/v1/apps
apollo api post /openapi/v1/apps --body '{"app":{"appId":"sample-app"}}' --yes

These commands use existing Apollo Portal OpenAPI endpoints only. Deprecated Portal WebAPI endpoints are intentionally not used. apollo namespace create registers the AppNamespace first and then creates the namespace in the requested environment and cluster. It creates a private AppNamespace by default. Pass --public only when the namespace should be public. File namespace formats are inferred from .json, .yml, .yaml, and .xml suffixes; other names default to properties. --comment is stored on the AppNamespace. Public AppNamespace registration sends Apollo's appendNamespacePrefix=true default; pass --no-append-namespace-prefix when the namespace name must be stored without Apollo's public namespace prefix behavior.

apollo config set sends Apollo's OpenItemDTO.type field. The default is 0 for a string item. Apollo Portal also uses 1 for number, 2 for boolean, and 3 for JSON; these values are validated client-side before the OpenAPI request is sent.

Global flags

The current scaffold parses these global flags before subcommands:

  • --profile
  • --server
  • --output json|table
  • --yes

--yes explicitly approves a mutating OpenAPI request without an interactive prompt. It does not skip target-plan construction, validation, redaction, or operation reporting.

Guided setup

Use apollo init for first-time setup. It creates a profile, writes non-secret profile metadata to config.toml, and can store an Apollo OpenAPI token through the credential-store abstraction. For Apollo versions with Portal user access tokens, user-token auth is the recommended mode for interactive users, AI agents, and personal automation.

For local Apollo assembly testing:

apollo --output json init --store-token-in-file
apollo profile show
apollo env list --app sample-app

By default, apollo init creates a local profile with:

  • server = "http://127.0.0.1:8070"
  • no persisted output unless --output is passed
  • auth_mode = "user-token"
  • no operator; user-token OpenAPI requests use the token owner as the operator
  • active_profile = "local"

Use apollo profile add to add more environments without hand-editing config:

printf '%s\n' "$DEV_TOKEN" | apollo \
  --server https://apollo-dev.example.com \
  --output json \
  profile add dev \
  --token-stdin

apollo profile add prod --server https://apollo-prod.example.com --use
apollo profile add legacy --server https://apollo-legacy.example.com --auth-mode consumer-token --operator alice

profile add does not switch the active profile by default. Pass --use when the newly added profile should become active. Existing profiles are protected from accidental replacement; pass --overwrite to replace one intentionally.

Profile config

The CLI stores non-secret profile metadata in config.toml under the OS config directory:

  • macOS: ~/Library/Application Support/apollo/config.toml
  • Linux: $XDG_CONFIG_HOME/apollo/config.toml or ~/.config/apollo/config.toml
  • Windows: %APPDATA%\apollo\config.toml

Set APOLLO_CLI_HOME to an absolute directory to place config.toml and file-backed credentials there instead. This is intended for isolated CI and smoke runs; normal interactive use should keep the platform default.

The config file stores:

  • active_profile
  • profile name
  • profile server
  • profile output
  • profile auth_mode, either user-token or consumer-token
  • optional operator
  • optional credential lookup metadata, such as backend/key names

Tokens are intentionally not part of the supported config schema. Prefer apollo init, apollo profile add, and apollo auth login over editing this file by hand.

If auth_mode is missing, the CLI treats the profile as consumer-token for backward compatibility with existing configs.

Example:

active_profile = "dev"

[profiles.dev]
server = "https://apollo-dev.example.com"
output = "table"
auth_mode = "user-token"

[profiles.dev.credential]
backend = "native"
key = "dev"

Profile commands

  • apollo init
  • apollo profile add [name]
  • apollo profile list
  • apollo profile show
  • apollo profile use <name>

Runtime context resolution follows this order:

  1. explicit flags such as --profile, --server, --output
  2. environment variables such as APOLLO_PROFILE, APOLLO_SERVER, APOLLO_OUTPUT
  3. active profile config
  4. command defaults

Auth commands

  • apollo auth login
  • apollo auth login --token-stdin
  • apollo auth login --token-stdin --store-token-in-file
  • apollo auth status
  • apollo auth whoami
  • apollo auth capabilities
  • apollo auth logout

Credential storage uses an internal store abstraction with these logical providers:

  • native: default credential backend, implemented through the OS credential store via the Rust keyring crate
  • env: read-only CI/headless provider through APOLLO_TOKEN
  • file: explicit fallback enabled only with --store-token-in-file
  • in-memory provider for unit tests

Native backend selection follows the OS behavior of the underlying credential store:

  • macOS: Keychain Services
  • Windows: Credential Manager
  • Linux desktop: freedesktop Secret Service compatible providers
  • Linux headless/CI: use APOLLO_TOKEN or explicitly opt into file fallback

File fallback writes token material outside config.toml under the CLI credentials directory and uses restrictive file permissions on Unix. The profile config stores only non-secret credential lookup metadata.

OpenAPI commands support two token modes:

  • user-token: recommended for interactive users, AI agents, and local automation. Tokens start with apollo_pat_ and are sent as Authorization: Bearer <token>.
  • consumer-token: compatibility mode for existing integrations and older Apollo deployments. Tokens are sent as the raw Authorization: <token> header value.

apollo init and apollo profile add default to user-token. Use --auth-mode consumer-token when configuring legacy consumer-token credentials. Mutating commands require a configured operator only in consumer-token mode; user-token requests use the owning Portal user.

apollo env list, apollo namespace list, apollo namespace get, apollo config get, apollo config list, apollo config diff, and apollo config apply read env, namespace, or configuration item data whose scope can be narrower than the app. Use user-token mode for these commands. Legacy consumer-token mode cannot safely verify env/namespace-level read scope from the available /openapi/v1/apps/authorized response, so the CLI rejects these scoped-read workflows instead of relying on app-level visibility alone.

For local or CI use, APOLLO_TOKEN takes precedence and is never written to disk:

APOLLO_TOKEN="$TOKEN" apollo --server http://localhost:8070 app list --output json

When APOLLO_TOKEN starts with apollo_pat_, the CLI automatically treats it as user-token; otherwise it uses consumer-token compatibility mode.

apollo auth logout removes the credential referenced by the selected profile. It cannot remove APOLLO_TOKEN from the parent shell environment. When APOLLO_TOKEN is still set, logout reports that environment credentials will continue to apply; run unset APOLLO_TOKEN to disable that temporary credential.

For interactive use, configure a profile and store the token with hidden input:

apollo --profile dev auth login
apollo --profile dev app list

auth login stores the token in the OS credential store by default. If the native store is not available in an interactive terminal, the CLI asks whether to use the local file fallback instead.

For scripts or manual paste-with-enter workflows, --token-stdin reads one token line:

printf '%s\n' "$TOKEN" | apollo --profile dev auth login --token-stdin
apollo --profile dev auth login --token-stdin
printf '%s\n' "$LEGACY_CONSUMER_TOKEN" | apollo --profile legacy auth login --auth-mode consumer-token --token-stdin

Use user-token self-check commands after login:

apollo --profile dev auth whoami
apollo --profile dev auth capabilities

These commands call /openapi/v1/user-tokens/current and /openapi/v1/user-tokens/current/capabilities. They require user-token auth mode and do not create, rotate, or revoke tokens; user token creation remains a Portal self-service flow.

Redaction and Errors

The output layer applies conservative redaction to human and JSON output before rendering. Token-like fields, Authorization: Bearer ... headers, and consumer token ... text are rendered as [REDACTED].

Structured JSON errors include:

  • code: stable error code
  • category: stable category
  • message: human-readable message
  • optional non-sensitive details such as command, profile, path, or operation

Current error categories:

  • authentication_failed
  • permission_denied
  • invalid_input
  • not_found
  • conflict
  • precondition_failed
  • network
  • server
  • confirmation_required
  • unsupported_operation

Process exit statuses are stable at the following level:

  • 0: success
  • 1: runtime or operation failure, including authentication, validation, network/server, and confirmation failures
  • 2: command-line parse or usage failure

Use the structured JSON error.code and error.category fields when automation needs a more specific failure reason than the process exit status.

Mutation safety

Before a built-in namespace, config, release, or raw API mutation, the CLI constructs a redacted operation plan from the selected profile/server and the command target. Plans include the fields available for that operation, such as app, env, cluster, namespace, config key/count, release IDs, or a sanitized raw OpenAPI method and path. Config values, request bodies, query values, tokens, and Authorization headers are not included.

In interactive table mode, a mutation without --yes writes the plan and a [y/N] prompt to stderr. Only y or yes executes the request; n, no, blank input, or EOF rejects it. In non-interactive mode and in JSON mode, mutations require --yes; otherwise the CLI returns a confirmation_required error whose operation field contains the redacted plan. Rejection occurs before any OpenAPI request is sent.

Namespace creation may perform read-only preflight requests after that initial approval. If Apollo resolves a different effective namespace name, such as adding an organization prefix to a public namespace, the CLI shows the resolved plan and requires approval again before either mutation is sent. If the selected profile, server, or output mode changes after approval, the CLI aborts before sending an OpenAPI request and asks the caller to review the new context.

With --yes, table mode still writes the plan before the request. A successful JSON response stays one valid JSON document and preserves the existing top-level status and data fields while adding the top-level operation plan.

Config synchronization contract

config diff and config apply use a conservative merge contract. Source-only keys are created, keys whose source value or comment differs are updated, matching keys are unchanged, and target-only keys are preserved. An empty source is therefore a successful no-op, not a request to empty the target. The CLI does not currently provide --prune; deletion requires a separate explicit config deletion workflow. If a Portal version reports delete operations from the synchronize diff endpoint, the CLI rejects the plan and does not call items/synchronize.

Both table and JSON output report the source and target scopes plus create, update, delete, and unchanged counts. The JSON contract also reports strategy: "merge" and targetOnlyBehavior: "preserve". It never includes config values in the diff result, apply plan, or apply result.

A standalone config diff is advisory; it does not create a plan artifact for a later invocation. config apply captures its own fully paginated source snapshot, assesses that exact snapshot through items/diff, captures the fully paginated target state, and builds the detailed mutation plan from the returned change set. After approval, it reads the target again and repeats the assessment with the same captured source snapshot. If either the target item state or assessment changed, the command returns stale_plan and sends no synchronize request. The initial approval intentionally occurs before these preflight reads; the detailed approval covers the resulting change counts. If all counts are zero, the command returns the deterministic data.result: "no-op" success response without calling items/synchronize.

This stale-plan check is optimistic and best-effort. The current Apollo items/synchronize OpenAPI contract has no target revision, ETag, or conditional-write precondition, so a target write after the final check can still race with synchronization. Eliminating that window requires a contract-first Apollo OpenAPI and server change that validates the target revision atomically with the item update. Callers that require exclusive writes must coordinate them outside the current CLI workflow.

OpenAPI behavior

The first v0 implementation uses a small generic HTTP client instead of a generated SDK. This keeps the CLI independent from the Apollo server repository while still constraining all built-in resource commands to /openapi/v1/*.

Path and payload mapping follows the current Apollo Portal OpenAPI contract, including:

  • GET /openapi/v1/apps
  • GET /openapi/v1/envs
  • GET /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces
  • GET|PUT|DELETE /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/{key}
  • POST /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/diff
  • POST /openapi/v1/apps/{appId}/appnamespaces for AppNamespace registration
  • POST /openapi/v1/namespaces
  • POST /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/synchronize
  • GET /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/active
  • POST /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases
  • PUT /openapi/v1/envs/{env}/releases/{releaseId}/rollback

Mutating command confirmation and operation-plan behavior are described in Mutation safety.

Binary releases

Manual release runs publish prebuilt apollo executables on the GitHub Releases page:

Platform Rust target Archive
Linux x86-64 x86_64-unknown-linux-gnu .tar.gz
Linux ARM64 aarch64-unknown-linux-gnu .tar.gz
macOS Intel x86_64-apple-darwin .tar.gz
macOS Apple silicon aarch64-apple-darwin .tar.gz
Windows x86-64 x86_64-pc-windows-msvc .zip

Archive names follow apollo-<tag>-<target>.<extension>. Each archive contains the executable, license, and English and Chinese READMEs. Releases also include SHA256SUMS and GitHub build provenance attestations for every uploaded archive and checksum file.

For example, download and verify a release with the GitHub CLI:

version=v0.1.0
mkdir "apollo-${version}"
gh release download "${version}" \
  --repo apolloconfig/apollo-cli \
  --dir "apollo-${version}"
(cd "apollo-${version}" && sha256sum --check SHA256SUMS)
gh attestation verify \
  "apollo-${version}/apollo-${version}-x86_64-unknown-linux-gnu.tar.gz" \
  --repo apolloconfig/apollo-cli

On macOS, use shasum -a 256 -c SHA256SUMS for the checksum step.

For maintainers, first merge the intended package version and updated Cargo.lock into the default branch. Then open Actions → Release → Run workflow, keep the default branch selected, and enter the SemVer without a leading v. The same release can be started with the GitHub CLI:

gh workflow run release.yml \
  --repo apolloconfig/apollo-cli \
  --ref main \
  -f version=0.1.0

The workflow rejects malformed versions, versions that do not match Cargo.toml, non-default-branch runs, and versions whose tag or Release already exists. It reruns formatting, Clippy, and tests; builds and smoke-tests all five native targets; and creates checksums and attestations. Only after those checks pass does it create the v<version> tag at the exact workflow commit, generate release notes, verify the complete draft asset set, and publish the Release. Versions containing a SemVer prerelease suffix are published as prereleases.

Before starting a release, maintainers must also run Actions → Apollo mutation smoke → Run workflow on the intended default-branch commit and require a successful result. The workflow is also scheduled weekly. It builds a pinned Apollo revision with Portal, ConfigService, AdminService, and disposable H2 databases, then exercises the real mutation contract described below. The Apollo pin is deliberately stored in scripts/mutation-smoke.sh; update it only as a reviewed source change.

Local development

Build the CLI:

cargo build

Run the help output:

cargo run -- --help

Run tests:

cargo test

Run focused OpenAPI command integration tests with the local mock HTTP server:

cargo test --test openapi

Run the repeatable real-Portal mutation smoke from a clean checkout (requires Git, curl, jq, awk, sha256sum or shasum, JDK 17, and the stable Rust toolchain):

./scripts/mutation-smoke.sh

The command fetches the pinned Apollo revision, builds its single-process assembly, starts Portal + H2, builds the CLI, creates an isolated APOLLO_CLI_HOME profile and disposable app/namespaces, and validates config diff/apply, confirmation rejection, permission failure, release creation/listing, and rollback against real server state. Target-only keys are verified as preserved under the merge contract, and no-op state is compared before and after the command. User tokens and config values stay in a mode-0700 temporary directory, failure diagnostics are dynamically redacted, and the directory and assembly process are always removed. To reuse an already checked-out clean Apollo tree at the same pinned commit:

APOLLO_SMOKE_APOLLO_SOURCE=/absolute/path/to/apollo ./scripts/mutation-smoke.sh

If you have a local Apollo Portal running, you can also smoke-test against it:

APOLLO_TOKEN="$TOKEN" cargo run -- --server http://localhost:8070 --output json env list --app sample-app
APOLLO_TOKEN="$TOKEN" cargo run -- --server http://localhost:8070 --output json app list
APOLLO_TOKEN="$USER_TOKEN" cargo run -- --server http://localhost:8070 --output json auth whoami

Format the repository:

cargo fmt

Lint the repository:

cargo clippy --all-targets --all-features -- -D warnings

Repository layout

  • src/cli.rs: CLI definition and flag parsing
  • src/config.rs: profile config loading, saving, and context resolution
  • src/command.rs: top-level command routing
  • src/credential.rs: credential-store abstraction and providers
  • src/error.rs: structured CLI error model
  • src/http.rs: generic OpenAPI HTTP client and path helpers
  • src/output.rs: output rendering abstractions
  • src/redaction.rs: conservative redaction utilities
  • tests/auth.rs: integration coverage for auth commands and credential behavior
  • tests/cli.rs: integration coverage for help, flags, and structured errors
  • tests/openapi.rs: integration coverage for OpenAPI paths, auth headers, and confirmation guards
  • tests/profile.rs: integration coverage for profile commands and context resolution
  • tests/redaction.rs: integration coverage for redaction behavior
  • scripts/mutation-smoke.sh: pinned Apollo Portal + H2 mutation smoke and state assertions

About

Official OpenAPI-based Apollo CLI

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages