diff --git a/.cspell.json b/.cspell.json new file mode 100644 index 0000000..9a578de --- /dev/null +++ b/.cspell.json @@ -0,0 +1,58 @@ +{ + "language": "en", + "dictionaries": ["companies", "filetypes", "fullstack", "softwareTerms"], + "words": [ + "aquasecurity", + "artipacked", + "cimd", + "clidocs", + "coverprofile", + "cpuprof", + "ehthumbs", + "goarch", + "gofmt", + "golangci", + "goreleaser", + "kics", + "ldflags", + "lfx", + "lfxv", + "linuxfoundation", + "memprof", + "techdocs", + "urfave", + "zizmor" + ], + "flagWords": [ + "abort", + "abortion", + "blackhat", + "black-hat", + "whitehat", + "white-hat", + "cripple", + "crippled", + "master", + "slave", + "tribe", + "sanity-check", + "whitelist", + "white-list", + "blacklist", + "black-list" + ], + "ignorePaths": [ + ".cspell.json", + "CODEOWNERS", + "LICENSE", + "LICENSE-docs", + "megalinter-reports", + "styles" + ], + "languageSettings": [ + { + "languageId": "markdown", + "ignoreRegExpList": ["/^\\s*```\\s*(mermaid)[\\s\\S]*?^\\s*```/gm"] + } + ] +} diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..601d9f4 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,53 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +--- +name: Build + +"on": + pull_request: null + +permissions: + contents: read + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + build: + # With GO_GOLANGCI_LINT disabled in MegaLinter, that workflow's + # revive-only run never compiles the Go packages, so this build/vet/test + # job is required to catch code that does not build. + name: Build and Test + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Set up Go + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + with: + go-version-file: go.mod + + - name: Format check + # gofmt -l lists unformatted files without rewriting them; fail if + # any are found instead of silently reformatting like `go fmt`. + run: | + unformatted=$(gofmt -l .) + if [ -n "$unformatted" ]; then + echo "The following files are not gofmt-formatted:" + echo "$unformatted" + exit 1 + fi + + - name: Build + run: go build ./... + + - name: Vet + run: go vet ./... + + - name: Test + run: go test ./... diff --git a/.github/workflows/license-header-check.yml b/.github/workflows/license-header-check.yml index ba4ba68..1026e8f 100644 --- a/.github/workflows/license-header-check.yml +++ b/.github/workflows/license-header-check.yml @@ -13,6 +13,9 @@ permissions: jobs: license-header-check: name: License Header Check - uses: linuxfoundation/lfx-public-workflows/.github/workflows/license-header-check.yml@main + # Pinned to main as of 2026-07-14 (no tagged releases are published for + # this repo); bump the SHA periodically to pick up upstream fixes. + uses: >- + linuxfoundation/lfx-public-workflows/.github/workflows/license-header-check.yml@8313b4b18b95d750f99a78d5ea160d851afbc40b with: copyright_line: "Copyright The Linux Foundation and each contributor to LFX." diff --git a/.github/workflows/mega-linter.yml b/.github/workflows/mega-linter.yml new file mode 100644 index 0000000..66a695b --- /dev/null +++ b/.github/workflows/mega-linter.yml @@ -0,0 +1,47 @@ +--- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT + +name: MegaLinter + +"on": + pull_request: null + +permissions: + contents: read + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + megalinter: + name: MegaLinter + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + # Git Checkout + - name: Checkout Code + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-depth: 0 + persist-credentials: false + + # MegaLinter + - name: MegaLinter + id: ml + # Use the Go flavor. Keep this version in sync with the + # `megalinter` target in the Makefile. + uses: oxsecurity/megalinter/flavors/go@ef3e84b8b836d76db562d0f3ed7da61e8fd538bc # v9.6.0 + env: + # All available variables are described in documentation + # https://megalinter.io/latest/configuration/ + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Allow GITHUB_TOKEN for working around rate limits (aquasecurity/trivy#7668). + REPOSITORY_TRIVY_UNSECURED_ENV_VARIABLES: GITHUB_TOKEN + # zizmor's "artipacked" audit needs the GitHub API (to verify + # SHA-pinned action tags), which requires GITHUB_TOKEN. + ACTION_ZIZMOR_UNSECURED_ENV_VARIABLES: GITHUB_TOKEN diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml new file mode 100644 index 0000000..05dc550 --- /dev/null +++ b/.github/workflows/release-tag.yml @@ -0,0 +1,45 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +--- +name: Publish Tagged Release + +"on": + push: + tags: + - "v*" + +permissions: {} + +jobs: + goreleaser: + name: GoReleaser + runs-on: ubuntu-latest + + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Go + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + with: + go-version-file: go.mod + # Disable caching: this workflow publishes runtime artifacts on + # tag push, so a poisoned cache from an earlier run could leak + # into a release build (zizmor cache-poisoning audit). + cache: false + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 + with: + # Constrained to v2 (the action's documented default) so a + # future GoReleaser v3 release doesn't silently break old tags. + version: "~> v2" + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9374fa0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,122 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT + +# Binaries and executables +/bin/ +/dist/ +*.exe +*.exe~ +*.dll +*.so +*.dylib +/lfx + +# Go build artifacts +*.test +*.prof +*.pprof + +# Go coverage files +coverage.out +coverage.html +coverage.txt +*.coverage +c.out + +# Go module cache (when using vendor) +/vendor/ + +# Go workspace file +go.work +go.work.sum + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store +Thumbs.db + +# Claude AI assistant +.claude + +# Environment and configuration files +.env +.env.local +.env.*.local +*.env +config.local.yaml +config.local.yml +secrets.yaml +secrets.yml + +# Log files +*.log +logs/ +*.log.* + +# Temporary files +*.tmp +*.temp +/tmp/ +.cache/ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Security and sensitive files +*.pem +*.key +*.crt +*.p12 +*.pfx +ca-certificates.crt +tls.crt +tls.key + +# Backup files +*.bak +*.backup +*.old +*~ + +# Profiling and debugging +*.pprof +pprof/ +*.memprof +*.cpuprof + +# Database files +*.db +*.sqlite +*.sqlite3 + +# Archive files +*.tar +*.tar.gz +*.zip +*.rar +*.7z + +# CI/CD temporary files +.github/workflows/*.tmp +.gitlab-ci.tmp +.travis.tmp + +# Local development overrides +Makefile.local + +# Generated documentation +docs/_build/ +site/ + +# Lint files +megalinter-reports diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..81533d8 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,52 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT + +version: 2 + +before: + hooks: + - go mod tidy + +builds: + - id: lfx + main: ./cmd/lfx + binary: lfx + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + ignore: + - goos: windows + goarch: arm64 + ldflags: + - -s -w -X main.version={{.Version}} + +archives: + - id: lfx + formats: [tar.gz] + format_overrides: + - goos: windows + formats: [zip] + name_template: >- + {{ .ProjectName }}_{{ .Os }}_{{ .Arch }} + +checksum: + name_template: "checksums.txt" + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + - "^ci:" + +release: + github: + owner: linuxfoundation + name: lfx-cli diff --git a/.lycheeignore b/.lycheeignore new file mode 100644 index 0000000..fa175a2 --- /dev/null +++ b/.lycheeignore @@ -0,0 +1,6 @@ +# Ignore local Kubernetes paths. +^https?://[a-zA-Z0-9.-]+\.svc\.cluster\.local + +# megalinter.io redirects (e.g. /configuration/ -> /latest/configuration/) and +# has been flaky/404-prone for lychee; not worth failing CI over. +^https?://megalinter\.io/ diff --git a/.mega-linter.yml b/.mega-linter.yml new file mode 100644 index 0000000..b93d824 --- /dev/null +++ b/.mega-linter.yml @@ -0,0 +1,23 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +--- +GITHUB_COMMENT_REPORTER: false +DISABLE_LINTERS: + # Prefer Revive. + - GO_GOLANGCI_LINT + # yamllint is sufficient for us. + - YAML_PRETTIER +DISABLE_ERRORS_LINTERS: + # Currently disabled upstream by MegaLinter itself (supply chain + # compromise). Keep it non-blocking here so a future upstream restore + # doesn't unexpectedly break the build before we've reviewed it. + - REPOSITORY_KICS + # There's no easy local feedback loop for spelling issues before + # pushing, so keep them non-blocking to avoid unnecessary PR cycles + # for words that just need adding to the dictionary. + - SPELL_CSPELL +YAML_YAMLLINT_CONFIG_FILE: .yamllint +SPELL_CSPELL_ANALYZE_FILE_NAMES: false +# Ignore YAML files with templating macros; these typically fail linting and/or +# schema checking. +FILTER_REGEX_EXCLUDE: '(templates/.*\.yml|templates/.*\.yaml)' diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..acd4017 --- /dev/null +++ b/.yamllint @@ -0,0 +1,11 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +--- +extends: default +ignore: | + .git + megalinter-reports +rules: + line-length: + max: 120 + level: warning diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9755211 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,210 @@ +# AGENTS.md + +This file provides essential information for AI agents working on the LFX +CLI codebase. It focuses on development workflows, architecture +understanding, and build processes needed for making code changes. + +## Repository Overview + +The LFX CLI (`lfx`) is a developer-facing command-line tool for +authenticating with the Linux Foundation's LFX platform and making +authenticated API calls, following the same interaction model as the `gh` +CLI (`lfx auth login` → `lfx auth token`). + +### Key Technologies + +- **Language**: Go (see `go.mod` for the minimum required version) +- **CLI framework**: [`urfave/cli/v3`](https://github.com/urfave/cli) for + subcommand routing +- **Docs generation**: [`urfave/cli-docs/v3`](https://github.com/urfave/cli-docs) + for LLM/agent-friendly Markdown reference docs (`lfx docs`) +- **Release automation**: [GoReleaser](https://goreleaser.com/) for + multi-arch binary builds published to GitHub Releases + +## Architecture Overview + +```text +lfx-cli/ +├── cmd/ +│ └── lfx/ # Main application entry point +├── internal/ +│ └── commands/ # CLI subcommand implementations +├── .goreleaser.yaml # Multi-arch release build configuration +├── go.mod # Go module definition +├── Makefile # Build automation +├── README.md # User documentation +└── AGENTS.md # This file (AI agent guidelines) +``` + +### Current State + +This repo is under active scaffolding. Auth and API commands are currently +stubs; real implementations land in follow-on work: + +- `lfx auth login` / `status` / `logout` +- `lfx auth token` +- `lfx api` + +Credential storage (system keychain via `99designs/keyring`) and the Auth0 +CIMD client are tracked separately. + +**No container build**: this project produces binary artifacts only, +distributed via GitHub Releases and `go install`. There is no Dockerfile, +Helm chart, or container image pipeline. + +## Development Workflow + +### Prerequisites + +```bash +# Verify Go is installed at or above the version in go.mod. +go version +``` + +### Common Development Tasks + +#### 1. Build the CLI + +```bash +make build +# or directly: go build -ldflags="-s -w" -o bin/lfx ./cmd/lfx +``` + +#### 2. Run the CLI + +```bash +make run ARGS="auth login" +# or directly: ./bin/lfx auth login +``` + +#### 3. Code Quality Checks + +```bash +make fmt # Format code +make vet # Run go vet +make lint # Run golangci-lint (if installed) +make revive # Run revive (if installed) +make goreleaser-check # Validate .goreleaser.yaml (if goreleaser installed) +make check # Run all of the above +``` + +#### 4. Tests + +```bash +make test # Run Go tests +make test-coverage # Run tests with coverage report +``` + +#### 5. Clean Build Artifacts + +```bash +make clean +``` + +## Adding New Commands + +Commands are implemented in `internal/commands` and registered with the +root `*cli.Command` in `cmd/lfx/main.go`. + +### Command Implementation Steps + +1. **Create or extend a file** in `internal/commands/` (e.g., `auth.go` for + an `auth` subcommand group) +2. **Implement a `NewCommand()` function** that returns a + `*cli.Command`, with nested `Commands` for subcommand groups +3. **Register it** in `cmd/lfx/main.go`'s `Commands` slice + +### Example Command Implementation + +```go +// Package commands implements the lfx CLI subcommands. +package commands + +import ( + "context" + "fmt" + + "github.com/urfave/cli/v3" +) + +// NewExampleCommand builds the `lfx example` command. +func NewExampleCommand() *cli.Command { + return &cli.Command{ + Name: "example", + Usage: "Brief description of what the command does", + Action: func(_ context.Context, cmd *cli.Command) error { + fmt.Println("example: not yet implemented") + return nil + }, + } +} +``` + +### Package Comments + +Every file in a package must start with the same `// Package ...` +doc comment immediately above the `package` declaration. Revive's +`package-comments` rule itself only requires one such comment per package, +but MegaLinter's `GO_REVIVE` linter defaults to `GO_REVIVE_CLI_LINT_MODE: +list_of_files`, invoking revive with a flat list of files instead of +`./...`. Under that mode revive loses per-package grouping and flags any +file lacking the comment, so duplicating the identical comment across +every file in a package is a required workaround for how MegaLinter calls +revive here, not an inherent revive requirement. Do not vary the wording +between files in the same package. + +## Documentation Generation + +The hidden `lfx docs` command generates Markdown reference documentation +for all commands via `cli-docs.ToMarkdown()`, intended for local agent use +and future publishing to the `gh-pages` branch: + +```bash +lfx docs # Print to stdout +lfx docs --output ./docs # Write to ./docs/cli.md +``` + +## Release Process + +Releases follow [semantic versioning](https://semver.org/) (`vMAJOR.MINOR.PATCH`). +The current series is v0.x; do not increment the major version unless +explicitly instructed. + +### Version bump guidelines + +| Change type | Version component | +|--------------------------------------------------------------------------------------|----------------------------| +| Bug fixes, help text/schema wording tweaks, operational changes (CI, release config) | **patch** | +| New commands or substantial updates to existing commands | **minor** | +| Breaking changes or explicit instruction | **major** (only when told) | + +### Cutting a release + +Do **not** create or push git tags manually. Instead, use the GitHub +Releases UI (or `gh` CLI) to create a release; GitHub will create the tag +automatically, and the **Publish Tagged Release** GitHub Actions workflow +will run GoReleaser to build and publish multi-arch binaries. + +```bash +# Determine the next version by inspecting the latest tag. +LATEST=$(git tag --sort=-v:refname | head -1) +echo "Latest tag: $LATEST" +NEXT=v0.1.1 # bump appropriately from the latest tag + +gh release create "$NEXT" \ + --generate-notes \ + --latest +``` + +After creating the release, verify that the **Publish Tagged Release** +workflow triggered by the new tag completes successfully; otherwise +release binaries may be missing even though the GitHub Release exists. + +## Contributing Guidelines + +1. **Add Commands**: Create new commands in `internal/commands/` following + the established pattern +2. **Package Comments**: Every new `*.go` file must include the same + `// Package ...` doc comment as the rest of its package +3. **Code Quality**: Run `make check` before commits +4. **Documentation**: Update README.md for user-facing changes diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5586ab1 --- /dev/null +++ b/Makefile @@ -0,0 +1,141 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT + +.PHONY: all build clean check fmt vet lint revive goreleaser-check test test-coverage run deps install-tools megalinter help + +# Build variables +BINARY_NAME=lfx +CMD_DIR=./cmd/lfx +BUILD_DIR=./bin + +# Build flags. Version is not injected via -ldflags: `go build` already +# embeds a pseudo-version (encoding the VCS revision, commit time, and +# dirty state) into the binary's module version, which main.go reads via +# debug.ReadBuildInfo() as a fallback. A local `make build` therefore +# reports a traceable pseudo-version like v0.0.0-