- Rust 74.2%
- Nix 21.7%
- Just 3.6%
- Shell 0.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .cargo | ||
| .woodpecker | ||
| core | ||
| nix | ||
| plugins | ||
| scripts | ||
| .gitignore | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CODEOWNERS | ||
| flake.lock | ||
| flake.nix | ||
| justfile | ||
| LICENSE | ||
| README.md | ||
| renovate.json | ||
| rust-toolchain.toml | ||
| TODO.md | ||
| treefmt.nix | ||
woodpecker-plugins
A Rust workspace of Woodpecker CI plugins. Each plugin is an independently versioned binary that ships as its own OCI image, built reproducibly with Nix via nix2container.
Layout
woodpecker-plugins/
├── Cargo.toml # workspace root (Rust plugins only)
├── flake.nix # discovers plugins, exposes binary + image per plugin
├── nix/
│ └── mk-rust-plugin.nix # shared Rust build helper
├── core/ # shared library: Plugin trait, env-var settings loader, tracing
└── plugins/
└── flake-lock-checker/ # non-Rust plugin wrapping pkgs.flake-checker
└── default.nix
Every plugin owns a plugins/<name>/default.nix that returns a derivation. The flake
auto-discovers any directory under plugins/ with that file, so adding a plugin requires
no flake edits — Rust or otherwise.
Rust plugins additionally depend on woodpecker-plugins-core for the Plugin trait,
env-var settings loading, tracing initialisation, and a unified error type, so main.rs
stays tiny.
Build & test (Cargo)
cargo build # whole workspace
cargo test --workspace
cargo clippy --workspace -- -D warnings
cargo fmt --all
Or via just:
just build
just test
just lint
just fmt
Build a plugin OCI image (Nix)
The flake auto-discovers plugins/<name> directories that contain a default.nix and
exposes:
| Attribute | What it produces |
|---|---|
.#<name> |
release binary for the plugin |
.#<name>-image |
OCI image (nix2container manifest) tagged with the derivation's version |
.#default |
symlinkJoin of every plugin binary |
Adding a new plugin
A plugin is whichever directory you create under plugins/ with a default.nix that
returns a derivation. The flake picks it up automatically.
Rust plugin
cargo new --bin plugins/my-plugin
In plugins/my-plugin/Cargo.toml:
[package]
name = "my-plugin"
version = "0.1.0"
edition.workspace = true
license.workspace = true
[dependencies]
woodpecker-plugins-core = { path = "../../core" }
serde = { workspace = true }
tokio = { workspace = true }
In plugins/my-plugin/default.nix:
{ mkRustPlugin }:
mkRustPlugin { name = "my-plugin"; }
Implement Plugin for your type and let the helper drive it. The trait uses RPITIT, so
write a bare async fn run — no #[async_trait]:
use serde::Deserialize;
use woodpecker_plugins_core::{Plugin, PluginError, run_plugin};
#[derive(Debug, Deserialize)]
struct Settings {
target: String,
}
struct MyPlugin;
impl Plugin for MyPlugin {
type Settings = Settings;
async fn run(settings: Self::Settings) -> Result<(), PluginError> {
tracing::info!(target = %settings.target, "running");
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<(), PluginError> {
run_plugin::<MyPlugin>().await
}
Plugin wrapping a nixpkgs package
When the work is already done by something in nixpkgs, skip the Rust crate entirely and
hand the flake a derivation directly. plugins/flake-lock-checker/default.nix is the
reference example: it wraps pkgs.flake-checker with writeShellApplication, translates
PLUGIN_* env vars to CLI flags, and pins the version literally so the release tag and
image tag agree.
{ lib, writeShellApplication, jq }:
let version = "1.7.1"; in
assert lib.assertMsg (jq.version == version)
"plugin pin (${version}) != nixpkgs (${jq.version}); bump or override src";
(writeShellApplication {
name = "jq-plugin";
runtimeInputs = [ jq ];
text = ''jq "$PLUGIN_FILTER" "$PLUGIN_INPUT"'';
}).overrideAttrs (old: {
inherit version;
meta = (old.meta or {}) // { mainProgram = "jq-plugin"; };
})
The image tag and release-tag check both read drv.version, so any non-Rust plugin must
expose a version attribute on its returned derivation.
Configuration
Plugins receive their configuration from environment variables prefixed with PLUGIN_, the
Woodpecker plugin convention. Field
names are matched case-insensitively against the prefixed env vars:
| Settings field | Env var |
|---|---|
message: String |
PLUGIN_MESSAGE |
uppercase: bool |
PLUGIN_UPPERCASE |
Use #[serde(default)] on Settings fields that are optional.
Releasing
Tag the commit with <plugin-name>-v<version>. The
.woodpecker/release.yaml pipeline parses the tag, builds .#<plugin-name>-image, and
pushes the image to the configured registry. Before pushing it asserts
nix eval --raw .#<plugin-name>.version equals <version> from the tag, so a tag that
disagrees with the derivation fails fast. For Rust plugins the version comes from
plugins/<name>/Cargo.toml; for nixpkgs-wrapped plugins it comes from the literal
version in their default.nix. Bump the relevant source before tagging.
License
MIT — see LICENSE.