Knowledge Hub
Comprehensive guides and references for the OpenFrame platform
OpenFrame Gen1 is Here ยท Our AI platform for autonomous IT is out of beta.
Comprehensive guides and references for the OpenFrame platform
Thank you for your interest in contributing to OpenFrame CLI! This document covers everything you need to know to submit high-quality contributions.
Note: 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
| 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 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 .
# Verify
./openframe --version
# 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
goimports -w .
# Run vet
go vet ./...
# Run linter
golangci-lint run
OpenFrame CLI follows standard Go conventions:
gofmt / goimports formatting is required โ no unformatted code will be mergedgo vet must pass with no warnings| 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 and use structured types from shared/errors:
// 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
},
}
cmd.Flags().StringVar(&flagVar, "flag-name", "default", "Flag description")
return cmd
}
context.Context as the first argument for cancellable operationsCommandExecutor interface for all external binary invocations โ never os/exec directlyAny credential read from environment variables, config files, or user prompts must be registered with the redact package before use:
import "github.com/flamingo-stack/openframe-cli/internal/shared/redact"
redact.RegisterSecret(githubToken)
redact.RegisterSecret(registryPassword)
Always use argv arrays via CommandExecutor, never shell string concatenation:
// 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)
ValidateClusterNameredact.RegisterSecret()| 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: Lowercase and hyphens only. Branch from main unless 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 |
feat(cluster): add --wait flag to cluster create command
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
# 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
Use the MockCommandExecutor for isolated unit tests โ never invoke real subprocesses:
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)
}
| Package Type | Target |
|---|---|
Core services (internal/) |
โฅ 80% |
Command layer (cmd/) |
โฅ 70% |
| Provider implementations | โฅ 75% |
| Shared utilities | โฅ 85% |
# 1. Run all tests
go test -race ./...
# 2. Format code
goimports -w .
# 3. Run vet
go vet ./...
# 4. Build successfully
go build -o openframe .
## 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
| 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 |
Follow these steps when adding a new CLI command:
cmd/<group>/<command>.goget<Name>Cmd() function returning *cobra.Commandcmd/cluster/cluster.go)internal/<group>/ with injected dependenciestestutil.TestClusterCommand--help output is accurate and descriptiveTo add a new cluster provider (e.g., Kind):
Provider interface in internal/cluster/providers/<name>/manager.gointernal/cluster/prerequisites/internal/cluster/models/cluster.goWhen reviewing a PR, check:
Correctness:
Security:
redact.RegisterSecret()Architecture:
Tests:
Documentation:
--help text is accurate and helpfulRelease binaries are code-signed automatically during the release workflow:
| Platform | Mechanism |
|---|---|
| macOS | codesign (Developer ID Application, hardened runtime) + notarytool notarization |
| Windows | Authenticode via Azure Trusted Signing |
| Linux | Integrity via checksums.txt + cosign bundle |
All release binaries can be verified using cosign:
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