Thank you for contributing to OpenFrame CLI! This guide covers everything you need to know to submit high-quality contributions.
- Join the OpenMSP Slack community to discuss your ideas before starting large features.
- Read the Architecture documentation to understand the codebase.
- Set up your development environment and verify you can build and run locally.
OpenFrame CLI follows standard Go conventions:
gofmt/goimportsformatting is required — no unformatted code will be merged.go vetmust pass with no warnings.- Follow Effective Go and the Go Code Review Comments.
# Format and organize imports
goimports -w .
# Run vet
go vet ./...
# Run linter (if golangci-lint is configured)
golangci-lint run| Element | Convention | Example |
|---|---|---|
| Package names | Lowercase, single word | cluster, executor, redact |
| Exported types | PascalCase | ClusterService, CommandExecutor |
| Unexported types | camelCase | clusterManager, mockExecutor |
| Constants | PascalCase (exported), camelCase (unexported) | DefaultClusterName, maxRetries |
| Test files | _test.go suffix |
service_test.go |
| Test functions | Test prefix + PascalCase |
TestCreateClusterSuccess |
- Wrap errors with context using
fmt.Errorf("context: %w", err). - Use
shared/errorstypes for structured errors (CommandError,AlreadyHandledError). - Use
friendlyHintpatterns for user-facing errors. - Never swallow errors silently — return them or log them.
// GOOD: Wrap with context
if err := mgr.CreateCluster(ctx, cfg); err != nil {
return fmt.Errorf("creating cluster %q: %w", cfg.Name, err)
}
// BAD: Lost context
if err := mgr.CreateCluster(ctx, cfg); err != nil {
return err
}When adding a new Cobra command, follow the established pattern:
func getMyCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "mycommand [name]",
Short: "One-line 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),
RunE: func(cmd *cobra.Command, args []string) error {
// Validate input
// Delegate to service layer
// Handle errors via sharedErrors.HandleGlobalError
return nil
},
}
// Add flags
cmd.Flags().StringVar(&flagVar, "flag-name", "default", "Flag description")
return cmd
}- Services must accept interfaces (not concrete types) for all dependencies.
- Always accept
context.Contextas the first argument for cancellable operations. - Return descriptive errors, not boolean success flags.
- Use the
CommandExecutorinterface for all external binary invocations — neveros/execdirectly.
| Type | Pattern | Example |
|---|---|---|
| Feature | feature/<short-description> |
feature/add-kind-provider |
| Bug fix | fix/<short-description> |
fix/cluster-delete-timeout |
| Documentation | docs/<short-description> |
docs/update-contributing-guide |
| Refactor | refactor/<short-description> |
refactor/extract-helm-manager |
| Test | test/<short-description> |
test/add-bootstrap-integration |
| Chore | chore/<short-description> |
chore/update-go-dependencies |
Rules:
- Use lowercase and hyphens only (no underscores, no uppercase).
- Keep descriptions short and meaningful.
- Branch from
mainunless working on a specific release branch.
OpenFrame CLI uses Conventional Commits:
<type>(<scope>): <short description>
[optional body]
[optional footer(s)]
| Type | When to Use |
|---|---|
feat |
A new feature |
fix |
A bug fix |
docs |
Documentation changes only |
refactor |
Code change that neither fixes a bug nor adds a feature |
test |
Adding or modifying tests |
chore |
Build process, dependency updates, tooling |
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 for details.
| 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 |
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
test(bootstrap): add integration test for non-interactive mode
chore: upgrade go-git to v5.12.0
# 1. Ensure all tests pass
go test -race ./...
# 2. Format code
goimports -w .
# 3. Run vet
go vet ./...
# 4. Build successfully
go build -o openframe .
# 5. Test your changes manually
./openframe --help## Summary
<!-- What does this PR do? -->
## Changes
<!-- List the key changes made -->
-
-
## Testing
<!-- How was this tested? -->
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated (if applicable)
- [ ] Manual testing performed
## Checklist
- [ ] Code follows the style guidelines
- [ ] Self-review completed
- [ ] Tests pass (`go test -race ./...`)
- [ ] `go vet ./...` passes
- [ ] `goimports` formatting applied
- [ ] No secrets or credentials in code
- [ ] Security guidelines followed (secrets registered with redact, no shell injection)
| Size | Lines Changed | Guidance |
|---|---|---|
| Small | < 100 lines | Preferred — fast review |
| Medium | 100–500 lines | Include detailed description |
| Large | 500+ lines | Split into smaller PRs if possible |
When reviewing a PR, check:
Correctness:
- Logic is correct and handles edge cases
- Error paths are handled and tested
- Context cancellation is propagated correctly
Security:
- No secrets hardcoded or logged
- New credentials registered with
redact.RegisterSecret() - User input validated before reaching shell-outs
- External commands use argv arrays, not shell strings
Architecture:
- Business logic is in the service layer, not command layer
- Dependencies are injected via interfaces (testable)
- New command follows the established Cobra pattern
Tests:
- Unit tests cover new code paths
- Error cases are tested
- Mock executor used instead of real subprocess calls in unit tests
Documentation:
- Public API has Go doc comments
- Complex logic has inline comments
-
--helptext is accurate and helpful
- Create the command file in
cmd/<group>/<command>.go. - Define a
get<Name>Cmd()function returning*cobra.Command. - Register it in the parent command group (e.g.,
cmd/cluster/cluster.go). - Create a service in
internal/<group>/with injected dependencies. - Write unit tests using
testutil.TestClusterCommand. - Add integration tests if the command interacts with external systems.
- Verify
--helpoutput is accurate and descriptive.
To add a new cluster provider (e.g., Kind):
- Implement the
Providerinterface ininternal/cluster/providers/<name>/manager.go. - Add prerequisite definitions in
internal/cluster/prerequisites/. - Register the provider in the cluster service provider resolution.
- Add the cluster type to
internal/cluster/models/cluster.go. - Write unit and integration tests.
If your change touches credentials, downloads, or self-update code:
- 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
CommandExecutorwith 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 documentation for the full set of practices.
Run all unit tests:
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):
go test ./tests/integration/...See the Testing documentation for the full testing strategy, including mocked unit tests and standardized per-command test coverage via TestClusterCommand.
All contribution discussions happen in the OpenMSP Slack. 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
- OpenFrame platform repo: https://github.com/flamingo-stack/openframe-oss-tenant
- Releases: https://github.com/flamingo-stack/openframe-cli/releases
- Pull Requests: https://github.com/flamingo-stack/openframe-cli/pulls