integration_openproject_provision.ymlΒΆ

---
- name: "πŸ” Resolve the OpenProject web hosting node and container id"
  ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/swarm/resolve_host_cid.yml') }}"
  vars:
    resolve_service_key: web
    resolve_service_name: "{{ lookup('container_service', 'web-app-openproject', 'web') }}"
    resolve_output_prefix: NEXTCLOUD_OPENPROJECT
    resolve_compose_cid: "{{ NEXTCLOUD_OPENPROJECT_CONTAINER_ADDRESS }}"
    resolve_converge_retries: 120
    resolve_converge_delay: 5

- name: ⏳ Wait for the OpenProject web endpoint
  wait_for:
    host: "{{ DOCKER_REACH_HOST }}"
    port: "{{ lookup('config', 'web-app-openproject', 'services.openproject.ports.local.http') }}"
    delay: 5
    timeout: "{{ lookup('timeout', 600) | int }}"

- name: πŸ”‘ Ensure the Nextcloud OAuth2 client OpenProject signs in with
  ansible.builtin.shell:
    cmd: >-
      {{ NEXTCLOUD_DOCKER_EXEC_INTERACTIVE }} php --
      {{ NEXTCLOUD_DOCKER_WORK_DIRECTORY | quote }}
      {{ NEXTCLOUD_OPENPROJECT_URL | quote }}
      {{ NEXTCLOUD_OPENPROJECT_CLIENT_NAME | quote }}
      {{ nextcloud_op_nc_client_current.stdout | trim | quote }}
    stdin: "{{ lookup('file', 'php/addons/openproject/ensure_nc_client.php') }}"
  register: nextcloud_op_ncclient_ensure
  changed_when: (nextcloud_op_ncclient_ensure.stdout | nextcloud_oauth_client_state).changed
  no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

- name: πŸ”Ž Build the OpenProject provisioning input
  set_fact:
    nextcloud_op_ncclient: "{{ nextcloud_op_ncclient_ensure.stdout | nextcloud_oauth_client_state }}"
    nextcloud_op_provision_input: |
      PROVISION_INPUT = JSON.parse(<<~'INPUT')
      {{ {
           'nextcloud_host': NEXTCLOUD_URL,
           'nextcloud_base_urls': NEXTCLOUD_NETWORK_BASE_URLS,
           'storage_name': nextcloud_op_storage_name,
           'nc_op_client_secret': nextcloud_op_client_secret_current.stdout | trim,
           'rotate_op_secret': lookup('config', 'web-app-nextcloud', 'addons.integration_openproject.config.rotate_oauth_client') | bool,
           'nc_oauth_client_id': (nextcloud_op_ncclient_ensure.stdout | nextcloud_oauth_client_state).client_id
             if (nextcloud_op_ncclient_ensure.stdout | nextcloud_oauth_client_state).action == 'created' else '',
           'nc_oauth_client_secret': (nextcloud_op_ncclient_ensure.stdout | nextcloud_oauth_client_state).client_secret
             if (nextcloud_op_ncclient_ensure.stdout | nextcloud_oauth_client_state).action == 'created' else ''
         } | to_json }}
      INPUT
  no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

- name: πŸ“ Ensure the OpenProject storage, OAuth application and Nextcloud client
  ansible.builtin.shell:
    cmd: >- # nocheck: container-exec-resolver  NEXTCLOUD_OPENPROJECT_LOCAL_CID resolved at delegate_to runtime; container_address would return a manager-only subshell
      container exec -i
      {{ NEXTCLOUD_OPENPROJECT_LOCAL_CID | quote }}
      bash -lc 'cd /app && bundle exec rails runner -'
    stdin: "{{ 'require \"json\"\n' ~ nextcloud_op_provision_input ~ lookup('file', 'ruby/utils/doorkeeper_allow_onion_redirect.rb') ~ '\n' ~ lookup('file', 'ruby/addons/openproject/ensure_nextcloud_storage.rb')
      }}"
  delegate_to: "{{ NEXTCLOUD_OPENPROJECT_HOST_NODE }}"
  register: nextcloud_op_storage_create
  changed_when: >-
    '"oauth_application_secret_issued":true' in nextcloud_op_storage_create.stdout
    or nextcloud_op_ncclient.action == 'created'
  no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

- name: ⬆️ Push a newly created Nextcloud OAuth2 client into integration_openproject
  shell: >
    {{ NEXTCLOUD_DOCKER_EXEC_OCC }} config:app:set integration_openproject nc_oauth_client_id
    --value={{ nextcloud_op_ncclient.id | string | quote }}
  when: nextcloud_op_ncclient.action == 'created'
  changed_when: true

- name: πŸ”Ž Parse the OpenProject OAuth application credentials
  set_fact:
    nextcloud_op_oauth: "{{ nextcloud_op_storage_create.stdout | regex_search('{.*}') | from_json }}"
  no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

- name: πŸ” Resolve the effective OpenProject OAuth client secret (fresh or persisted)
  set_fact:
    nextcloud_op_client_secret: >-
      {{ nextcloud_op_oauth.openproject_client_secret
         | default((nextcloud_op_client_secret_current.stdout | trim), true) }}
  no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

- name: πŸ›‘ Fail when no usable OpenProject OAuth application secret is available
  assert:
    that:
      - nextcloud_op_oauth.openproject_client_id | default('') | length > 0
      - (nextcloud_op_client_secret | default('') | trim) | length > 0
    fail_msg: >-
      OpenProject did not return a usable OAuth application and no client secret is persisted in
      Nextcloud. Set addons.integration_openproject.config.rotate_oauth_client to true for one
      deploy to issue a new secret.

- name: ⬆️ Push the OpenProject OAuth application credentials into Nextcloud
  ansible.builtin.shell:
    cmd: "{{ NEXTCLOUD_DOCKER_EXEC_INTERACTIVE }} sh -s"
    stdin: |
      set -eu
      {{ NEXTCLOUD_INTERNAL_OCC_COMMAND | quote }} config:app:set integration_openproject authorization_method --value=oauth2
      {{ NEXTCLOUD_INTERNAL_OCC_COMMAND | quote }} config:app:set integration_openproject openproject_client_id --value={{ nextcloud_op_oauth.openproject_client_id | quote }}
      {{ NEXTCLOUD_INTERNAL_OCC_COMMAND | quote }} config:app:set integration_openproject openproject_client_secret --value={{ nextcloud_op_client_secret | quote }}
  when: >-
    nextcloud_op_oauth.oauth_application_secret_issued
    or (nextcloud_op_client_id_current.stdout | trim) != nextcloud_op_oauth.openproject_client_id
  no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"