New to Kestra?
Use blueprints to kickstart your first workflows.
Audit a Ceph RBD image's snapshots for unprotected ones via the Dashboard API and alert Slack, read-only.
An RBD snapshot without Ceph's own protected flag set is one rbd snap rm away
from being gone - no confirmation, no recovery, whether that command runs by a
typo, a careless cleanup script, or someone who did not realize that snapshot was
the only backup of a volume. This blueprint lists every snapshot on a named RBD
image through the Ceph Dashboard API and alerts Slack by name the moment any of
them is not protected.
periodic_protection_check (io.kestra.plugin.core.trigger.Schedule) runs every 6 hours. Shipped disabled so you can validate a manual run first.list_image_snapshots (io.kestra.plugin.ceph.rbd.snapshots.List, fetchType: FETCH) calls GET /api/block/image/{pool_name}/{image_name} and returns the embedded snapshots array, each with id, name, size, timestamp, and is_protected.evaluate_snapshot_protection (io.kestra.plugin.core.flow.If) filters snapshots with a jq expression for is_protected == false, branching on whether that count is greater than zero.else (HEALTHY): log_protection_healthy records a clean check.then (AT_RISK): alert_unprotected_snapshots names every unprotected snapshot in the Slack message.log_audit_complete always runs last, printing the headline count regardless of which branch fired.errors block alerts Slack separately if the flow itself fails outright - an unreachable Dashboard host, bad credentials, or a pool_name/image_name that does not exist must not read as "no risk".is_protected flag per snapshot, the same signal rbd snap protect/unprotect controls - not a guess based on snapshot naming or age.rbd snap rm would be a real incident, not an inconvenience.cluster.GetHealth and pools.List/Update - this one exercises the rbd.snapshots package, the plugin's third and final real feature area, none of which overlap with this repo's other two Ceph blueprints.rbd snap ls run by hand, or the equivalent Dashboard API call, answers one moment in time; it does not decide a cadence, filter for the unprotected ones specifically, or notify anyone. Kestra supplies the schedule, the protection-state filter as a first-class branch, and an execution history that shows exactly when a snapshot went unprotected and for how long before anyone noticed.
/api/block/image/{image_spec}.Local testing: as with this repo's other Ceph blueprints, Ceph is a multi-daemon distributed system with no official single-container demo image (confirmed against Ceph's own containerized-deployment documentation). Use cephadm bootstrap to stand up a single-node cluster with the Dashboard enabled, create an RBD image, and take a snapshot with rbd snap create (left unprotected on purpose) to exercise this flow. The plugin's own default Dashboard port is 8443, which does not collide with Kestra's own UI on 8080.
CEPH_USERNAME / CEPH_PASSWORD: Dashboard account credentials used by list_image_snapshots.SLACK_WEBHOOK_URL: Slack incoming webhook used by alert_unprotected_snapshots and the errors block.SECRET_, base64-encoded, and read back in flows with {{ secret('NAME') }} - for example SECRET_CEPH_PASSWORD=$(echo -n '<your-local-password>' | base64). This keeps credentials out of the flow YAML but, per Kestra's own documentation, offers no encryption at rest or access control beyond the host environment; use the Enterprise secrets backend for stronger guarantees.ceph_host (STRING, default localhost): Ceph Dashboard hostname or IP.ceph_port (INT, default 8443): Ceph Dashboard REST API port.pool_name (STRING, default rbd): pool the target image belongs to.image_name (STRING, default data-volume): RBD image audited.outputs.list_image_snapshots.snapshots / .total: the full snapshot list and count for the image, every run.cephadm bootstrap.rbd create rbd/data-volume --size 1G then rbd snap create rbd/data-volume@test-snap.CEPH_USERNAME, CEPH_PASSWORD, and SLACK_WEBHOOK_URL secrets.alert_unprotected_snapshots fires, naming test-snap.rbd snap protect rbd/data-volume@test-snap and re-run to confirm log_protection_healthy fires instead.periodic_protection_check once you trust the check.(pool_name, image_name) pairs with io.kestra.plugin.core.flow.Loop to run the same gate across every image a platform team considers backup-critical.io.kestra.plugin.ceph.rbd.images.List first to discover every image in a pool automatically, instead of naming one image_name per flow run.timestamp on a best-effort basis (accepting the plugin's own documented caveat that its format is Ceph-version dependent) to add an age-based signal alongside protection state, clearly flagging it as approximate rather than authoritative.io.kestra.plugin.ceph.rbd.snapshots.Clone) as a verification step, confirming the snapshot it just depended on is still protected before trusting the clone.timestamp is a raw, unparsed string by design. Verified directly from the plugin's own source comment: the format is Ceph-version dependent, so this flow deliberately does not attempt date arithmetic on it - an age-based "stale snapshot" check would need its own, version-aware parsing this blueprint does not attempt.rbd.snapshots only exposes Clone, Create, Delete, List, Rollback - none of them sets is_protected, which is exactly why alert_unprotected_snapshots only reports, it has nothing safe to call to fix the gap itself.pool_name/image_name target exactly one RBD image per run - a pool with many images needs a Loop (see How to extend) or one flow execution per image, not a single run covering the whole pool.is_protected guards against deletion, not against the image itself being deleted. A protected snapshot still disappears if the parent image is deleted along with all its snapshots through a sufficiently privileged call - protection only blocks the specific rbd snap rm command on that one snapshot.rbd.snapshots.List's properties and SnapshotInfo's fields (id, name, size, timestamp, is_protected) are taken verbatim from the plugin's source on main - but no Docker/Ceph environment or Kestra engine was run to execute this flow end to end.