Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,27 @@ jobs:
ARGS="$ARGS --json-output yamlspec-results.json"
ARGS="$ARGS --emd-output yamlspec-results.emd.md"
ARGS="$ARGS --junit-output yamlspec-results.xml"
# GITHUB_ACTIONS=true auto-enables --github-annotations via env binding,
# but we pass it explicitly for clarity in the run log.
ARGS="$ARGS --github-annotations"
ARGS="$ARGS ${{ inputs.extra-args }}"

echo "Running: yamlspec $ARGS"
yamlspec $ARGS && echo "success=true" >> "$GITHUB_OUTPUT" || echo "success=false" >> "$GITHUB_OUTPUT"

# --- Step summary (visible on the workflow run page) ---

- name: Write results to step summary
if: always()
run: |
if [ -f yamlspec-results.emd.md ]; then
cat yamlspec-results.emd.md >> "$GITHUB_STEP_SUMMARY"
else
echo "## yamlspec" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo ":warning: Results file not produced — check the run logs." >> "$GITHUB_STEP_SUMMARY"
fi

# --- Upload artifacts ---

- name: Upload test results
Expand Down
2 changes: 2 additions & 0 deletions .structlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ file_naming_pattern:
- "*.md"
- "*.xml"
- "*.txt"
- "*.gif"
- "*.cast"
- "Makefile"
- ".gitignore"
- ".goreleaser.yml"
Expand Down
81 changes: 81 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Changelog

All notable changes to yamlspec are documented here.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- `docs/index.md` documentation landing page with cross-links between docs.
- `docs/recipes.md` cookbook of common assertion patterns
(multi-environment, security baseline, image pinning, label conventions,
HPA/PDB invariants, NetworkPolicy, etc.).
- `docs/troubleshooting.md` with the common authoring/runtime errors and how
to read them.
- `examples/README.md` describing each worked example and the exact command
to run it.
- `CONTRIBUTING.md` covering dev setup, layer conventions, how to add an
operator/formatter/command, commit conventions, and the release flow.
- README field-path quick reference (dotted, `[index]`, `[*]` wildcard,
bracket notation, leading-dot JQ form) and cross-links to the docs.
- GitHub Actions reusable workflow now writes the EMD output to
`$GITHUB_STEP_SUMMARY` so results show on the workflow run page, not only
as a PR comment.
- `--github-annotations` output mode (auto-enabled when `GITHUB_ACTIONS=true`)
emits `::error file=...,line=...::` lines so failing assertions appear
inline in the GitHub "Files changed" diff view.

### Changed
- README install instructions now reflect that no GitHub release exists yet —
use `go install` from source until v0.1.0 is cut.

## [0.1.0] — Initial release

The first usable yamlspec build, including a correctness-hardening pass.

### Added
- RSpec-like `describe`/`it`/`should` test syntax in `spec.yaml`.
- 22 assertion operators: equality (`toEqual`, `toNotEqual`), numeric
comparison (`toBeGreaterThan`/`Less`/`OrEqual`), string
(`toContain`/`Start`/`End`/`Match` and negations), existence (`toExist`,
`toBeNull`), object (`toHaveKey`), set membership
(`toBeOneOf`/`toNotBeOneOf`), array (`toContainItem`, `toHaveLength`,
`toHaveMinLength`, `toHaveMaxLength`).
- Wildcard array iteration in field paths
(`spec.containers[*].image`) — assertion runs against every element.
- Distinction between "field missing" and "field is null" for
`toExist`/`toBeNull` semantics.
- Six output formats: console (colored RSpec-style tree), JSON, YAML,
Markdown, enriched Markdown (collapsible `<details>` for PR comments),
JUnit XML.
- Parallel execution via `--workers N`.
- `--fail-fast` for fast feedback on sequential runs.
- Configurable `--pre-run-timeout` (default 60s) for `pre_run` shell
commands.
- Tag filtering via repeatable `--tag` flag.
- Strict YAML decoding for `spec.yaml` — typos in field names are rejected
rather than silently ignored.
- Comprehensive spec validation: empty `describe`, missing `should`/`expect`,
assertions with no operator are caught at parse time.
- `pre_run` execution kills the entire process group on timeout — orphaned
`sleep`-style children no longer keep the command alive past its deadline.
- Cross-platform process management (Unix `setpgid` + `kill -PGID`; Windows
fallback).
- Reusable GitHub Actions workflow with one-line opt-in via
`uses: AxeForging/yamlspec/.github/workflows/reusable.yml@main`. Posts
enriched-markdown PR comments and updates them in place on subsequent
pushes.
- `init` command to scaffold new specs.
- `list` / `list --tags` for spec/tag discovery (deterministic ordering).
- `ai-help` command emitting a comprehensive reference for AI assistants.
- Built on Go 1.25.8 (covers the recent stdlib CVE).

### Validation
- 118 tests passing across 6 packages.
- Examples for plain manifests, Helm charts, Kustomize overlays,
multi-resource specs, and a security baseline.

[Unreleased]: https://github.com/AxeForging/yamlspec/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/AxeForging/yamlspec/releases/tag/v0.1.0
171 changes: 171 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Contributing

Thanks for considering a contribution. yamlspec aims to stay small, fast, and
opinionated — pull requests that keep it that way are very welcome.

## Dev setup

Requirements:

- Go 1.25+ (the minimum tracks the upstream stdlib security floor)
- `golangci-lint` for `make lint`
- [`lefthook`](https://github.com/evilmartians/lefthook) for pre-commit/pre-push hooks (optional but recommended)
- `helm` and/or `kustomize` if you want to run those examples locally

```bash
git clone git@github.com:AxeForging/yamlspec.git
cd yamlspec
go mod download
make build-local # produces ./yamlspec
make test # runs unit + integration tests
```

Install hooks (optional):

```bash
lefthook install
```

This wires up `gofmt`, `go vet`, `golangci-lint`, `structlint`, and
conventional-commits message validation on `pre-commit`, plus `govulncheck` on
`pre-push`.

## Project layout

```
yamlspec/
├── main.go # CLI entry point (urfave/cli v1)
├── flags.go # CLI flag definitions
├── actions/ # Command handlers (validate, list, init, version, ai-help)
├── domain/ # Models (Spec, Results, Config) — no business logic
├── services/ # Business logic
│ ├── assertion.go # Assertion engine (operator evaluation, field paths)
│ ├── discovery.go # Spec file discovery + strict YAML decode
│ ├── runner.go # Test execution (sequential + parallel)
│ ├── runner_unix.go # Unix process-group handling for pre_run timeouts
│ ├── runner_windows.go
│ ├── formatter.go # Formatter interface
│ └── fmt_*.go # Output formatters
├── helpers/ # Utilities (errors, logger, terminal detection)
├── integration/ # E2E tests with testdata fixtures
├── examples/ # Worked examples
└── docs/ # Public-facing documentation
```

Keep the layers honest: `actions/` orchestrates, `services/` does the work,
`domain/` is data only. New cross-cutting code generally belongs in
`services/`.

## Common contribution patterns

### Adding a new assertion operator

1. Add the field to `domain.Assertion` in `domain/models.go`. Pointer types
(`*float64`, `*int`, `*bool`) for "not set" / "set to zero" disambiguation.
2. If it's value-based, add it to `Assertion.HasValueOperators()`. If it can
stand alone (like `toExist` / `toBeNull`), wire it into `HasAnyOperator()`.
3. Add the evaluation logic to `services/assertion.go` `evaluateOperators()` —
each operator gets its own short helper.
4. Add unit tests in `services/assertion_test.go`. Cover: passing case,
failing case, type mismatch, missing field.
5. Update [docs/spec-format.md](docs/spec-format.md) and the README operator
table.

### Adding a new output format

1. Create `services/fmt_<name>.go` implementing the `Formatter` interface
(single method: `Format(*SuiteResult) ([]byte, error)`).
2. Add a constructor `NewXFormatter()`.
3. Wire it into the `formatters` map in `actions/validate.go`.
4. Add the CLI flag in `flags.go` and reference it from `main.go`.
5. Add an integration test in `integration/`.

### Adding a new CLI command

1. Create `actions/<command>.go` with a struct + `Execute(c *cli.Context)`
method.
2. Add a constructor `NewXAction()`.
3. Wire it into the `app.Commands` slice in `main.go`.

## Testing

```bash
make test # everything (unit + integration)
make test-unit # services/ only — fast
make test-e2e # integration/ — builds the binary first
make test-coverage # writes coverage.html
```

Integration tests live in `integration/` and use `testdata/` fixtures. They
build the binary from source and run it as a subprocess against real spec
files, so they catch regressions in the actual CLI surface, not just the
library code.

## Linting and formatting

```bash
make lint # golangci-lint
gofmt -l -w . # auto-format (or let lefthook do it)
```

Pre-commit hooks (via `lefthook install`) catch these automatically.

## Commit conventions

Conventional Commits are enforced on the commit message. Format:

```
<type>[optional scope][!]: <description>
```

Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`,
`build`, `ci`, `perf`, `revert`.

Examples:

```
feat: add toContainItem operator
fix(assertion): handle nil values in toEqual
docs: add troubleshooting guide
test: add integration tests for tag filtering
```

Use `!` after the type/scope to mark a breaking change:

```
feat!: rename --test-dir to --suite-dir
```

## Pull requests

- Keep changes focused. One feature or fix per PR.
- Update tests. New code without tests will not be merged.
- Update docs. If you change user-facing behavior, update the README, the
relevant `docs/*.md` page, and `CHANGELOG.md` under `## [Unreleased]`.
- The CI workflow (`.github/workflows/pr.yml`) runs lint, tests, and a
govulncheck. All must pass.

## Releasing

Releases are cut via the workflow_dispatch `release` workflow:

1. Land all changes on `main`. Make sure `CHANGELOG.md` has a populated
`## [Unreleased]` section.
2. Run the **Release** workflow from the GitHub Actions tab. Optional `tag`
input — if omitted, the patch version auto-bumps.
3. The workflow runs tests, creates the tag, and runs GoReleaser, which
publishes platform binaries (`linux/darwin/windows × amd64/arm64`) and a
checksums file.
4. After the release lands, move the `## [Unreleased]` entries under a new
`## [vX.Y.Z]` heading in `CHANGELOG.md` and commit.

GoReleaser config lives in `.goreleaser.yml`.

## Code of conduct

Be respectful, give constructive feedback, assume good intent. Standard stuff.

## Questions?

Open an issue, or start a draft PR with a question — early feedback beats a
finished PR going the wrong direction.
Loading
Loading