New to Kestra?
Use blueprints to kickstart your first workflows.
Scan a Huawei GeminiDB table for items missing or past their expiry attribute, alert Slack above a threshold, and log the result.
GeminiDB for NoSQL is Huawei Cloud's DynamoDB-compatible database, and like DynamoDB itself, a time-to-live attribute only works if every write path actually sets it. A single service that forgets, or a migration that backfilled rows without one, leaves a growing set of items that will never expire and that nothing in GeminiDB itself will ever flag. This blueprint scans for exactly that: items with no expiry attribute at all, or one that has already passed, and treats the count as a governance signal.
This blueprint was created by Mohit Jeswani.
scan_missing_ttl (io.kestra.plugin.huawei.geminidb.Scan) reads a single response page of
table_name, server-side filtered to items where ttl_attribute is absent
(attribute_not_exists) or its value is less than today's date, read as FETCH so the
matching rows are available for the alert. Read-only; nothing is written.evaluate (io.kestra.plugin.core.output.OutputValues) compares the matched count against
max_missing_ttl_count.route (io.kestra.plugin.core.flow.If):summary logs the flagged count and severity, closing the governance cycle.errors alerts Slack if the flow itself fails, for example wrong database-account
credentials or an unreachable endpoint.Scan talks the DynamoDB wire protocol either way.Scan with a
server-side filterExpression, distinct from reading the whole table into memory and
filtering client-side.GeminiDB enforces nothing about whether a TTL-style attribute is actually set, and its own console has no alert for a growing count of unexpiring items. Kestra turns that into a scheduled check with the threshold as a flow input, a direct Slack page naming the count, and a full history of every run so a slow regression in write-path hygiene is visible before it is a storage bill.
amazon/dynamodb-local image: GeminiDB's data plane is DynamoDB wire-compatible, so Scan
works against either one by only changing endpoint.accessKeyId is always the literal rwuser, and
secretAccessKey is the instance admin password set when it was purchased (not a Huawei IAM
key - IAM credentials are rejected by this data plane with an opaque auth error).table_name carrying ttl_attribute as an ISO yyyy-MM-dd (or longer
ISO) string, to confirm the filter actually excludes compliant items.Local testing:
docker run -d --name dynamodb-local -p 8000:8000 amazon/dynamodb-local
docker network connect YOUR_KESTRA_NETWORK dynamodb-local (check existing networks first with
docker network ls; only needed if the Kestra Worker runs in a separate Docker network than
this container; set endpoint to http://dynamodb-local:8000 from inside that network, or
http://localhost:8000 from the host). accessKeyId/secretAccessKey can be any non-empty
strings against dynamodb-local, which does not validate them.
GEMINIDB_ADMIN_PASSWORD: the instance's database admin password, paired with the literal
accessKeyId: rwuser. In Kestra OSS, provide it as the base64-encoded environment variable
SECRET_GEMINIDB_ADMIN_PASSWORD.SLACK_WEBHOOK_URL: used by alert and the errors block. In Kestra OSS, provide it as the
base64-encoded environment variable SECRET_SLACK_WEBHOOK_URL.| Input | Type | Default | Purpose |
|---|---|---|---|
endpoint |
String | http://localhost:8000 |
DynamoDB-compatible connection address |
region |
String | cn-north-1 |
SigV4 signing region only, does not affect routing |
table_name |
String | <your-geminidb-table> |
Table to scan |
ttl_attribute |
String | expires_at |
Attribute expected to hold an ISO expiry date |
scan_limit |
Integer | 1000 |
Items evaluated in the single response page (1-1000) |
max_missing_ttl_count |
Integer | 1 |
Flagged-item count that triggers the Slack alert |
{{ outputs.scan_missing_ttl.rows }}: the matched items themselves, present because
fetchType: FETCH is used.{{ outputs.scan_missing_ttl.size }}: how many items in the scanned page matched the filter.{{ outputs.evaluate.values.severity }}: OK or TTL_HYGIENE_BREACH.Scan reads a single response page only; it never follows LastEvaluatedKey. A table larger
than scan_limit items is not fully covered by one run, and the task logs when the response
was truncated so this isn't silent. Narrow with a smaller, sharded table_name strategy or
accept partial coverage per run.{{ inputs.ttl_attribute }} < :cutoff only behaves correctly if every item
actually stores ttl_attribute as a string, and specifically one that starts with
yyyy-MM-dd. GeminiDB (like the AWS DynamoDB task this plugin ports from) stores numeric
values written through its own PutItem task as strings already, but data loaded by other
means could store it as a native number (N) attribute, which this filter will not match
correctly against a string :cutoff.filterExpression is a server-side filter applied after items are read; it does not reduce
GeminiDB's read cost or the amount of data scanned, only the amount returned.expressionAttributeNames property. If ttl_attribute collides with a
DynamoDB reserved word, the filter expression fails outright rather than needing an alias.accessKeyId/secretAccessKey here are the instance's database account, never a Huawei IAM
AK/SK pair - a perfectly valid IAM key is rejected with a bare AccessDeniedException that
gives no indication the credential type itself is the problem.amazon/dynamodb-local with the command above, or point at a real GeminiDB instance.GEMINIDB_ADMIN_PASSWORD and SLACK_WEBHOOK_URL secrets.expires_at set to a past date and some with
it omitted entirely.alert fires with the count you expect.compliant logs instead.daily trigger once endpoint and table_name point at the real instance.auto_remediate/dry_run pair used elsewhere in
this repo's guard blueprints, that loops over outputs.scan_missing_ttl.rows and calls
io.kestra.plugin.huawei.geminidb.PutItem to backfill a default ttl_attribute on each
flagged item rather than deleting it - left out of this blueprint since PutItem replaces the
whole item, and doing that safely needs the exact attribute types already in use in your table.table_name values with io.kestra.plugin.core.flow.Loop to cover more than
one table in a single scheduled run.missing_ttl_count over successive runs to catch a slow regression, not just a one-time
breach.