Webhook icon
Schedule icon
WorkingDirectory icon
Commands icon
Docker icon
Script icon
Process icon
If icon
Create icon
SlackIncomingWebhook icon
Log icon

Test rendered Kubernetes manifests with conftest before they merge

Test rendered Kubernetes manifests with OPA conftest on a CI webhook, then open a GitHub issue and alert Slack when a policy rule fails.

Categories
Infrastructure

Diagram unavailable

We could not build the topology for this blueprint. The flow itself is valid, use the YAML on the left to run it.

Most Kubernetes policy checks are written once, wired into nobody's pipeline, and quietly forgotten. This blueprint puts conftest in front of the merge: CI posts to a keyed webhook whenever rendered manifests change, the flow stages those manifests next to a Rego policy, runs conftest in its own container, and turns the JSON report into a verdict that humans and machines can both act on. Failures become a labeled GitHub issue plus a Slack ping, so the violation lands where the team already works.

How it works

  1. The on_manifest_change trigger (io.kestra.plugin.core.trigger.Webhook) exposes a keyed URL for your CI to POST to when rendered manifests change. A disabled nightly_safety_net Schedule sits behind it as a fallback net.
  2. policy_workspace (io.kestra.plugin.core.flow.WorkingDirectory) opens one shared filesystem so every later task sees the same files.
  3. write_fixtures (io.kestra.plugin.scripts.shell.Commands) writes deployment.yaml (a three replica Deployment deliberately rendered with no resources.limits), service.yaml and policy/policy.rego with heredocs. The Rego defines deny_resource_limits for containers missing CPU or memory limits and deny_privileged_container for securityContext.privileged == true, covering both containers and initContainers. Every denial returns a structured violation that carries its own rule id, so the report can name the rule that fired instead of only the text it produced.
  4. run_conftest (io.kestra.plugin.scripts.shell.Commands on the io.kestra.plugin.scripts.runner.docker.Docker runner, image openpolicyagent/conftest:v0.56.0) executes conftest test *.yaml --output json > conftest-report.json || true. The || true absorbs the violation exit code so the parsed report, not the shell, decides the outcome.
  5. parse_report (io.kestra.plugin.scripts.python.Script) reads the report, walks the failures list of each file entry, takes the rule name from the violation's metadata.rule (falling back to metadata.query, then code), and writes policy-failures.txt with one line per violation. It emits verdict, failure_count, failures, pass_count and summary through the outputs protocol. A missing, empty or unparseable report becomes a synthetic failure, so a broken tool run can never masquerade as a pass.
  6. policy_gate (io.kestra.plugin.core.flow.If) branches on failure_count. Over zero, open_policy_issue (io.kestra.plugin.github.issues.Create) files a labeled issue with the full failure list, then alert_slack posts the count and the first failing rules. At zero, log_clean records the all-clear.
  7. The errors block alerts Slack when the pipeline itself breaks, because a dead policy runner looks exactly like a passing gate.

What you get

  • Policy failures caught before merge instead of during the next incident review.
  • A per-failure line report kept as a flow output (report_uri) for every execution.
  • Structured outputs, verdict, failure_count, failures and report_uri, ready for a status check.
  • Escalation where engineers already work, a labeled GitHub issue plus a Slack alert.

Who it's for

  • Platform and DevOps teams shipping policy-as-code with OPA but with no merge gate around it.
  • Security engineers who want resource limits and privilege rules enforced on rendered manifests instead of reviewed by hand.
  • SRE teams that need a repeatable, auditable answer to whether a change passed its policy checks.

Why orchestrate this with Kestra

conftest evaluates policy well, but it does not trigger itself, keep its reports, or route its findings. Kestra supplies the loop around it, an event entry point for CI, a containerized runner so the tool version lives in the flow, a branch that separates escalation from the all-clear, output files that survive the run, and alert routing into GitHub and Slack from one declarative file. A bare conftest line in a pipeline script gives you none of that: it prints to a log nobody reads and disappears when the job ends.

Prerequisites

  • A Kestra worker with Docker access for the conftest container and Python 3 for the report parser.
  • A GitHub token allowed to open issues in repository (classic repo scope, or fine-grained Issues: write) and a Slack incoming webhook.

Secrets

  • MANIFEST_TEST_WEBHOOK_KEY: key segment guarding the webhook URL, so only your CI can start a run.
  • GITHUB_TOKEN: token used by open_policy_issue to create the issue.
  • SLACK_WEBHOOK_URL: Slack incoming webhook used for the violation alert and the flow error handler.

Quick start

  1. Add the three secrets to your Kestra namespace, then set repository, branch and image for the change you want gated.
  2. Point your CI at /api/v1/{tenant}/executions/webhook/{namespace}/conftest-opa-manifest-tests/{MANIFEST_TEST_WEBHOOK_KEY} when manifests change, or trigger the webhook by hand.
  3. Confirm the first run fails on the staged Deployment, read report_uri, and check that the issue plus the Slack alert landed. Then add resources.limits to the fixture and watch it go green.

How to extend

  • Replace the heredocs in write_fixtures with io.kestra.plugin.git.Clone so the real rendered manifests from the pull request are tested instead of fixtures.
  • Add a core.execution.Fail in the failing branch to turn the gate red for branch protection, and extend policy.rego with image tag, pod security or required label rules.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.