kongctl is a command-line interface (CLI) tool for managing Kong Konnect and (eventually) Kong Gateway on-prem. This tool is currently under heavy development and is released as Beta software.
Main documentation is published at: https://developer.konghq.com/kongctl/
This file provides repository-specific guidance for AI coding agents working with the kongctl codebase.
For declarative resource type implementation work, use
planning/DECLARATIVE_RESOURCE_IMPLEMENTATION_GUIDE.md as the primary guide.
kongctl is a command-line interface (CLI) tool for operating Kong Konnect and (eventually) Kong Gateway on-prem. The tool is currently in beta.
- Language: Go 1.26
- CLI Framework: Cobra for command-line processing
- Configuration: Viper for profile-based configuration management
- Project Size: Medium (~14 internal packages, comprehensive test suite)
main.go # CLI entrypoint; wires build info and IO
internal/cmd/ # Cobra commands and shared CLI helpers
internal/declarative/ # Declarative config engine
├── loader/ # Configuration loading and parsing
├── planner/ # Plan generation and diffing
├── executor/ # Execution of planned changes
└── validator/ # Configuration validation
internal/konnect/ # Konnect API integration
├── auth/ # Authentication and token management
├── httpclient/ # HTTP client with retry logic
└── helpers/ # API helper functions
internal/profile/ # Profile management
internal/iostreams/ # I/O stream handling
internal/util/ # Utility functions
internal/log/ # Structured logging
internal/build/ # Build information
docs/ # User documentation
test/ # Test code
├── integration/ # Integration tests (-tags=integration)
├── e2e/ # End-to-end tests (-tags=e2e)
│ └── harness/ # E2E test harness
├── cmd/ # Test helpers for commands
└── config/ # Test configuration utilities
Configuration Files:
Makefile: common tasks;.golangci.yml,.pre-commit-config.yaml: lint/format hooks..secrets.baseline: Baseline for detect-secrets toolgo.mod/go.sum: Go module dependencies
CRITICAL: CGO must be disabled for builds. Always use make build or set CGO_ENABLED=0.
CRITICAL: You have been given read/write access to base the directory given from go env
variables that determine cache and temp file writing for go lang tool chain.
You should not use /tmp folder for go cache or tmp file writing unless writing to
directories provided from go env fail.
# Build the main binary (CGO_ENABLED=0 handled by Makefile)
make build
# Run unit tests (ALWAYS run before committing)
make test
# Run all tests (unit + integration)
make test-all
# Run integration tests (requires -tags=integration)
make test-integration
# Run E2E tests (captures artifacts, requires -tags=e2e)
make test-e2e
# Auto-format code (REQUIRED before commit)
make format # or: make fmt
# Uses gofumpt and golines -m 120 for 120-char line wrapping
# Run linter (REQUIRED before PR)
make lint
# Generate coverage report
make coverageAll code changes must pass these gates in order:
- Modernize:
go fix ./...(apply Go 1.26 modernizations; no regressions) - Format check:
make format(must produce no changes) - Build check:
make build(must succeed) - Lint check:
make lint(zero issues) - Unit tests:
make test(all pass) - Integration tests:
make test-integration(when applicable)
Go Module Issues:
go mod tidy # Fix "missing go.sum entry" or dependency errors
goimports -w . # Fix broken importsBuild Failures:
go build -v ./... # Debug with verbose output
CGO_ENABLED=0 go build -o kongctl # Ensure CGO is disabledTest Failures:
go test -v ./path/to/package # Run specific test with verbose output
go test -race ./... # Check for race conditionsCommands follow a verb-noun pattern with konnect as the default product:
kongctl <verb> [product] <resource-type> [resource-name] [flags]
Examples:
kongctl get apis- List all APIs (konnect implied)kongctl get konnect apis- List all APIs (explicit)kongctl delete api my-api- Delete specific API
Command Implementation Pattern:
func newResourceCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "resource-name",
Short: "Brief description",
Long: "Detailed description",
RunE: runResourceCommand,
}
cmd.Flags().StringVar(&flagVar, "flag-name", "", "Description")
return cmd
}
func runResourceCommand(cmd *cobra.Command, args []string) error {
// Implementation - return errors, don't log
return nil
}CRITICAL: Always return errors, never log them in functions. Bubble errors to the highest level possible before reporting to user on STDERR.
// CORRECT: Return errors
func doOperation() error {
if err := someOperation(); err != nil {
return fmt.Errorf("operation failed: %w", err)
}
return nil
}
// WRONG: Don't log errors in functions
func doOperation() error {
if err := someOperation(); err != nil {
log.Error(err) // ❌ Don't do this
return err
}
return nil
}Uses Viper with profile-based configuration:
// Get configuration values
config := viper.GetViper()
token := config.GetString("konnect.token")
baseURL := config.GetString("konnect.base_url")
// Check for required configuration
if token == "" {
return fmt.Errorf("konnect token not configured")
}Configuration locations:
$XDG_CONFIG_HOME/kongctl/config.yaml$HOME/.config/kongctl/config.yaml- Environment variables:
KONGCTL_<PROFILE>_<PATH>
When binding cobra flags to config paths, use the bindFlag helper and a
bindings loop to avoid repetitive flag-lookup-and-bind blocks:
func bindFlag(
cfg config.Hook,
flags *pflag.FlagSet,
flagName, configPath string,
) error {
if f := flags.Lookup(flagName); f != nil {
return cfg.BindFlag(configPath, f)
}
return nil
}
func BindFlags(cfg config.Hook, flags *pflag.FlagSet) error {
if cfg == nil || flags == nil {
return nil
}
bindings := []struct{ flag, config string }{
{FlagName1, ConfigPath1},
{FlagName2, ConfigPath2},
}
for _, b := range bindings {
if err := bindFlag(cfg, flags, b.flag, b.config); err != nil {
return err
}
}
return nil
}Support multiple output formats consistently:
func outputResult(data interface{}, format string) error {
switch format {
case "json":
return json.NewEncoder(os.Stdout).Encode(data)
case "yaml":
return yaml.NewEncoder(os.Stdout).Encode(data)
default:
return outputAsText(data)
}
}The codebase has been fully modernized to Go 1.26 idioms using go fix. All
new code must conform to these standards, and go fix ./... should be run
after making changes to catch regressions.
Required idioms (enforced by linters and go fix):
- Use
anyinstead ofinterface{} - Use range-over-integer:
for i := range nnotfor i := 0; i < n; i++ - Use
slicespackage:slices.Contains,slices.Sort, etc. instead of manual loops orsort.Slice - Use
mapspackage:maps.Keys,maps.Values, etc. instead of explicit iteration loops that build slices - No redundant loop variable copies:
x := xinside a loop body is dead code in Go 1.22+ and must not be written - Use
strings.Builderwithfmt.Fprintf(&b, ...)notb.WriteString(fmt.Sprintf(...)) - Use
fmt.Appendfover[]byte(fmt.Sprintf(...)) - Use
min/maxbuiltins instead of if/else comparisons - Use
strings.Cutoverstrings.Index/strings.Splitfor splitting on a delimiter - Use
t.Context()in tests instead ofcontext.WithCancel
Available modernizers (run via go fix ./...):
any, forvar, fmtappendf, inline, mapsloop, minmax, rangeint,
slicescontains, slicessort, stringsbuilder, stringscut, stringsseq,
testingcontext, waitgroup
- All changes should pass coding standard gates (make format, make lint, make test-all).
- Coding changes are not complete until the gates pass
- Packages: lowercase, short, no underscores
- Exported identifiers: PascalCase; Internal identifiers: camelCase
- Error wrapping: Use
%wformat verb; avoid unused/unchecked errors (linters enforce) - Line length: Max 120 characters (enforced by golines)
- Comments: Only add if they match existing style or explain complexity
- Documentation markdown: Wrap at 80 characters
- Place tests in
*_test.gowithTestXxxfunctions. - Use
test/integration/...for API-backed flows (-tags=integration). - Use
test/e2e/...for CLI flows; harness lives intest/e2e/harness. - e2e test scenarios are preferred
test/e2e/scenarios - Keep tests deterministic; use provided helpers in
test/{cmd,config}. - Unit tests for core business functionality but only when necessary. Don't test other libaries or SDKs.
- Integration tests with the
-tags=integrationbuild tag - Test utilities in
test/directory
- Always: New CLI commands or subcommands
- Always: Authentication flow changes
- Always: Configuration management changes
- Often: API client modifications
- Unit tests sufficient: Pure functions, validation, string manipulation
- Skip: Documentation-only changes (unless doc has specific tests)
- Commits: concise subject, imperative mood; prefix scope when helpful
- Scopes:
cmd:,declarative:,konnect:,docs:,test:,ci: - Examples:
cmd: add support for API product filtering,docs: update getting started guide
- Scopes:
- PRs: include description, rationale, and linked issue; add tests and docs when applicable.
- CI hygiene: run
make test-all(lint, unit, integration) locally; attach E2E artifact path if relevant.
The repository runs these checks on PRs:
- checks.yaml: Basic validation and PR checks
- test.yaml: Unit and integration tests
- Runs
go test -race -count=1 ./... - Runs integration tests with
-tags=integration - Requires golangci-lint v2.10.1
- Runs
Install with: pre-commit install
Hooks include:
- YAML linting
- Secret detection (uses
.secrets.baseline) - File formatting checks
Run manually: pre-commit run -a
-
Device Flow (Recommended):
kongctl login- Tokens stored in
.<profile>-konnect-token.json - Supports token refresh
- Tokens stored in
-
Personal Access Token:
--patflag orKONGCTL_DEFAULT_KONNECT_PATenv var
- Install hooks:
pre-commit install; runpre-commit run -abefore pushing (YAML lint, secrets scan). - Avoid committing secrets;
detect-secretsuses.secrets.baseline. - Redact sensitive headers in logs (Authorization, X-Api-Key, tokens)
- Use trace logging (
--log-level trace) for debugging HTTP requests - Local auth/config lives under
$XDG_CONFIG_HOME/kongctl/. UseKONGCTL_PROFILEandKONGCTL_*env vars for tests.
Always return errors, don't log or capture within functions. Bubble errors to the highest level possible and report to user on STDERR.
func doOperation() error {
if err := someOperation(); err != nil {
return fmt.Errorf("operation failed: %w", err)
}
return nil
}
// In command functions, handle errors at the top level:
func runCommand(cmd *cobra.Command, args []string) error {
if err := doOperation(); err != nil {
return fmt.Errorf("command failed: %w", err)
}
return nil
}Kongctl is a Go-based CLI built with the following key components:
-
Command Structure: Uses Cobra for command-line processing with a verb-noun command pattern (e.g.,
get konnect gateway control-planes).- Ideally we will build these verb-noun commands following a "Konnect first" approach, meaning that the
konnectproduct will be implied in the command structure where possible.
- Ideally we will build these verb-noun commands following a "Konnect first" approach, meaning that the
-
Configuration Management:
- Uses Viper for configuration handling
- Supports profiles (default, dev, prod, etc.)
- Config file at
$XDG_CONFIG_HOME/kongctl/config.yamlor$HOME/.config/kongctl/config.yaml - Configuration can be overridden via environment variables or flags
-
Authentication:
- Supports both Personal Access Tokens (PAT) (--pat flag)
- browser-based device authorization flow which handles token storage, refresh, and expiration (kongctl login)
-
Command Organization:
- Root commands (verbs): get, list, create, delete, login, dump, apply, plan, diff
- Product namespaces: konnect, gateway, mesh (defaults to konnect for "Konnect First" approach)
- Resource types: apis, portals, control-planes, services, routes, etc.
-
I/O Handling:
- Supports multiple output formats (text, json, yaml)
- Configurable logging levels (trace, debug, info, warn, error) --log-level
-
Profile-Based Configuration: Commands are executed in the context of a profile, which determines which configuration values to use.
- Users can switch profiles using the
--profileflag or environment variableKONGCTL_PROFILE.
- Users can switch profiles using the
-
Error Handling: Structured error handling with consistent logging. In functions prefer to reeturn errors and defer handling to callers. Ideally errors are bubbled as high as possible in the call stack to provide context before reporting.
-
When writing markdown documentation, use the following conventions:
- Line width should be 80 characters or less
Enable trace logging to see HTTP requests/responses:
# Via flag
kongctl apply --plan plan.json --log-level trace
# Via config file (in $XDG_CONFIG_HOME/kongctl/config.yaml or $HOME/.config/kongctl/config.yaml)
log_level: trace
# Via environment variable
KONGCTL_LOG_LEVEL=trace kongctl apply --plan plan.json- Build failures: Ensure
CGO_ENABLED=0(usemake build) - Import errors: Run
go mod tidyandgoimports -w . - Test failures: Check for race conditions with
go test -race - Lint errors: Run
make formatbeforemake lint
- README.md: User-facing documentation and getting started guide
- docs/declarative.md: Declarative configuration guide
- docs/e2e.md: E2E test harness documentation
- docs/troubleshooting.md: Common issues and solutions