Skip to content

FUN-8 funops - Operator CLI

Pre-Discussion

1. Introduction

This FUN introduces funops, a command-line interface tool for Fundament platform operators. The tool provides direct database access for administrative tasks that require root-level system access, such as bootstrapping new organizations or performing system recovery operations.

2. Why funops

2.1. The Need for Operator Tooling

Fundament exposes APIs for end-users (used by Console UI, Terraform provider), but certain administrative operations require elevated privileges beyond what tenant-scoped APIs provide:

  • Bootstrap operations: Creating the initial organization before any users exist

  • System recovery: Fixing data inconsistencies or recovering from failures

  • Administrative tasks: Operations that span multiple organizations or require system-level access

  • Debugging: Inspecting system state during development or incident response

2.2. Direct Database Access

For the initial proof-of-concept, funops operates directly against the PostgreSQL database rather than through the existing APIs. This approach provides:

  • Simplicity: No additional API surface to design and maintain

  • Full access: No RLS restrictions when using a privileged database user

  • Speed of development: Faster iteration for PoC needs

This is not necessarily a permanent decision. Future versions may transition to API-based operations where appropriate, particularly for operations that benefit from audit logging or can be safely exposed to less privileged operators.

3. Design

3.1. CLI Philosophy

funops follows conventions established by similar cloud-native CLI tools:

Tool Pattern

kubectl

kubectl <verb> <resource> (e.g., kubectl get pods)

metalctl

metalctl <resource> <action> (e.g., metalctl machine list)

gardenctl

gardenctl <action> <resource> (e.g., gardenctl target shoot)

funops adopts the resource-centric pattern:

funops <resource> <action> [arguments] [flags]

Examples:

funops organization create acme-corp
funops organization create acme-corp --output json

3.2. Non-Interactive Design

funops is designed to be non-interactive and scriptable:

  • All input comes from command-line arguments and flags

  • No interactive prompts or manifest files

  • Suitable for automation, CI/CD pipelines, and scripting

  • Clear exit codes for success/failure

This differs from kubectl’s declarative manifest approach. Fundament operators use imperative commands for administrative tasks rather than applying YAML files.

3.3. Output Formats

funops supports multiple output formats via the --output / -o flag:

Format Description

table (default)

Human-readable tabular format for terminal use

json

Machine-readable JSON for scripting and automation

3.4. Configuration

funops uses environment variables for configuration, following the 12-factor app methodology:

Variable Required Description

DATABASE_URL

Yes

PostgreSQL connection string (e.g., postgres://user:pass@host:5432/fundament)

No configuration files are used. This keeps the tool simple and explicit about its dependencies.

3.5. Database Access

funops connects directly to the Fundament PostgreSQL database. The database user specified in DATABASE_URL should have appropriate permissions:

  • For full operator access: Use a PostgreSQL user that has explicit RLS policies granting full access, or a superuser role

  • RLS policies in the Fundament schema can be configured to allow specific users full access, or the bypassrls attribute

Initial development uses the superuser role and bypassrls attributes.

3.6. Logging

funops uses structured logging via Go’s slog package, with output directed to stderr:

  • Default level: INFO - Shows user feedback for operations that would otherwise be silent (e.g., successful deletes)

  • Debug level (--debug) - Includes detailed internal operations like database connections and query parameters

This design keeps stdout clean for programmatic use (piping, scripting) while providing appropriate feedback to operators. Commands that produce data output (list, create) write to stdout; informational messages go to stderr via the logger.