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
56 changes: 54 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,48 @@ jobs:
path: coverage-integration.out
retention-days: 1

e2e-test:
name: E2E Tests
runs-on: ubuntu-latest
needs: [validate]
steps:
- uses: actions/checkout@v6

- uses: cachix/install-nix-action@v31
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}

- uses: nix-community/cache-nix-action@v7
with:
primary-key: nix-${{ runner.os }}-${{ hashFiles('flake.nix', 'flake.lock') }}
restore-prefixes-first-match: nix-${{ runner.os }}-
gc-max-store-size-linux: 3221225472
purge: true
purge-prefixes: nix-${{ runner.os }}-
purge-created: 0
purge-last-accessed: P7D
purge-primary-key: never

# Kind node-image preload/cache is tracked in #41; omit until wired.
# Race is off in test-e2e on purpose (Kind + client-go; see nix/apps/testing.nix).
- name: Run e2e tests
run: nix run .#test-e2e
env:
CI: "true"
DEPLOYAH_E2E_FORCE: "1"

- name: Upload coverage artifact
uses: actions/upload-artifact@v7
with:
name: coverage-e2e
path: coverage-e2e.out
retention-days: 1

report:
name: Coverage Report
runs-on: ubuntu-latest
needs: [unit-test, integration-test]
if: always() && needs.unit-test.result == 'success' && needs.integration-test.result == 'success'
needs: [unit-test, integration-test, e2e-test]
if: always() && needs.unit-test.result == 'success' && needs.integration-test.result == 'success' && needs.e2e-test.result == 'success'
permissions:
contents: read
id-token: write
Expand All @@ -213,6 +250,11 @@ jobs:
with:
name: coverage-integration

- name: Download e2e test coverage
uses: actions/download-artifact@v8
with:
name: coverage-e2e

- name: Upload unit coverage to Codecov
uses: codecov/codecov-action@v7
with:
Expand All @@ -232,3 +274,13 @@ jobs:
flags: integration
name: deployah-integration
disable_search: true

- name: Upload e2e coverage to Codecov
uses: codecov/codecov-action@v7
with:
use_oidc: true
fail_ci_if_error: true
files: ./coverage-e2e.out
flags: e2e
name: deployah-e2e
disable_search: true
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
# Deployah config (keep scenario fixtures tracked for CI)
# Deployah config (keep scenario and e2e fixtures tracked for CI)
deployah.platform.yaml
deployah.yaml
.env.*
.env
# Only the repo-root scaffold; fixture trees under scenarios/ and
# internal/e2e/testdata/ keep their .deployah/ contents tracked.
/.deployah/
!scenarios/**/deployah.platform.yaml
!scenarios/**/deployah.yaml
!scenarios/**/.env.*
!scenarios/**/.env
!internal/e2e/testdata/**/deployah.platform.yaml
!internal/e2e/testdata/**/deployah.yaml
!internal/e2e/testdata/**/.env.*
!internal/e2e/testdata/**/.env

# Go workspace file
go.work
Expand Down
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1530,6 +1530,17 @@ nix run .#test-integration # scenario tests in internal/testing
Coverage profiles are written to `coverage-unit.out` and
`coverage-integration.out`.

### Running e2e tests locally

Requires Docker. Creates and destroys a Kind cluster named `deployah`.

```sh
DEPLOYAH_E2E_FORCE=1 nix run .#test-e2e
```

Skips automatically when no container engine is found (unless `CI=true`).
Set `DEPLOYAH_E2E_DUMP=1` with `-v` to print live objects when adding a scenario.

### Build and run

```sh
Expand All @@ -1540,10 +1551,11 @@ nix run . -- --help # run without installing
### CI

GitHub Actions runs flake validation, lint/fmt/tidy checks, `nix run .#test-unit`,
and `nix run .#test-integration` on every pull request and push to `main`.
Scenario fixtures under `scenarios/` (including `deployah.yaml` and
`deployah.platform.yaml`) are tracked so integration tests can run on a clean
checkout.
`nix run .#test-integration`, and `nix run .#test-e2e` on every pull request and
push to `main`.
Scenario fixtures under `scenarios/` and e2e fixtures under
`internal/e2e/testdata/` (including `deployah.yaml`, `deployah.platform.yaml`,
and `.deployah/`) are tracked so tests can run on a clean checkout.

```sh
nix flake check # runs the pre-commit hooks (lint, markdownlint, tidy, nixfmt)
Expand Down
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

buildGoModule' = pkgs.buildGoModule.override { inherit go; };

deployahVendorHash = "sha256-zEzSRm+uowbh0hwsUO0RFlIBOhI0EdRUnF7UMwIGIps=";
deployahVendorHash = "sha256-gtK9utTbZWrWCYtCGbz3SXtF3uFDnpqzF1tyOs+Ijro=";

deployah = import ./nix/deployah.nix {
buildGoModule = buildGoModule';
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ require (
k8s.io/apimachinery v0.36.3
k8s.io/client-go v0.36.3
nabat.dev v0.6.3
sigs.k8s.io/e2e-framework v0.7.0
sigs.k8s.io/kind v0.32.0
sigs.k8s.io/yaml v1.6.0
)
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,8 @@ github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqa
github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8=
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo=
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c=
github.com/vladimirvivien/gexe v0.5.0 h1:AWBVaYnrTsGYBktXvcO0DfWPeSiZxn6mnQ5nvL+A1/A=
github.com/vladimirvivien/gexe v0.5.0/go.mod h1:3gjgTqE2c0VyHnU5UOIwk7gyNzZDGulPb/DJPgcw64E=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
Expand Down Expand Up @@ -706,6 +708,8 @@ pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4=
sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw=
sigs.k8s.io/e2e-framework v0.7.0 h1:AHkySTC6MvnnMbVSxaO4z1m2MhQKNFP+2Ihs5pRNLlM=
sigs.k8s.io/e2e-framework v0.7.0/go.mod h1:1ZgXkUSjmnf18/JgHZNEATWjv48O5lJm9aI1QIsRdbw=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kind v0.32.0 h1:p9hscbj98u/qyrjVpjId86LI70nQmbSsipV7wCG10Xk=
Expand Down
8 changes: 5 additions & 3 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ import (
var version = "dev"

// NewApp creates a new Nabat application with all subcommands registered.
func NewApp() *nabat.App {
// Caller options are appended after the defaults so nabat's last-wins
// semantics let tests override construction-time settings such as IO.
func NewApp(opts ...nabat.Option) *nabat.App {
// app is referenced by the WithErrorHandler closure below, which only
// runs later (inside app.Run), by which point this assignment has
// completed; see the "self-referential closure" note there.
var app *nabat.App
app = nabat.MustNew("deployah",
app = nabat.MustNew("deployah", append([]nabat.Option{
nabat.WithTheme("gruvbox"),
nabat.WithVersion(version),
nabat.WithDescription("Deployah turns a spec into a running release on Kubernetes (Spec-to-Release)"),
Expand All @@ -78,7 +80,7 @@ func NewApp() *nabat.App {
errStyle := app.Theme().Style(theme.StatusError)
fmt.Fprintf(app.IO().ErrOut, "%s %s\n", errStyle.Render("error:"), err) //nolint:errcheck // best-effort diagnostic write
}),
)
}, opts...)...)

// Build runtime once from global flags and store in context for all commands.
if err := app.OnPreRun(func(c *nabat.Context) error {
Expand Down
21 changes: 21 additions & 0 deletions internal/e2e/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2025 The Deployah Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package e2e holds Deployah's end-to-end suite, which drives the CLI
// in-process against a live Kind cluster.
//
// The tests are gated behind the "e2e" build tag and need a container engine;
// run them with `nix run .#test-e2e`. This file intentionally carries no build
// tag so `go list ./...` and golangci-lint can resolve the package without it.
package e2e
Loading