mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
refactor(roles): keep a container's environment in one file
A container's environment had two homes. sys-svc-container's base.yml.j2 already emits env_file for any role that ships templates/env.j2, and 89 roles do, yet fifty compose templates still opened an environment: block of their own. The half written there is the half nobody greps when they go looking for what a container is configured with, and a secret placed there also shows up in docker inspect, where an env file does not. Nine roles move their keys across: odoo twelve, jenkins five, zammad three, friendica and peertube two each, and one apiece for lmstudio, ollama, registry-cache and registry-docker. Four of them gain their first env.j2. The keys are carried over unchanged except where the new home demands it: YAML quotes are dropped, because an env file would take them as part of the value, and odoo's database password gains dotenv_quote, which the inline list never gave it. The remaining forty-one blocks keep a nocheck that names the services the role's single env.j2 would otherwise reach. Most are mcp sidecars whose ADAPTER_BEARER must not land in the application container beside them; gitlab's six services disagree about CONFIG_TEMPLATE_DIRECTORY; xwiki re-renders its compose template a second time with the auth switches flipped, which an env file rendered once cannot follow. Nine plus forty-one accounts for all fifty. A lint holds the line from here. It flags an environment: that opens a block mapping in any role's compose template and takes a marker on that line, on the comment block above it, or in the file's leading comment block. The leading-comment scope is deliberate: a raw line window would let a marker written for one service silence every later block in the same file, and a unit test pins exactly that, failing against the window and passing against the header. Measured on the tree: the key set each role moves is identical before and after, with no key lost or gained in any of the nine, and no moved value contained a dollar sign, so nothing changed meaning between compose interpolation and an env file that performs none. Dotted keys such as zammad's discovery.type survive both docker compose config and docker stack config unaltered. Not yet deployed. Eight of the nine cannot spread a key at all, being single service or, for jenkins, having the second service override docker_compose_env. zammad is the exception: three elasticsearch keys now reach its siblings, where ES_JAVA_OPTS is read only by a JVM entrypoint and the dotted names are not valid shell identifiers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
44260b8786
commit
37e80b957e
46 files changed
+261
-85
No files matched your search
@@ -13,8 +13,6 @@
|
||||
{{ lookup('compose_only', 'container_name', LMSTUDIO_CONTAINER) }}
|
||||
expose:
|
||||
- "{{ LMSTUDIO_PORT }}"
|
||||
environment:
|
||||
LMS_SERVER_HOST: "0.0.0.0"
|
||||
{{ lookup('container_volumes', service_name) | indent(4) }}
|
||||
{{ lookup('container_healthcheck', service_name) | indent(4) }}
|
||||
{{ lookup('container_networks') | indent(4) }}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{% include 'roles/sys-svc-container/templates/env.j2' %}
|
||||
|
||||
LMS_SERVER_HOST=0.0.0.0
|
||||
@@ -8,8 +8,6 @@
|
||||
{{ lookup('compose_only', 'container_name', OLLAMA_CONTAINER) }}
|
||||
expose:
|
||||
- "{{ OLLAMA_PORT }}"
|
||||
environment:
|
||||
OLLAMA_KEEP_ALIVE: "{{ OLLAMA_KEEP_ALIVE }}"
|
||||
{{ lookup('container_volumes', service_name) | indent(4) }}
|
||||
{{ lookup('container_networks') | indent(4) }}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{% include 'roles/sys-svc-container/templates/env.j2' %}
|
||||
|
||||
OLLAMA_KEEP_ALIVE={{ OLLAMA_KEEP_ALIVE }}
|
||||
@@ -29,7 +29,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file qdrantmcp is not the service env.j2 covers; that single env file is also loaded by the primary qdrant container, so moving these would hand qdrant ADAPTER_BEARER and ADAPTER_UPSTREAM_KEY
|
||||
ADAPTER_CONTRACT: {{ QDRANT_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ QDRANT_MCP_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ QDRANT_MCP_PORT }}"
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
{% if IS_COMPOSE_MODE %}
|
||||
{{ lookup('compose_only', 'container_name', REGISTRY_CACHE_CONTAINER) }}
|
||||
{% endif %}
|
||||
environment:
|
||||
REGISTRY_PROXY_REMOTEURL: "{{ REGISTRY_CACHE_PROXY_REMOTE_URL }}"
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
{{ lookup('container_healthcheck', service_name) | indent(4) }}
|
||||
{{ lookup('container_ports', ['cache', 'registry']) | indent(4) }}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{% include 'roles/sys-svc-container/templates/env.j2' %}
|
||||
|
||||
REGISTRY_PROXY_REMOTEURL={{ REGISTRY_CACHE_PROXY_REMOTE_URL }}
|
||||
@@ -4,10 +4,7 @@
|
||||
image: "{{ REGISTRY_IMAGE }}:{{ REGISTRY_VERSION }}" # nocheck: container-image-lookup
|
||||
{{ lookup('compose_only', 'container_name', REGISTRY_CONTAINER) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
{% if IS_SWARM_MODE %}
|
||||
environment:
|
||||
REGISTRY_HTTP_ADDR: "{{ REGISTRY_BIND_IP }}:{{ REGISTRY_PORT }}"
|
||||
{% else %}
|
||||
{% if not IS_SWARM_MODE %}
|
||||
{{ lookup('container_healthcheck', service_name) | indent(4) }}
|
||||
{{ lookup('container_ports', ['docker', 'registry']) | indent(4) }}
|
||||
{% endif %}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{% include 'roles/sys-svc-container/templates/env.j2' %}
|
||||
|
||||
{% if IS_SWARM_MODE %}
|
||||
REGISTRY_HTTP_ADDR={{ REGISTRY_BIND_IP }}:{{ REGISTRY_PORT }}
|
||||
{% endif %}
|
||||
@@ -31,7 +31,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file baserowmcp-only keys; this role sets no docker_compose_env, so its single env.j2 is loaded by the primary baserow container too and would receive ADAPTER_BEARER, and swarm does not unescape the $$ of ADAPTER_CONTRACT in an env file
|
||||
ADAPTER_CONTRACT: {{ BASEROW_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ BASEROW_MCP_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ BASEROW_MCP_PORT }}"
|
||||
|
||||
@@ -81,7 +81,7 @@ services:
|
||||
{{ service_name }}:
|
||||
{{ lookup('container_image', application_id, service_name) }}
|
||||
{{ lookup('container_volumes', service_name) | indent(4) }}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file MS_WEBRTC_LISTEN_IPS relies on compose expanding ${EXTERNAL_IPv4} from --env-file, which env_file values never get; compose_mods.py:39 also pins env.j2 onto every bbb service, so these mediasoup keys would reach all ~20 containers instead of webrtc-sfu
|
||||
MS_WORKERS: "1"
|
||||
MS_ENABLE_IPV6: "{{ BBB_IP6_ENABLED | bool | lower }}"
|
||||
MS_WEBRTC_LISTEN_IPS: >-
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
{{ lookup('compose_only', 'pull_policy', 'never') }}
|
||||
{{ lookup('compose_restart') }}
|
||||
{{ lookup('container_ports', [BLUESKY_BROKER_SERVICE, 'http', DOCKER_BIND_HOST]) | indent(4) }}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file broker has no env_file (only pds includes the container base), and PDS_ADMIN_PASSWORD carries compose $$ escaping that an env_file would not unescape
|
||||
BROKER_PORT: "{{ BLUESKY_BROKER_INTERNAL_PORT }}"
|
||||
SOCIAL_APP_URL: "http://web:{{ BLUESKY_WEB_INTERNAL_PORT }}"
|
||||
PDS_URL: "{{ lookup('tls', application_id, 'protocols.web') }}://{{ BLUESKY_API_DOMAIN }}"
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file checkmkmcp-only keys; the role's single env_file also feeds the primary checkmk container, which must not receive ADAPTER_BEARER or ADAPTER_UPSTREAM_KEY
|
||||
ADAPTER_CONTRACT: {{ CHECKMK_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ CHECKMK_MCP_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ CHECKMK_MCP_PORT }}"
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
command: ["bash", "-c", "{{ ERPNEXT_BACKEND_READY_GATE }} exec nginx-entrypoint.sh"]
|
||||
{{ lookup('depends_on', {'backend': 'service_started', 'websocket': 'service_started'}) }}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file nginx-entrypoint.sh inputs for frontend only; this role sets no docker_compose_env, so its one env_file reaches all 7 services
|
||||
BACKEND: "backend:{{ ERPNEXT_BACKEND_PORT }}"
|
||||
SOCKETIO: "websocket:{{ ERPNEXT_WEBSOCKET_PORT }}"
|
||||
FRAPPE_SITE_NAME_HEADER: "{{ ERPNEXT_SITE_NAME }}"
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
stop_signal: SIGTERM
|
||||
stop_grace_period: 30s
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file websocket-only keys; this role sets no docker_compose_env, so its single env.j2 is read by the espocrm and daemon services too, and files/shell/sidecar_boot.sh calls applyConfigEnv, which would then write the websocket listener's SUBSCRIBER_DSN bind address into the shared config.php from all three containers
|
||||
- ESPOCRM_CONFIG_USE_WEB_SOCKET=true
|
||||
- ESPOCRM_CONFIG_WEB_SOCKET_URL={{ lookup('tls', application_id, 'protocols.websocket') }}://{{ lookup('domain',application_id) }}/ws
|
||||
- ESPOCRM_CONFIG_WEB_SOCKET_ZERO_M_Q_SUBSCRIBER_DSN=tcp://*:7777
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file fidermcp-only keys; the role sets no docker_compose_env, so its single env.j2 is also the env_file of the primary fider container, which must not receive ADAPTER_BEARER
|
||||
ADAPTER_CONTRACT: {{ FIDER_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ FIDER_MCP_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ FIDER_MCP_PORT }}"
|
||||
|
||||
@@ -7,11 +7,6 @@
|
||||
args:
|
||||
FRIENDICA_IMAGE: "{{ FRIENDICA_IMAGE }}"
|
||||
FRIENDICA_VERSION: "{{ FRIENDICA_VERSION }}"
|
||||
environment:
|
||||
FRIENDICA_CONFIG_FILE: "{{ FRIENDICA_CONFIG_FILE }}"
|
||||
{% if IS_SWARM_MODE %}
|
||||
TASK_SLOT: "{% raw %}{{.Task.Slot}}{% endraw %}"
|
||||
{% endif %}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
{{ lookup('container_volumes', service_name) | indent(4) }}
|
||||
{{ lookup('container_ports', ['friendica', 'http', DOCKER_BIND_HOST]) | indent(4) }}
|
||||
|
||||
@@ -30,3 +30,8 @@ SMTP_FROM=no-reply
|
||||
|
||||
FRIENDICA_ADMIN_MAIL={{ lookup('users', 'administrator').email }}
|
||||
MAILNAME={{ lookup('users', 'administrator').email }}
|
||||
|
||||
FRIENDICA_CONFIG_FILE={{ FRIENDICA_CONFIG_FILE }}
|
||||
{% if IS_SWARM_MODE %}
|
||||
TASK_SLOT={% raw %}{{.Task.Slot}}{% endraw %}{{"\n"}}
|
||||
{% endif %}
|
||||
@@ -4,7 +4,7 @@
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
{{ lookup('container_image', application_id, 'api') }}
|
||||
command: celery -A funkwhale_api.taskapp worker -l INFO --concurrency={{FUNKWHALE_CELERYD_CONCURRENCY}}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file celeryworker-only key; no service here overrides docker_compose_env, so the role's single env.j2 is loaded by celerybeat, api, front, typesense and migrate too, none of which has C_FORCE_ROOT today
|
||||
- C_FORCE_ROOT=true
|
||||
{{ lookup('container_volumes', service_name) | indent(4) }}
|
||||
{{ lookup('container_depends_on', application_id, extra={
|
||||
@@ -44,7 +44,7 @@
|
||||
{{ lookup('container_image', application_id, 'funkwhale') }}
|
||||
{{ lookup('container_healthcheck', 'funkwhale') | indent(4) }}
|
||||
{{ lookup('depends_on', ['api']) }}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file front-only key read by the funkwhale image's nginx entrypoint; the role's single env.j2 also feeds celeryworker, celerybeat, api, typesense and migrate, none of which has NGINX_MAX_BODY_SIZE today
|
||||
- "NGINX_MAX_BODY_SIZE=100M"
|
||||
{{ lookup('container_volumes', service_name) | indent(4) }}
|
||||
{{ lookup('container_ports', ['funkwhale', 'http', DOCKER_BIND_HOST]) | indent(4) }}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
{{ lookup('container_image', application_id, 'giteamcpupstream') }}
|
||||
{{ lookup('compose_only', 'container_name', GITEA_MCP_SIDECAR_CONTAINER) }}
|
||||
command: {{ GITEA_MCP_COMMAND | to_json }}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file the sidecar shares the role's single env_file with the gitea server, so these keys would land in gitea too
|
||||
GITEA_HOST: "{{ GITEA_MCP_UPSTREAM_URL }}"
|
||||
GITEA_SCOPES: "{{ GITEA_MCP_TOOL_SCOPES }}"
|
||||
GITEA_READONLY: "true"
|
||||
@@ -48,7 +48,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file docker_compose_env points this service at the generated upstream.env, so env.j2 never reaches it
|
||||
ADAPTER_CONTRACT: {{ GITEA_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ GITEA_MCP_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ GITEA_MCP_PORT }}"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{{ lookup('compose_only', 'container_name', GITLAB_WEBSERVICE_CONTAINER) }}
|
||||
{{ lookup('container_image', application_id, service_name) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file six services share this role's single env.j2; CONFIG_TEMPLATE_DIRECTORY is /etc/gitaly/templates in gitaly and /srv/gitlab-config in shell, and GITLAB_WEBSERVER/ACTION_CABLE_IN_APP belong to webservice alone
|
||||
CONFIG_TEMPLATE_DIRECTORY: "/var/opt/gitlab/config/templates"
|
||||
CONFIG_DIRECTORY: "/srv/gitlab/config"
|
||||
GITLAB_WEBSERVER: "PUMA"
|
||||
@@ -22,7 +22,7 @@
|
||||
{{ lookup('compose_only', 'container_name', GITLAB_WORKHORSE_CONTAINER) }}
|
||||
{{ lookup('container_image', application_id, service_name) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file six services share this role's single env.j2; CONFIG_TEMPLATE_DIRECTORY differs in gitaly and shell, and the GITLAB_WORKHORSE_* keys belong to workhorse alone
|
||||
CONFIG_TEMPLATE_DIRECTORY: "/var/opt/gitlab/config/templates"
|
||||
CONFIG_DIRECTORY: "/srv/gitlab/config"
|
||||
GITLAB_WORKHORSE_AUTH_BACKEND: "http://{{ GITLAB_WEBSERVICE_SERVICE_NAME }}:{{ GITLAB_WEBSERVICE_PORT }}"
|
||||
@@ -42,7 +42,7 @@
|
||||
{{ lookup('compose_only', 'container_name', GITLAB_SIDEKIQ_CONTAINER) }}
|
||||
{{ lookup('container_image', application_id, service_name) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file six services share this role's single env.j2; CONFIG_TEMPLATE_DIRECTORY is /etc/gitaly/templates in gitaly and /srv/gitlab-config in shell, and gitaly carries no CONFIG_DIRECTORY at all
|
||||
CONFIG_TEMPLATE_DIRECTORY: "/var/opt/gitlab/config/templates"
|
||||
CONFIG_DIRECTORY: "/srv/gitlab/config"
|
||||
command: ["/scripts/wait-for-deps", "/scripts/process-wrapper"]
|
||||
@@ -59,7 +59,7 @@
|
||||
{{ lookup('compose_only', 'container_name', GITLAB_GITALY_CONTAINER) }}
|
||||
{{ lookup('container_image', application_id, service_name) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file gitaly's CONFIG_TEMPLATE_DIRECTORY differs from the five sibling services that share this role's single env.j2
|
||||
CONFIG_TEMPLATE_DIRECTORY: "/etc/gitaly/templates"
|
||||
{{ lookup('container_healthcheck', service_name) | indent(4) }}
|
||||
{{ lookup('container_depends_on', application_id) | indent(4) }}
|
||||
@@ -71,7 +71,7 @@
|
||||
{{ lookup('compose_only', 'container_name', GITLAB_SHELL_CONTAINER) }}
|
||||
{{ lookup('container_image', application_id, service_name) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file shell's CONFIG_TEMPLATE_DIRECTORY and CONFIG_DIRECTORY differ from the five sibling services that share this role's single env.j2, and SSH_DAEMON belongs to shell alone
|
||||
CONFIG_TEMPLATE_DIRECTORY: "/srv/gitlab-config"
|
||||
CONFIG_DIRECTORY: "/srv/gitlab-shell"
|
||||
SSH_DAEMON: "gitlab-sshd"
|
||||
@@ -93,7 +93,7 @@
|
||||
{{ lookup('compose_only', 'container_name', GITLAB_MIGRATIONS_CONTAINER) }}
|
||||
{{ lookup('container_image', application_id, service_name) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file six services share this role's single env.j2; CONFIG_TEMPLATE_DIRECTORY differs in gitaly and shell, and BYPASS_SCHEMA_VERSION belongs to the migrations job alone
|
||||
CONFIG_TEMPLATE_DIRECTORY: "/var/opt/gitlab/config/templates"
|
||||
CONFIG_DIRECTORY: "/srv/gitlab/config"
|
||||
BYPASS_SCHEMA_VERSION: "true"
|
||||
@@ -120,7 +120,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file gitlabmcp overrides docker_compose_env with mcp/upstream.env, so this role's env.j2 never reaches it and these keys would instead leak into the six GitLab services
|
||||
ADAPTER_CONTRACT: {{ GITLAB_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ GITLAB_MCP_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ GITLAB_MCP_PORT }}"
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file jellyfinmcp overrides docker_compose_env with mcp/upstream.env, so the role's templates/env.j2 never reaches it and these keys would instead land in the primary jellyfin container, which must not receive ADAPTER_BEARER
|
||||
ADAPTER_CONTRACT: {{ JELLYFIN_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ JELLYFIN_MCP_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ JELLYFIN_MCP_PORT }}"
|
||||
|
||||
@@ -8,14 +8,6 @@
|
||||
{{ lookup('container_image', application_id, 'jenkins') }}
|
||||
{{ lookup('compose_only', 'container_name', JENKINS_CONTAINER) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
JAVA_OPTS: "-Djenkins.install.runSetupWizard=false"
|
||||
CASC_JENKINS_CONFIG: {{ lookup('volume', 'web-app-jenkins', 'casc').target }}
|
||||
{% if JENKINS_MCP_ENABLED | bool %}
|
||||
JENKINS_MCP_TOKEN_FILE: "{{ JENKINS_MCP_TOKEN_FILE }}"
|
||||
JENKINS_MCP_TOKEN_NAME: "{{ JENKINS_MCP_TOKEN_NAME }}"
|
||||
JENKINS_MCP_USERNAME: "{{ JENKINS_MCP_USERNAME }}"
|
||||
{% endif %}
|
||||
{{ lookup('container_ports', ['jenkins', 'http', DOCKER_BIND_HOST]) | indent(4) }}
|
||||
{{ lookup('container_volumes', service_name) | indent(4) }}
|
||||
{{ lookup('container_healthcheck', service_name) | indent(4) }}
|
||||
@@ -39,7 +31,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file jenkinsmcp-only keys; this service overrides env_file with upstream.env, and the role's templates/env.j2 feeds the primary jenkins container, which must not receive ADAPTER_BEARER
|
||||
ADAPTER_CONTRACT: {{ JENKINS_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ JENKINS_MCP_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ JENKINS_MCP_PORT }}"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{% include 'roles/sys-svc-container/templates/env.j2' %}
|
||||
|
||||
JAVA_OPTS=-Djenkins.install.runSetupWizard=false
|
||||
CASC_JENKINS_CONFIG={{ lookup('volume', 'web-app-jenkins', 'casc').target }}
|
||||
{% if JENKINS_MCP_ENABLED | bool %}
|
||||
JENKINS_MCP_TOKEN_FILE={{ JENKINS_MCP_TOKEN_FILE }}
|
||||
JENKINS_MCP_TOKEN_NAME={{ JENKINS_MCP_TOKEN_NAME }}
|
||||
JENKINS_MCP_USERNAME={{ JENKINS_MCP_USERNAME }}
|
||||
{% endif %}
|
||||
@@ -24,7 +24,7 @@
|
||||
user: "110010100"
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
{{ lookup('depends_on', {KIX_BACKEND_SERVICE: 'service_healthy'} | combine({'redis': 'service_healthy'} if lookup('engine', 'redis', application_id, 'local') else {})) }}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file frontend-only keys; the role's single env.j2 is shared by the backend and proxy services, so moving them would inject REDIS_CACHE_PASSWORD and the frontend URLs into both
|
||||
- USE_SSL=0
|
||||
- REDIS_CACHE_HOST={{ lookup('engine', 'redis', application_id, 'host') }}
|
||||
- REDIS_CACHE_PORT={{ lookup('engine', 'redis', application_id, 'port') }}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file listmonkmcp-only keys; this role sets no docker_compose_env, so its single templates/env.j2 is also the env_file of the primary listmonk container, which must not receive ADAPTER_BEARER or ADAPTER_UPSTREAM_KEY
|
||||
ADAPTER_CONTRACT: {{ LISTMONK_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ LISTMONK_MCP_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ LISTMONK_MCP_PORT }}"
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
{{ lookup('container_image', application_id, 'search') }}
|
||||
{{ lookup('compose_only', 'container_name', MAGENTO_SEARCH_CONTAINER) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file search-only keys; this role sets no docker_compose_env, so its single templates/env.j2 is also the env_file of the nginx and php containers, which must not receive the OpenSearch daemon's discovery.type, plugins.security.disabled or OPENSEARCH_JAVA_OPTS
|
||||
- discovery.type=single-node
|
||||
- plugins.security.disabled=true
|
||||
- OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file mattermostmcp overrides docker_compose_env with mcp/upstream.env, so this role's env.j2 never reaches it and these keys would instead land in the primary mattermost container, which must not receive ADAPTER_BEARER; ADAPTER_CONTRACT also carries compose $$ escaping that an env_file would not unescape
|
||||
ADAPTER_CONTRACT: {{ MATTERMOST_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ MATTERMOST_MCP_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ MATTERMOST_MCP_PORT }}"
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
{{ lookup('container_volumes', service_name) | indent(4) }}
|
||||
{% if IS_SWARM_MODE %}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file the value is a swarm task-template placeholder, and this role's single env.j2 is also the env_file of context_agent, hpb, whiteboard and talk_recording
|
||||
TASK_SLOT: "{% raw %}{{.Task.Slot}}{% endraw %}"
|
||||
{% endif %}
|
||||
sysctls:
|
||||
@@ -51,7 +51,7 @@
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
{{ lookup('container_image', application_id, 'context_agent') }}
|
||||
{{ lookup('compose_only', 'container_name', NEXTCLOUD_CONTEXT_AGENT_CONTAINER) }}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file non-primary service on the shared env.j2; APP_SECRET would reach nextcloud, hpb, whiteboard and talk_recording, and env.j2 already binds NEXTCLOUD_URL to the public URL
|
||||
APP_ID: {{ NEXTCLOUD_CONTEXT_AGENT_SERVICE }}
|
||||
APP_VERSION: "{{ NEXTCLOUD_CONTEXT_AGENT_VERSION }}"
|
||||
APP_HOST: 0.0.0.0
|
||||
@@ -104,7 +104,7 @@
|
||||
{{ lookup('container_healthcheck', service_name) | indent(4) }}
|
||||
{{ lookup('compose_only', 'container_name', NEXTCLOUD_WHITEBOARD_CONTAINER) }}
|
||||
{{ lookup('container_volumes', service_name) | indent(4) }}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file non-primary service on the shared env.j2; NODE_OPTIONS would reach the nextcloud, context_agent, hpb and talk_recording containers
|
||||
- NODE_OPTIONS=--max-old-space-size={{ NEXTCLOUD_WHITEBOARD_MAX_OLD_SPACE_SIZE }}
|
||||
expose:
|
||||
- "{{ container_port }}"
|
||||
@@ -145,7 +145,7 @@
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
{{ lookup('container_image', application_id, 'talk_recording') }}
|
||||
{{ lookup('compose_only', 'container_name', NEXTCLOUD_RECORDING_CONTAINER) }}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file non-primary service on the shared env.j2; RECORDING_SECRET would reach nextcloud, context_agent, hpb and whiteboard
|
||||
- HPB_DOMAIN={{ NEXTCLOUD_HPB_DOMAIN }}
|
||||
- NC_DOMAIN={{ NEXTCLOUD_DOMAIN }}
|
||||
- RECORDING_SECRET={{ NEXTCLOUD_RECORDING_SECRET }}
|
||||
@@ -176,7 +176,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file this service sets docker_compose_env to upstream.env, so env.j2 never reaches it and the keys would land only in the five services that do read it
|
||||
ADAPTER_CONTRACT: {{ NEXTCLOUD_MCP_ADAPTER_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ NEXTCLOUD_MCP_ADAPTER_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ container_port }}"
|
||||
|
||||
@@ -19,21 +19,6 @@
|
||||
{{ lookup('container_ports',
|
||||
['odoo', 'http', DOCKER_BIND_HOST],
|
||||
['odoo', 'websocket', DOCKER_BIND_HOST]) | indent(4) }}
|
||||
environment:
|
||||
- HOST={{ ODOO_DB_HOST }}
|
||||
- USER={{ ODOO_DB_USER }}
|
||||
- PASSWORD={{ ODOO_DB_PASSWORD }}
|
||||
- ODOO_URL={{ ODOO_URL }}
|
||||
- ODOO_INIT_MODULES={{ ODOO_INIT_MODULES }}
|
||||
- ODOO_UPDATE_MARKER={{ ODOO_UPDATE_MARKER }}
|
||||
{% if ODOO_OIDC_ENABLED | bool %}
|
||||
- ODOO_OIDC_CLIENT_ID={{ ODOO_OIDC_CLIENT_ID }}
|
||||
- ODOO_OIDC_CLIENT_SECRET={{ ODOO_OIDC_CLIENT_SECRET }}
|
||||
- ODOO_OIDC_ISSUER_URL={{ ODOO_OIDC_ISSUER_URL }}
|
||||
- ODOO_OIDC_TOKEN_URL={{ ODOO_OIDC_TOKEN_URL }}
|
||||
- ODOO_OIDC_JWKS_URI={{ ODOO_OIDC_JWKS_URI }}
|
||||
- ODOO_OIDC_UID_FIELD={{ ODOO_OIDC_UID_FIELD }}
|
||||
{% endif %}
|
||||
{{ lookup('container_depends_on', application_id) | indent(4) }}
|
||||
{{ lookup('container_networks') | indent(4) }}
|
||||
{{ lookup('container_volumes', service_name) | indent(4) }}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{% include 'roles/sys-svc-container/templates/env.j2' %}
|
||||
|
||||
HOST={{ ODOO_DB_HOST }}
|
||||
USER={{ ODOO_DB_USER }}
|
||||
PASSWORD={{ ODOO_DB_PASSWORD | dotenv_quote }}
|
||||
ODOO_URL={{ ODOO_URL }}
|
||||
ODOO_INIT_MODULES={{ ODOO_INIT_MODULES }}
|
||||
ODOO_UPDATE_MARKER={{ ODOO_UPDATE_MARKER }}
|
||||
{% if ODOO_OIDC_ENABLED | bool %}
|
||||
ODOO_OIDC_CLIENT_ID={{ ODOO_OIDC_CLIENT_ID }}
|
||||
ODOO_OIDC_CLIENT_SECRET={{ ODOO_OIDC_CLIENT_SECRET }}
|
||||
ODOO_OIDC_ISSUER_URL={{ ODOO_OIDC_ISSUER_URL }}
|
||||
ODOO_OIDC_TOKEN_URL={{ ODOO_OIDC_TOKEN_URL }}
|
||||
ODOO_OIDC_JWKS_URI={{ ODOO_OIDC_JWKS_URI }}
|
||||
ODOO_OIDC_UID_FIELD={{ ODOO_OIDC_UID_FIELD }}
|
||||
{% endif %}
|
||||
@@ -23,7 +23,7 @@ x-op-app: &app
|
||||
{{ lookup('compose_only', 'pull_policy', 'never') }}
|
||||
command: "./docker/prod/proxy"
|
||||
{{ lookup('container_ports', ['openproject', 'http', DOCKER_BIND_HOST]) | indent(4) }}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file proxy-only key read by ./docker/prod/proxy; this role sets no docker_compose_env, so its single env.j2 is loaded by cache, web, worker, cron and seeder too, none of which has APP_HOST today
|
||||
APP_HOST: {{ OPENPROJECT_WEB_SERVICE }}
|
||||
{{ lookup('depends_on', [OPENPROJECT_WEB_SERVICE]) }}
|
||||
{{ lookup('container_volumes', service_name) | indent(4) }}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
{{ lookup('container_image', application_id, 'rabbit') }}
|
||||
{{ lookup('compose_only', 'container_name', OPENTALK_RABBIT_CONTAINER) }}
|
||||
{{ lookup('compose_restart') }}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file rabbit includes deploy.yml.j2 only, so it has no env_file; env.j2 would drop this key here and add it to opentalk and controller
|
||||
RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: "-rabbit consumer_timeout 30000"
|
||||
{{ lookup('container_healthcheck', service_name) | indent(4) }}
|
||||
{{ lookup('container_volumes', service_name) | indent(4) }}
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
{{ lookup('container_ports', ['peertube', 'http', DOCKER_BIND_HOST]) | indent(4) }}
|
||||
{% endif %}
|
||||
{{ lookup('container_volumes', service_name) | indent(4) }}
|
||||
environment:
|
||||
- NODE_OPTIONS=--max-old-space-size={{ PEERTUBE_MAX_OLD_SPACE_SIZE }}
|
||||
- PEERTUBE_TRANSCODING_CONCURRENCY={{ PEERTUBE_TRANSCODING_CONCURRENCY }}
|
||||
shm_size: "512m"
|
||||
tmpfs:
|
||||
- /tmp:size=1g,exec
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{% include 'roles/sys-svc-container/templates/env.j2' %}
|
||||
|
||||
NODE_OPTIONS=--max-old-space-size={{ PEERTUBE_MAX_OLD_SPACE_SIZE }}
|
||||
PEERTUBE_TRANSCODING_CONCURRENCY={{ PEERTUBE_TRANSCODING_CONCURRENCY }}
|
||||
|
||||
POSTGRES_USER={{ lookup('database', application_id, 'username') }}
|
||||
POSTGRES_PASSWORD={{ lookup('database', application_id, 'password') | dotenv_quote }}
|
||||
POSTGRES_DB={{ lookup('database', application_id, 'name') }}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
{{ lookup('compose_only', 'container_name', PIXELFED_WORKER_CONTAINER) }}
|
||||
{{ lookup('container_image', application_id, 'pixelfed') }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file worker-only override; no service here sets docker_compose_env, so the role's single env.j2 is also the env_file of the primary pixelfed container, where these same four keys are already set to true
|
||||
AUTORUN_LARAVEL_EVENT_CACHE: "false"
|
||||
AUTORUN_LARAVEL_ROUTE_CACHE: "false"
|
||||
AUTORUN_LARAVEL_VIEW_CACHE: "false"
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file pretixmcp-only keys; this role sets no docker_compose_env, so its single templates/env.j2 is also the env_file of the primary pretix container, which must not receive ADAPTER_BEARER or ADAPTER_UPSTREAM_KEY, and swarm does not unescape the $$ of ADAPTER_CONTRACT in an env file
|
||||
ADAPTER_CONTRACT: {{ PRETIX_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ PRETIX_MCP_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ PRETIX_MCP_PORT }}"
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
# nocheck: compose-environment-from-env-file the role's single env_file reaches all six services, so ADAPTER_BEARER would leak into prometheus, blackbox-exporter, alertmanager, cadvisor and node-exporter
|
||||
environment:
|
||||
ADAPTER_CONTRACT: {{ PROMETHEUS_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ PROMETHEUS_MCP_BEARER | to_json }}
|
||||
|
||||
@@ -78,7 +78,7 @@ x-environment: &shopware
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
{{ lookup('container_image', application_id, 'opensearch') }}
|
||||
{{ lookup('compose_only', 'container_name', SHOPWARE_OPENSEARCH_CONTAINER) }}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file opensearch is not the service the role's single env_file targets; the role sets no docker_compose_env, so env.j2 is shared by init/web/worker/scheduler too and these keys would leak into all four
|
||||
- discovery.type=single-node
|
||||
- plugins.security.disabled=true
|
||||
- bootstrap.memory_lock=true
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file snipeitmcp overrides docker_compose_env with mcp/upstream.env, so the role's templates/env.j2 never reaches it and these keys would instead land in the primary snipe-it container, which must not receive ADAPTER_BEARER; ADAPTER_CONTRACT also carries compose $$ escaping that an env_file would not unescape
|
||||
ADAPTER_CONTRACT: {{ SNIPEIT_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ SNIPEIT_MCP_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ SNIPEIT_MCP_PORT }}"
|
||||
|
||||
@@ -131,7 +131,7 @@ build:
|
||||
{{ lookup('compose_only', 'container_name', TAIGA_MANAGER_CONTAINER) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
{{ lookup('container_image', application_id, 'manager', image='taigaio/taiga-back', version=TAIGA_VERSION) }}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file manager-only key; this role sets no docker_compose_env, so its single env.j2 is the env_file of all nine services, and CELERY_ENABLED=False there would also reach the taiga back and async worker containers, which carry no such key today and run celery
|
||||
CELERY_ENABLED: "False"
|
||||
entrypoint: ["sleep", "infinity"]
|
||||
{{ lookup('container_depends_on', application_id, extra={
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{{ lookup('compose_only', 'container_name', XWIKI_CONTAINER) }}
|
||||
hostname: '{{ XWIKI_HOSTNAME}}'
|
||||
{{ lookup('container_ports', ['xwiki', 'http', DOCKER_BIND_HOST]) | indent(4) }}
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file JAVA_OPTS follows xwiki_oidc_enabled_switch/xwiki_ldap_enabled_switch, and tasks/02_manager_ops.yml flips those for a second render of compose.yml.j2 alone; env.j2 renders once under tasks/00_core.yml with both pinned false, so an env-file JAVA_OPTS would freeze the standard-auth branch and OIDC/LDAP would never reach the container
|
||||
JAVA_OPTS: >-
|
||||
{% if xwiki_oidc_enabled_switch| bool %}
|
||||
-Dxwiki.authentication.authservice=oidc
|
||||
|
||||
@@ -47,10 +47,6 @@
|
||||
{{ lookup('container_image', application_id, 'search') }}
|
||||
{{ lookup('compose_only', 'container_name', ZAMMAD_SEARCH_CONTAINER) }}
|
||||
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
|
||||
environment:
|
||||
- discovery.type=single-node
|
||||
- xpack.security.enabled=false
|
||||
- ES_JAVA_OPTS=-Xms512m -Xmx512m
|
||||
{{ lookup('container_volumes', service_name) | indent(4) }}
|
||||
{{ lookup('container_networks') | indent(4) }}
|
||||
|
||||
@@ -98,7 +94,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file mcp-adapter-only keys; the shared env.j2 would leak ADAPTER_BEARER into every zammad service, and swarm does not unescape the $$ of ADAPTER_CONTRACT in an env file
|
||||
ADAPTER_CONTRACT: {{ ZAMMAD_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ ZAMMAD_MCP_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ ZAMMAD_MCP_PORT }}"
|
||||
|
||||
@@ -16,6 +16,9 @@ ELASTICSEARCH_PORT={{ ZAMMAD_SEARCH_PORT }}
|
||||
ELASTICSEARCH_SCHEMA=http
|
||||
ELASTICSEARCH_NAMESPACE=zammad
|
||||
ELASTICSEARCH_REINDEX=true
|
||||
discovery.type=single-node
|
||||
xpack.security.enabled=false
|
||||
ES_JAVA_OPTS=-Xms512m -Xmx512m
|
||||
|
||||
RAILS_ENV=production
|
||||
RAILS_LOG_TO_STDOUT=true
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
environment: # nocheck: compose-environment-from-env-file libretranslatemcp-only keys; this role sets no docker_compose_env, so its single templates/env.j2 is also the env_file of the primary libretranslate container, which must not receive ADAPTER_BEARER or ADAPTER_UPSTREAM_KEY
|
||||
ADAPTER_CONTRACT: {{ LIBRETRANSLATE_MCP_CONTRACT | to_json | to_json | replace('$', '$$') }}
|
||||
ADAPTER_BEARER: {{ LIBRETRANSLATE_MCP_BEARER | to_json }}
|
||||
ADAPTER_PORT: "{{ LIBRETRANSLATE_MCP_PORT }}"
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
"""Lint guard: a compose template MUST NOT declare ``environment:`` inline.
|
||||
|
||||
A container's environment belongs in ``roles/<role>/templates/env.j2``. The
|
||||
shared container base wires it in on its own: ``sys-svc-container``'s
|
||||
base.yml.j2 emits ``env_file`` whenever the role ``has_env``, so a role that
|
||||
ships ``env.j2`` needs no ``environment:`` key at all. 89 roles already carry
|
||||
one.
|
||||
|
||||
An inline mapping in the compose template splits the same contract over two
|
||||
files, and the half in the compose template is the half nobody greps when
|
||||
they go looking for what a container is configured with. A secret written
|
||||
there also lands in ``docker inspect`` output, where the env file does not.
|
||||
|
||||
Allowed:
|
||||
|
||||
* No ``environment:`` at all, with the values in ``templates/env.j2``.
|
||||
* ``environment:`` whose value is on the same line, which is a variable or
|
||||
an empty mapping rather than a declaration.
|
||||
|
||||
Forbidden:
|
||||
|
||||
* A block mapping of keys written inline under ``environment:``.
|
||||
|
||||
Suppress with a ``# nocheck: compose-environment-from-env-file`` marker on
|
||||
the ``environment:`` line, on the contiguous comment lines above it, or in
|
||||
the template's leading comment block. Use it where a key genuinely cannot
|
||||
live in the env file, for example one a Jinja loop varies per service
|
||||
instance.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import unittest
|
||||
from dataclasses import dataclass
|
||||
|
||||
from utils.cache.files import read_text
|
||||
from utils.roles.mapping import ROLE_FILE_TEMPL_COMPOSE
|
||||
|
||||
from . import PROJECT_ROOT
|
||||
|
||||
RULE = "compose-environment-from-env-file"
|
||||
COMPOSE_GLOB = f"roles/*/{ROLE_FILE_TEMPL_COMPOSE}"
|
||||
EXTRA_COMPOSE_GLOB = "roles/*/templates/*compose*.yml.j2"
|
||||
_ENVIRONMENT_RE = re.compile(r"^(?P<indent>\s*)environment:(?P<rest>.*)$")
|
||||
_NOCHECK_RE = re.compile(rf"#\s*nocheck:\s*{re.escape(RULE)}\b")
|
||||
_COMMENT_RE = re.compile(r"#.*$")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Violation:
|
||||
file: str
|
||||
line_no: int
|
||||
|
||||
|
||||
def _header(lines: list[str]) -> list[str]:
|
||||
"""Return the file's leading comment block.
|
||||
|
||||
Args:
|
||||
lines: the template's lines.
|
||||
"""
|
||||
header = []
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
header.append(line)
|
||||
continue
|
||||
if stripped.startswith(("#", "{#")):
|
||||
header.append(line)
|
||||
continue
|
||||
break
|
||||
return header
|
||||
|
||||
|
||||
def _suppressed(lines: list[str], index: int) -> bool:
|
||||
"""Whether a nocheck naming this rule covers the line at *index*."""
|
||||
if _NOCHECK_RE.search(lines[index]):
|
||||
return True
|
||||
if any(_NOCHECK_RE.search(line) for line in _header(lines)):
|
||||
return True
|
||||
above = index - 1
|
||||
while above >= 0 and lines[above].lstrip().startswith("#"):
|
||||
if _NOCHECK_RE.search(lines[above]):
|
||||
return True
|
||||
above -= 1
|
||||
return False
|
||||
|
||||
|
||||
def _opens_block(lines: list[str], index: int, indent: str) -> bool:
|
||||
"""Whether the ``environment:`` at *index* opens a block mapping."""
|
||||
for line in lines[index + 1 :]:
|
||||
if not line.strip() or line.lstrip().startswith(("#", "{#", "{%")):
|
||||
continue
|
||||
return len(line) - len(line.lstrip()) > len(indent)
|
||||
return False
|
||||
|
||||
|
||||
def _violations(path) -> list[Violation]:
|
||||
rel = str(path.relative_to(PROJECT_ROOT))
|
||||
lines = read_text(str(path)).splitlines()
|
||||
found = []
|
||||
for index, line in enumerate(lines):
|
||||
match = _ENVIRONMENT_RE.match(line)
|
||||
if not match:
|
||||
continue
|
||||
if _COMMENT_RE.sub("", match.group("rest")).strip():
|
||||
continue
|
||||
if not _opens_block(lines, index, match.group("indent")):
|
||||
continue
|
||||
if _suppressed(lines, index):
|
||||
continue
|
||||
found.append(Violation(rel, index + 1))
|
||||
return found
|
||||
|
||||
|
||||
class TestComposeEnvironmentFromEnvFile(unittest.TestCase):
|
||||
def test_compose_templates_keep_their_environment_in_env_j2(self) -> None:
|
||||
paths = set(PROJECT_ROOT.glob(COMPOSE_GLOB)) | set(
|
||||
PROJECT_ROOT.glob(EXTRA_COMPOSE_GLOB)
|
||||
)
|
||||
offenders: list[Violation] = []
|
||||
for path in sorted(paths):
|
||||
offenders.extend(_violations(path))
|
||||
|
||||
report = "\n".join(f" - {v.file}:{v.line_no}" for v in offenders)
|
||||
self.assertFalse(
|
||||
offenders,
|
||||
f"{len(offenders)} compose template(s) declare environment: inline "
|
||||
"instead of keeping it in templates/env.j2:\n"
|
||||
f"{report}\n\n"
|
||||
"Move the keys to roles/<role>/templates/env.j2. The container base "
|
||||
"emits env_file for a role that has one, so the compose template "
|
||||
"needs no environment: key.\n"
|
||||
f"Suppress with `# nocheck: {RULE}` where a key cannot live there.",
|
||||
)
|
||||
|
||||
|
||||
class TestSuppression(unittest.TestCase):
|
||||
MARKER = f"# nocheck: {RULE} reason"
|
||||
|
||||
def test_a_marker_on_the_environment_line_suppresses_it(self) -> None:
|
||||
lines = ["services:", " a:", f" environment: {self.MARKER}", " K: v"]
|
||||
|
||||
self.assertTrue(_suppressed(lines, 2))
|
||||
|
||||
def test_a_marker_in_the_leading_comment_block_suppresses_the_file(self) -> None:
|
||||
lines = [f"{{# {self.MARKER} #}}", "services:", " a:", " environment:"]
|
||||
|
||||
self.assertTrue(_suppressed(lines, 3))
|
||||
|
||||
def test_a_marker_below_the_leading_block_covers_only_its_own_line(self) -> None:
|
||||
lines = [
|
||||
"services:",
|
||||
" a:",
|
||||
f" environment: {self.MARKER}",
|
||||
" K: v",
|
||||
" b:",
|
||||
" environment:",
|
||||
" K: v",
|
||||
]
|
||||
|
||||
self.assertFalse(
|
||||
_suppressed(lines, 5),
|
||||
"a marker written for one service must not silence a later block",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user