plugins.filter package¶
Subpackages¶
- plugins.filter.docker package
- plugins.filter.generate package
- plugins.filter.get package
- Subpackages
- Submodules
- plugins.filter.get.category_entries module
- plugins.filter.get.deployment_types_from_groups module
- plugins.filter.get.docker_paths module
- plugins.filter.get.domain module
- plugins.filter.get.entity_name module
- plugins.filter.get.role module
- plugins.filter.get.service_script_path module
- plugins.filter.get.url module
- Module contents
- plugins.filter.has package
- plugins.filter.merge package
- plugins.filter.swarm package
- plugins.filter.url package
Submodules¶
plugins.filter.active_docker module¶
Ansible filter to count active docker services for current host.
Active means: - application key is in group_names - application key matches prefix regex (default: ^(web-|svc-).* ) - under applications[app][‘services’] each service is counted if:
‘enabled’ is True, OR
‘enabled’ is missing/undefined (treated as active)
Returns an integer. If ensure_min_one=True, returns at least 1.
plugins.filter.application_allowed module¶
- plugins.filter.application_allowed.application_allowed(application_id: str, group_names: list, allowed_applications: list | None = None)¶
- Return True if:
application_id exists in group_names, AND
either allowed_applications is not provided (or empty), OR application_id is in allowed_applications.
- Parameters:
application_id (str) – Name of the application/role to check.
group_names (list) – List of groups the current host belongs to.
allowed_applications (list, optional) – List of application IDs to allow.
- Returns:
True if this application is allowed to deploy, False otherwise.
- Return type:
bool
plugins.filter.async_failures module¶
Filter async_failures: reduce reaped async_status results to the
failures a deploy should stop for.
poll: 0 makes a task fire and forget, so its own changed_when and
failed_when never see an outcome. The outcome only exists once the job is
collected with async_status, and collecting it is where the verdict has to
be made – for a whole batch at once, so one run reports every broken job
rather than the first.
A job counts as failed when it never finished, when the module reported
failed, or when a command returned a non-zero rc. What distinguishes a
real failure from an idempotent no-op differs per API and lives in different
keys: modules put it in msg (cloudflare answers “An identical record
already exists”), commands put it in stdout (ollama pull prints “up to
date” and exits non-zero). Both are searched, so a caller passes the tolerated
phrasing and does not have to know which key carries it.
plugins.filter.bcrypt_hash module¶
plugins.filter.ca_trust_paths module¶
Filter ca_cert_host: SPOT for the host-side root-CA cert location.
{{ SOFTWARE_DOMAIN | ca_cert_host }} -> /etc/<software_domain>/ca/root-ca.crt
ca_cert_host is where sys-ca-selfsigned provisions the root CA on the host.
The container-side bind-mount targets live in the CA_TRUST group_vars SPOT
(inject_cert_container / inject_wrapper_container) and reach the
standalone runtime scripts as env vars and CLI arguments.
- plugins.filter.ca_trust_paths.ca_cert_host(software_domain: str) str¶
plugins.filter.canonical_domains_map module¶
- class plugins.filter.canonical_domains_map.FilterModule¶
Bases:
object- canonical_domains_map(apps, domain_primary, *, recursive: bool = False, roles_base_dir: str | None = None, seed: Iterable[str] | None = None)¶
Build { app_id: [canonical domains…] }.
Rekursiv werden nur include_role, import_role und meta/main.yml:dependencies verfolgt. ‘run_after’ wird hier absichtlich ignoriert.
- filters()¶
plugins.filter.csp_filters module¶
- class plugins.filter.csp_filters.FilterModule¶
Bases:
objectJinja filters for building a robust, CSP3-aware Content-Security-Policy header. Safari/CSP2 compatibility is ensured by merging the -elem/-attr variants into the base directives (style-src, script-src). We intentionally do NOT mirror back into -elem/-attr to allow true CSP3 granularity on modern browsers.
- static add_csp_hash(current, application_id, directive, snippet)¶
Return a new
webserver_csp_hashes_extra_by_app-shaped dict withsnippetappended (deduplicated) to<application_id>.<directive>.Replaces the verbose inline
combine(..., recursive=True)pattern previously duplicated across sys-front-inj-* roles.
- build_csp_header(applications, application_id, domains, web_protocol, extra_whitelist=None, extra_hashes=None, domain_primary=None)¶
Builds the Content-Security-Policy header value dynamically based on application settings.
- Key points:
CSP3-aware: supports base/elem/attr for styles and scripts.
Safari/CSP2 fallback: base directives (style-src, script-src) always include the union of their -elem/-attr variants.
We do NOT mirror back into -elem/-attr; finer CSP3 rules remain effective on modern browsers if you choose to use them.
If the app explicitly disables a token on the base (e.g. style-src.unsafe-inline: false), that token is removed from the merged base even if present in elem/attr.
Inline hashes are added ONLY if that directive does NOT include ‘unsafe-inline’.
- Whitelists/flags/hashes read from:
csp.whitelist.<directive> csp.flags.<directive> csp.hashes.<directive>
- “Smart defaults”:
internal CDN for style/script elem and connect
Matomo endpoints (if services.matomo.enabled) for script-elem/connect
Simpleicons service (if services.simpleicons.enabled) for connect
reCAPTCHA (if services.recaptcha.enabled) for script-elem/frame-src
hCaptcha (if services.hcaptcha.enabled) for script-elem/frame-src
frame-ancestors extended for dashboard/logout/keycloak if enabled
- filters()¶
- static get_csp_flags(applications, application_id, directive)¶
Returns CSP flag tokens (e.g., “‘unsafe-eval’”, “‘unsafe-inline’”) for a directive, merging sane defaults with app config.
- Defaults:
For styles we enable ‘unsafe-inline’ by default (style-src, style-src-elem, style-src-attr), because many apps rely on inline styles / style attributes.
For scripts we do NOT enable ‘unsafe-inline’ by default.
- static get_csp_hash(content)¶
Computes the SHA256 hash of the given inline content and returns a CSP token like “‘sha256-<base64>’”.
- static get_csp_inline_content(applications, application_id, directive)¶
Returns inline script/style snippets to hash for a given directive. Accepts both scalar and list in config; always returns a list.
- static get_csp_whitelist(applications, application_id, directive)¶
Returns a list of additional whitelist entries for a given directive. Accepts both scalar and list in config; always returns a list.
- static get_extra_values(extra_mapping, directive)¶
- static is_feature_enabled(applications: dict, feature: str, application_id: str) bool¶
Returns True if the docker service flag is enabled for this application.
- New flag layout (examples):
services.matomo.enabled
services.dashboard.enabled
services.simpleicons.enabled
services.logout.enabled
services.hcaptcha.enabled
services.recaptcha.enabled
plugins.filter.curl module¶
Render the timeout-hardened, retry-hardened curl base command (SPOT).
{{ 30 | curl }} -> curl -s –connect-timeout 5 –max-time 30 –retry 3 –retry-all-errors –retry-delay 2 {{ 300 | curl }} -> curl -s –connect-timeout 5 –max-time 300 –retry 3 –retry-all-errors –retry-delay 2 {{ lookup(‘timeout’, 30) | curl }} -> TIMEOUT_FACTOR-scaled variant
- param max_time:
Seconds for the whole transfer; without it curl hangs forever on an accepted-but-silent peer and task retries never fire.
- param connect_timeout:
Seconds curl waits for the TCP/TLS connect (default 5).
- param retries:
–retry count; –retry-all-errors makes it cover transport-layer resets (exit 35/52/56), the class that hard-fails deploys on flaky CI egress. Pass 0 to opt a call out of retrying.
Callers append their own flags (-S, -f, -o, …) after the filter.
plugins.filter.domain_tools module¶
- plugins.filter.domain_tools.to_zone(hostname: str) str¶
plugins.filter.dotenv module¶
- plugins.filter.dotenv.dotenv_quote(ctx: Context, value: Any = <object object>) str¶
Dotenv-safe quote, switched on DEPLOYMENT_MODE from the render context.
Also callable directly from Python (unit tests, helpers) without a Jinja context: in that case the only positional argument is the value and compose-style quoting is used.
plugins.filter.gitea_auth_noop module¶
Filter gitea_auth_noop: classify gitea auth-source idempotency errors.
- failed_when: >
result.rc != 0 and not (result.stderr | gitea_auth_noop)
Returns True when the stderr of a failed gitea admin auth add-ldap|add-oauth
only reports that the login source already exists; any other CLI error stays
False so the caller’s failed_when still trips.
- plugins.filter.gitea_auth_noop.gitea_auth_noop(stderr: str | None) bool¶
plugins.filter.invokable_paths module¶
- plugins.filter.invokable_paths.get_invokable_paths(roles_file: str | None = None, suffix: str | None = None) list[str]¶
Load nested roles YAML and return dash-joined paths where ‘invokable’ is True. Appends suffix if provided.
- plugins.filter.invokable_paths.get_non_invokable_paths(roles_file: str | None = None, suffix: str | None = None) list[str]¶
Load nested roles YAML and return dash-joined paths where ‘invokable’ is False or missing. Appends suffix if provided.
plugins.filter.ldap_modify_noop module¶
Filter ldap_modify_noop: classify ldapmodify/ldapadd idempotency errors.
- failed_when: >
result.rc != 0 and not (result.stderr | ldap_modify_noop)
Returns True when the stderr of a failed ldapmodify/ldapadd only reports that the attribute, value or entry already exists; any other LDAP error stays False so the caller’s failed_when still trips.
- plugins.filter.ldap_modify_noop.ldap_modify_noop(stderr: str | None) bool¶
plugins.filter.mirror_filters module¶
Jinja filter: external_asset_origins extracts the third-party asset hosts an application (or every application) declares in csp.whitelist.*.
The result feeds the web-svc-mirror privacy proxy: the mirror’s nginx vhost whitelists exactly these hosts as proxy upstreams, and the body-filter rewrite maps https://<host>/… to <mirror>/<host>/… in served HTML.
- Only literal https://<host> tokens count as external assets:
tokens containing unresolved Jinja ({{ … }}) point at deployment-own domains and are skipped,
wildcard tokens (*) cannot be mirrored deterministically,
hosts on the deployment’s primary domain (or .onion) are already local.
- plugins.filter.mirror_filters.external_asset_origins(applications, application_id=None, primary_domain='')¶
Return the sorted list of external asset hosts.
application_id: restrict to one application; None aggregates across all. primary_domain: the deployment’s primary domain (its hosts are not external).
plugins.filter.on_off module¶
- plugins.filter.on_off.on_off(value)¶
plugins.filter.redirect_sources module¶
- plugins.filter.redirect_sources.redirect_sources(host: str) list[str]¶
Ordered match prefixes for a redirect that targets
host.- Parameters:
host – the upstream host, as it appears in the proxied URL.
- Returns:
The two host-qualified forms, then the bare
/for a target the upstream expressed as an absolute path.
plugins.filter.reserved_users module¶
- class plugins.filter.reserved_users.FilterModule¶
Bases:
objectUser filters for identity-directory subsets and reserved usernames.
- filters()¶
- plugins.filter.reserved_users.non_reserved_users(users_dict)¶
Return the subset of users that have an identity-directory account (
identityinaccounts). This is the set Keycloak/LDAP manage.
- plugins.filter.reserved_users.reserved_usernames(users_dict)¶
Return the list of usernames that do NOT have an identity-directory account (
identitynot inaccounts). These names are blocked from being registered as Keycloak usernames. Usernames are regex-escaped so the result is safe to embed in a deny pattern.
plugins.filter.resource_filter module¶
- plugins.filter.resource_filter.resource_filter(applications: dict, application_id: str, key: str, service_name: str, hard_default)¶
- Lookup order:
services.<service_name or get_entity_name(application_id)>.<key>
services.<get_entity_name(application_id)>.<key>
hard_default (mandatory)
service_name may be “” → will resolve to get_entity_name(application_id).
hard_default is mandatory (no implicit None).
required=False always.
plugins.filter.role_path_by_app_id module¶
- class plugins.filter.role_path_by_app_id.FilterModule¶
Bases:
objectProvides the filters abs_role_path_by_application_id and rel_role_path_by_application_id.
- filters()¶
plugins.filter.ruby module¶
- class plugins.filter.ruby.FilterModule¶
Bases:
object- filters()¶
- static ruby_double_quoted_string(value)¶
Escape a value for use inside a Ruby double-quoted string and wrap it in double quotes.
Ruby rules handled: - “ -> ” - -> \ - newlines ->
carriage return ->
tab ->
NOT escaped on purpose: - $ - € - ! - ‘
plugins.filter.seaweedfs module¶
- plugins.filter.seaweedfs.min_free_space()¶
- plugins.filter.seaweedfs.seaweedfs_anonymous_deny_policy(bucket)¶
Build the bucket policy that denies every unsigned read of bucket.
SeaweedFS honours a public-read object or bucket ACL on an unsigned read unless a bucket policy explicitly denies it first. A bare
Principal: "*"deny would also hit the consumer’s own signed and presigned requests; theNullcondition onaws:usernamelimits it to unsigned ones, because SeaweedFS derives that key from a signed caller’s IAM user ARN and evaluates an unsigned request as principal*without it.- Parameters:
bucket – bucket the policy is attached to; scopes both the bucket ARN (listings) and its object ARNs (GET/HEAD).
- plugins.filter.seaweedfs.seaweedfs_bucket_names(output)¶
Return the bucket names a
weed shells3.bucket.listprinted.- Parameters:
output – stdout of the shell; every bucket line carries the name followed by a tab and its
size:column, and the first one may follow the shell prompt on the same line.
- plugins.filter.seaweedfs.seaweedfs_bucket_policy_target(stored, bucket, public)¶
Return the policy bucket converges to.
Every statement except
DenyAnonymousReadis kept as stored; that one is replaced by the currentseaweedfs_anonymous_deny_policy()statement on a private bucket and dropped on a public one.- Parameters:
stored – policy document the bucket carries, an empty dict for none.
bucket – bucket the deny statement scopes.
public – whether the consumer is marked public; must be a bool.
- Returns:
The target policy document, or an empty dict when no statement remains and the bucket must carry no policy.
- plugins.filter.seaweedfs.seaweedfs_command(s3_config='', collections=None)¶
Build the SeaweedFS all-in-one
servercommand.Single source for the args shared by the standalone provider stack (
compose.yml.j2) and the local sidecar (service.yml.j2).-ip=localhostis deliberate: the all-in-one server otherwise advertises the auto-detected swarm ingress IP, which is unreachable for gRPC and hangsweed shellduring bucket provisioning.- Parameters:
s3_config – path to the mounted s3.json; appended as
-s3.configwhen truthy. The local sidecar passes""since it does not mount that config.collections – number of buckets this server serves, passed to
volume_slots(). Required; without it the stock entrypoint injects-volume.max=0and derives the ceiling from free disk space instead.
- plugins.filter.seaweedfs.seaweedfs_sidecar_script(bucket, s3_port, access_key, secret_key)¶
Build the
sh -cbootstrap script for the embedded sidecar.The sidecar creates its consumer’s bucket itself at startup because a swarm stack’s converge gate blocks before any post-deploy task could create it, deadlocking consumers that hard-fail on a missing bucket (e.g. the OpenTalk controller). The stock entrypoint routes every argv through
weed(shas a command would becomeweed sh), so the template overrides it withsh -c; the server itself is still launched via/entrypoint.shto keep its /data chown, privilege drop, and default server args.The script must stay free of every shell
$: the compose and swarm render paths launder dollars differently (a$pidreached the container as""and crash-looped the sidecar on a sh syntax error). The bucket bootstrap therefore polls/statusfrom a backgrounded subshell whileexecmakes the server PID 1; the poll loop dies with the container and readiness is gated by the consumer’s bounded wait.The
s3.configuregrant is required since SeaweedFS 4.39: a server started without an-s3.confighas zero identities, and ListBuckets then returns an empty list to the consumer’s access key, so the consumer reads the bucket as missing even though it exists.WriteAcpis required since SeaweedFS 4.49: a PutObject carryingx-amz-aclis also authorized as s3:PutObjectAcl and answers AccessDenied without it.- Parameters:
bucket – consumer bucket name created idempotently at startup.
s3_port – S3 API port polled via
/statusbefore bucket creation.access_key – consumer S3 access key granted access to the bucket.
secret_key – consumer S3 secret key.
- plugins.filter.seaweedfs.seaweedfs_stored_bucket_policy(output)¶
Return the policy SeaweedFS reports for a bucket.
- Parameters:
output – stdout of the admin curl
GET /<bucket>?policyrun with-w '\n%{http_code}': the response body, a newline, and the HTTP status.- Returns:
The stored policy document on HTTP 200, an empty dict on HTTP 404
NoSuchBucketPolicy, andNoneon every other answer, a missing bucket (NoSuchBucket) included.
- plugins.filter.seaweedfs.volume_slots(collections)¶
Volume-slot ceiling for a server hosting collections buckets.
- Parameters:
collections – number of buckets this server serves. Each bucket is a SeaweedFS collection, and the master grows a collection in batches of VOLUME_GROW_BATCH; the default collection takes one batch of its own, hence the +1.
plugins.filter.security_headers module¶
Expose the response security headers of one application to templates.
plugins.filter.sed module¶
plugins.filter.served_domains module¶
plugins.filter.split_postgres_connections module¶
- plugins.filter.split_postgres_connections.split_postgres_connections(total_connections, roles_dir='roles')¶
Return an integer average: total_connections / number_of_roles_with_database_type_postgres. Uses max(count, 1) to avoid division-by-zero.
plugins.filter.status_codes module¶
Expose the status codes that prove a domain is served to templates.
plugins.filter.swarm_ingress_guard module¶
Ports the swarm ingress publishes on every node that no application declares public.
- plugins.filter.swarm_ingress_guard.swarm_ingress_guarded(published: Iterable[Mapping[str, Any]], public: Iterable[int]) list[dict[str, Any]]¶
Drop the ports that are public by declaration from the published ones.
- Parameters:
published – the output of
swarm_ingress_published.public – the output of
swarm_public_ports.
- Returns:
The published entries whose port no application declares public.
- plugins.filter.swarm_ingress_guard.swarm_ingress_published(inspect_lines: Iterable[str]) list[dict[str, Any]]¶
Return every port the routing mesh publishes, one entry per protocol and port.
- Parameters:
inspect_lines – one
docker service inspect --format '{{json .Endpoint.Ports}}'document per service;nullfor a service that publishes nothing.- Returns:
{"protocol": "tcp"|"udp", "port": int}for eachingressentry, sorted and without duplicates; host-mode entries are left out.- Raises:
AnsibleFilterError – a line is not JSON, or an entry names a protocol other than tcp or udp.
- plugins.filter.swarm_ingress_guard.swarm_node_addresses(inspect_lines: Iterable[str]) dict[str, list[str]]¶
Return the addresses the swarm nodes reach each other from, split by family.
- Parameters:
inspect_lines – one
docker node inspect --format '{{.Status.Addr}}'line per node.- Returns:
{"ipv4": [...], "ipv6": [...]}, sorted and without duplicates; the unspecified address a node may report is left out.- Raises:
AnsibleFilterError – a line is not an IP address.
- plugins.filter.swarm_ingress_guard.swarm_public_ports(applications: Mapping[str, Any]) list[int]¶
Return every port any application declares under
services.*.ports.public.- Parameters:
applications – the merged
lookup('applications')mapping.- Returns:
The declared public ports,
{start, end}ranges expanded, sorted and without duplicates.
plugins.filter.text_filters module¶
- plugins.filter.text_filters.to_one_liner(s)¶
Collapse any multi-line string into a single line, trim extra whitespace, and remove JavaScript comments. Supports removal of both ‘//’ line comments and ‘/…/’ block comments, but preserves ‘//’ inside string literals and templating expressions.
plugins.filter.timeout_start_sec_for_domains module¶
- class plugins.filter.timeout_start_sec_for_domains.FilterModule¶
Bases:
object- filters()¶
- timeout_start_sec_for_domains(domains_dict, include_www=True, per_domain_seconds=25, onion_per_domain_seconds=None, overhead_seconds=30, min_seconds=120, max_seconds=3600)¶
- Parameters:
domains_dict (dict | list[str] | str) – Either the domain mapping dict (values can be str | list[str] | dict[str,str]) or an already flattened list of domains, or a single domain string.
include_www (bool) – If true, add ‘www.<domain>’ for non-www entries.
per_domain_seconds (int) – Budget a clearnet domain contributes.
onion_per_domain_seconds (int | None) – Budget a ‘.onion’ domain contributes; None gives it per_domain_seconds.
...
plugins.filter.to_primary_domain module¶
plugins.filter.value_generator module¶
- plugins.filter.value_generator.strong_password(length: int = 32) str¶