diff --git a/roles/web-app-openproject/tasks/01_manager_ops.yml b/roles/web-app-openproject/tasks/01_manager_ops.yml index fd34a3a50f..d2c0628857 100644 --- a/roles/web-app-openproject/tasks/01_manager_ops.yml +++ b/roles/web-app-openproject/tasks/01_manager_ops.yml @@ -33,12 +33,12 @@ - name: 🚿 flush docker service meta: flush_handlers -- name: 🚀 Deploy Settings - include_tasks: 02_settings.yml +- name: ▶️ Migrate the OpenProject database + include_tasks: 02_migrate.yml - name: 🔧 Setup LDAP include_tasks: 03_ldap.yml when: OPENPROJECT_LDAP_ENABLED | bool -- name: 📁 Create OpenProject Administrator +- name: 🔧 Apply settings and the OpenProject administrator include_tasks: 04_admin.yml diff --git a/roles/web-app-openproject/tasks/02_settings.yml b/roles/web-app-openproject/tasks/02_migrate.yml similarity index 75% rename from roles/web-app-openproject/tasks/02_settings.yml rename to roles/web-app-openproject/tasks/02_migrate.yml index 50a30dfd6e..0a6bd547bc 100644 --- a/roles/web-app-openproject/tasks/02_settings.yml +++ b/roles/web-app-openproject/tasks/02_migrate.yml @@ -50,18 +50,3 @@ ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/swarm/wait_converged.yml') }}" vars: wait_service_key: "{{ OPENPROJECT_WEB_SERVICE }}" - -- name: 🔧 Set settings in OpenProject (rails runner via STDIN from an external script) - ansible.builtin.shell: - cmd: >- # nocheck: container-exec-resolver OPENPROJECT_WEB_LOCAL_CID resolved at delegate_to runtime; container_address would return a manager-only subshell - container exec -i - -e OPENPROJECT_RAILS_SETTINGS={{ OPENPROJECT_RAILS_SETTINGS | to_json | quote }} - {{ OPENPROJECT_WEB_LOCAL_CID | quote }} - bash -lc "cd /app && bundle exec rails runner -" - stdin: "{{ lookup('file', 'ruby/apply_settings.rb') }}" - delegate_to: "{{ OPENPROJECT_WEB_HOST_NODE }}" - register: openproject_apply_settings - retries: 30 - delay: 5 - until: openproject_apply_settings.rc == 0 - no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" diff --git a/roles/web-app-openproject/tasks/04_admin.yml b/roles/web-app-openproject/tasks/04_admin.yml index f964176b91..798c22e1a9 100644 --- a/roles/web-app-openproject/tasks/04_admin.yml +++ b/roles/web-app-openproject/tasks/04_admin.yml @@ -6,42 +6,24 @@ resolve_output_prefix: OPENPROJECT_WEB resolve_compose_cid: "{{ OPENPROJECT_WEB_CONTAINER_ADDRESS }}" -- name: ✅ Ensure administrator user exists and is admin - ansible.builtin.command: - argv: - - container - - exec - - -i - - -e - - "OPENPROJECT_ADMIN_PASSWORD={{ OPENPROJECT_ADMINISTRATOR_PASSWORD }}" - - -e - - "OPENPROJECT_ADMIN_LOGIN={{ OPENPROJECT_ADMINISTRATOR_USERNAME }}" - - -e - - "OPENPROJECT_ADMIN_EMAIL={{ OPENPROJECT_ADMINISTRATOR_EMAIL }}" - - "{{ OPENPROJECT_WEB_LOCAL_CID }}" # nocheck: container-exec-resolver CID resolved at delegate_to runtime; container_address targets a manager-only subshell - - bash - - -lc - - "cd /app && bundle exec rails runner -" - stdin: "{{ lookup('file', 'ruby/ensure_admin_user.rb') }}" +- name: 🔧 Apply settings, ensure the administrator and link it to LDAP + ansible.builtin.shell: + cmd: >- # nocheck: container-exec-resolver CID resolved at delegate_to runtime; container_address targets a manager-only subshell + container exec -i + -e {{ ('OPENPROJECT_ADMIN_LOGIN=' ~ OPENPROJECT_ADMINISTRATOR_USERNAME) | quote }} + -e {{ ('OPENPROJECT_ADMIN_EMAIL=' ~ OPENPROJECT_ADMINISTRATOR_EMAIL) | quote }} + {% if OPENPROJECT_HEADER_SSO_ENABLED | bool %}-e {{ ('OPENPROJECT_LDAP_NAME=' ~ OPENPROJECT_LDAP.name) | quote }}{% endif %} + {{ OPENPROJECT_WEB_LOCAL_CID | quote }} + sh -c 'IFS= read -r OPENPROJECT_ADMIN_PASSWORD; IFS= read -r OPENPROJECT_RAILS_SETTINGS; export OPENPROJECT_ADMIN_PASSWORD OPENPROJECT_RAILS_SETTINGS; exec "$@"' sh bash -lc 'cd /app && bundle exec rails runner -' + stdin: |- + {{ OPENPROJECT_ADMINISTRATOR_PASSWORD }} + {{ OPENPROJECT_RAILS_SETTINGS | to_json }} + {{ lookup('file', 'ruby/apply_settings.rb') }} + {{ lookup('file', 'ruby/ensure_admin_user.rb') }} + {% if OPENPROJECT_HEADER_SSO_ENABLED | bool %}{{ lookup('file', 'ruby/link_admin_to_ldap.rb') }}{% endif %} delegate_to: "{{ OPENPROJECT_WEB_HOST_NODE }}" + register: openproject_rails_runner + retries: 30 + delay: 5 + until: openproject_rails_runner.rc == 0 no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" - -- name: 🔧 Link admin to LDAP auth source so trusted-header bridge can resolve it - ansible.builtin.command: - argv: - - container - - exec - - -i - - -e - - "OPENPROJECT_LDAP_NAME={{ OPENPROJECT_LDAP.name }}" - - -e - - "OPENPROJECT_ADMIN_LOGIN={{ OPENPROJECT_ADMINISTRATOR_USERNAME }}" - - "{{ OPENPROJECT_WEB_LOCAL_CID }}" # nocheck: container-exec-resolver CID resolved at delegate_to runtime; container_address targets a manager-only subshell - - bash - - -lc - - "cd /app && bundle exec rails runner -" - stdin: "{{ lookup('file', 'ruby/link_admin_to_ldap.rb') }}" - delegate_to: "{{ OPENPROJECT_WEB_HOST_NODE }}" - when: OPENPROJECT_HEADER_SSO_ENABLED | bool - register: openproject_ldap_link - changed_when: "'Linked' in openproject_ldap_link.stdout" diff --git a/roles/web-app-openproject/templates/playwright.env.j2 b/roles/web-app-openproject/templates/playwright.env.j2 index f93ab1cbd2..2abad26e23 100644 --- a/roles/web-app-openproject/templates/playwright.env.j2 +++ b/roles/web-app-openproject/templates/playwright.env.j2 @@ -25,10 +25,10 @@ OIDC_ISSUER_URL={{ OIDC.CLIENT.ISSUER_URL | dotenv_quote }} OPENPROJECT_BASE_URL={{ lookup('tls', application_id, 'url.base') | dotenv_quote }} -# nocheck: persona-block-rationale — administrator is deliberately NOT blocked: tasks/04_admin.yml:9-27 seeds a native OpenProject account whose password is the Keycloak administrator secret (files/ruby/ensure_admin_user.rb:5-18), so the journey runs in every variant; the flag stays rendered because the contract wants the opt-out state declared, not implied. +# nocheck: persona-block-rationale — administrator is deliberately NOT blocked: tasks/04_admin.yml:9-29 seeds a native OpenProject account whose password is the Keycloak administrator secret (files/ruby/ensure_admin_user.rb:5-18), so the journey runs in every variant; the flag stays rendered because the contract wants the opt-out state declared, not implied. PERSONA_ADMINISTRATOR_BLOCKED=false -# nocheck: persona-block-rationale — OpenProject is seeded with the administrator only (files/ruby/ensure_admin_user.rb:5-18); biber owns no OpenProject account and is auto-provisioned solely through the oauth2-proxy trusted-header bridge onto the LDAP auth source (meta/services.yml:96-109, vars/main.yml:45, tasks/04_admin.yml:29-45), which the sso: false variants remove (see README "Persona contract opt-outs"). +# nocheck: persona-block-rationale — OpenProject is seeded with the administrator only (files/ruby/ensure_admin_user.rb:5-18); biber owns no OpenProject account and is auto-provisioned solely through the oauth2-proxy trusted-header bridge onto the LDAP auth source (meta/services.yml:96-109, vars/main.yml:69, tasks/04_admin.yml:9-29), which the sso: false variants remove (see README "Persona contract opt-outs"). PERSONA_BIBER_BLOCKED={{ 'false' if (lookup('config', application_id, 'services.sso.enabled') | bool) else 'true' }} SSO_SERVICE_ENABLED={{ lookup('config', application_id, 'services.sso.enabled') | string | lower }}