diff --git a/.ci/eda_v1alpha1_eda.activation_job_namespace.ci.yaml b/.ci/eda_v1alpha1_eda.activation_job_namespace.ci.yaml new file mode 100644 index 00000000..77d1b39d --- /dev/null +++ b/.ci/eda_v1alpha1_eda.activation_job_namespace.ci.yaml @@ -0,0 +1,11 @@ +apiVersion: eda.ansible.com/v1alpha1 +kind: EDA +metadata: + name: eda-demo + annotations: + "ansible.sdk.operatorframework.io/verbosity": "5" +spec: + no_log: false + automation_server_url: http://foo.bar + activation_worker: + activation_job_namespace: "eda-jobs" diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6feadce6..820a7fb1 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -16,6 +16,7 @@ jobs: - SCENARIO: externaldb - SCENARIO: ingress - SCENARIO: event_persistence + - SCENARIO: activation_job_namespace steps: - name: Checkout sources uses: actions/checkout@v4 @@ -68,6 +69,10 @@ jobs: run: kubectl apply -f .ci/eda-event-stream-external-database.secret.yaml if: ${{ matrix.SCENARIO == 'externaldb' }} + - name: Create activation job namespace + run: kubectl create namespace eda-jobs + if: ${{ matrix.SCENARIO == 'activation_job_namespace' }} + - name: Create the EDA demo CR run: | kubectl apply -f .ci/eda_v1alpha1_eda.${{ matrix.SCENARIO }}.ci.yaml diff --git a/config/crd/bases/eda.ansible.com_edas.yaml b/config/crd/bases/eda.ansible.com_edas.yaml index d5230b57..7af67851 100644 --- a/config/crd/bases/eda.ansible.com_edas.yaml +++ b/config/crd/bases/eda.ansible.com_edas.yaml @@ -1479,6 +1479,13 @@ spec: activation_worker: description: Defines desired state of eda-activation-worker resources properties: + activation_job_namespace: + description: Kubernetes namespace where activation job pods are + created. When set, jobs run in this namespace instead of the + EDA operator namespace. The operator creates the necessary + RBAC to allow the EDA service account to manage resources + in the target namespace. + type: string node_selector: additionalProperties: type: string diff --git a/config/rbac/activation_job_namespace_role.yaml b/config/rbac/activation_job_namespace_role.yaml new file mode 100644 index 00000000..09054130 --- /dev/null +++ b/config/rbac/activation_job_namespace_role.yaml @@ -0,0 +1,43 @@ +--- +# Cluster-scoped because the target namespace is user-configurable at +# runtime via spec.activation_worker.activation_job_namespace. The +# operator must be able to create Role/RoleBinding resources in whatever +# namespace the user specifies. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: eda-activation-job-namespace-manager +rules: + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + verbs: + - bind + - create + - delete + - escalate + - get + - list + - patch + - update + - watch + - apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/config/rbac/activation_job_namespace_role_binding.yaml b/config/rbac/activation_job_namespace_role_binding.yaml new file mode 100644 index 00000000..4e1ebee7 --- /dev/null +++ b/config/rbac/activation_job_namespace_role_binding.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: eda-activation-job-namespace-manager-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: eda-activation-job-namespace-manager +subjects: + - kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 02139c5c..88a7d201 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -12,3 +12,5 @@ resources: - metrics_auth_role.yaml - metrics_auth_role_binding.yaml - metrics_reader_role.yaml +- activation_job_namespace_role.yaml +- activation_job_namespace_role_binding.yaml diff --git a/roles/eda/defaults/main.yml b/roles/eda/defaults/main.yml index a5a30d11..b62aa207 100644 --- a/roles/eda/defaults/main.yml +++ b/roles/eda/defaults/main.yml @@ -58,6 +58,7 @@ _activation_worker: memory: 150Mi node_selector: {} tolerations: [] + activation_job_namespace: "" # Note: Deprecated "worker: {}" is intentionally excluded here so we know if the user set it _worker: {} diff --git a/roles/eda/tasks/deploy_eda.yml b/roles/eda/tasks/deploy_eda.yml index 144899de..d68d7dae 100644 --- a/roles/eda/tasks/deploy_eda.yml +++ b/roles/eda/tasks/deploy_eda.yml @@ -19,6 +19,22 @@ event_stream_mtls_base_url: "{{ public_base_url.rstrip('/') }}/{{ event_stream_mtls_prefix_path }}" when: public_base_url | default('') | length > 0 +- name: Look up existing ConfigMap for previous activation_job_namespace + kubernetes.core.k8s_info: + api_version: v1 + kind: ConfigMap + namespace: "{{ ansible_operator_meta.namespace }}" + name: "{{ ansible_operator_meta.name }}-{{ deployment_type }}-env-properties" + register: _eda_env_cm + +- name: Record previous activation_job_namespace from ConfigMap + ansible.builtin.set_fact: + _previous_activation_job_namespace: >- + {{ (_eda_env_cm.resources | first).data.EDA_ACTIVATION_JOB_NAMESPACE | default('') }} + when: + - _eda_env_cm.resources | length > 0 + - (_eda_env_cm.resources | first).data is defined + - name: Apply ConfigMap resources k8s: apply: yes @@ -38,6 +54,28 @@ wait: yes when: public_base_url is defined +- name: Apply cross-namespace RBAC for activation job pods + k8s: + apply: yes + definition: "{{ lookup('template', 'eda-activation-job-namespace-rbac.yaml.j2') }}" + wait: yes + when: combined_activation_worker.activation_job_namespace | default('') | length > 0 + +- name: Remove cross-namespace RBAC from previous namespace + k8s: + state: absent + api_version: rbac.authorization.k8s.io/v1 + kind: "{{ item.kind }}" + name: "{{ ansible_operator_meta.name }}-activation-job-manager" + namespace: "{{ _previous_activation_job_namespace }}" + loop: + - { kind: RoleBinding } + - { kind: Role } + when: + - _previous_activation_job_namespace | default('') | length > 0 + - combined_activation_worker.activation_job_namespace | default('') != _previous_activation_job_namespace + ignore_errors: yes + - name: Apply Backend deployment resources k8s: apply: yes diff --git a/roles/eda/templates/eda-activation-job-namespace-rbac.yaml.j2 b/roles/eda/templates/eda-activation-job-namespace-rbac.yaml.j2 new file mode 100644 index 00000000..eeb90bfd --- /dev/null +++ b/roles/eda/templates/eda-activation-job-namespace-rbac.yaml.j2 @@ -0,0 +1,65 @@ +# RBAC resources for cross-namespace activation job pods. +# Created when activation_job_namespace is set on the EDA CR. +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: '{{ ansible_operator_meta.name }}-activation-job-manager' + namespace: '{{ combined_activation_worker.activation_job_namespace }}' + labels: + {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} +rules: + - apiGroups: + - "" + resources: + - secrets + - pods + - pods/log + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - "" + resources: + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - batch + resources: + - jobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: '{{ ansible_operator_meta.name }}-activation-job-manager' + namespace: '{{ combined_activation_worker.activation_job_namespace }}' + labels: + {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: '{{ ansible_operator_meta.name }}-activation-job-manager' +subjects: + - kind: ServiceAccount + name: '{{ ansible_operator_meta.name }}' + namespace: '{{ ansible_operator_meta.namespace }}' diff --git a/roles/eda/templates/eda.configmap.yaml.j2 b/roles/eda/templates/eda.configmap.yaml.j2 index 55f62f7b..69f5b80d 100644 --- a/roles/eda/templates/eda.configmap.yaml.j2 +++ b/roles/eda/templates/eda.configmap.yaml.j2 @@ -34,6 +34,10 @@ data: EDA_STATIC_URL: /api/eda/static/ +{% if combined_activation_worker.activation_job_namespace | default('') | length > 0 %} + EDA_ACTIVATION_JOB_NAMESPACE: "{{ combined_activation_worker.activation_job_namespace }}" +{% endif %} + # Custom user variables {% for item in extra_settings | default([]) %} {{ item.setting | upper }}: "{{ item.value }}"