├── roles ├── .placeholder ├── eda │ ├── files │ │ └── .placeholder │ ├── templates │ │ ├── .placeholder │ │ ├── secrets │ │ │ ├── admin_password_secret.yaml.j2 │ │ │ └── db_fields_encryption.yaml.j2 │ │ ├── eda-event-stream.service.yaml.j2 │ │ ├── eda-api.service.yaml.j2 │ │ ├── eda-ui.service.yaml.j2 │ │ ├── eda-event-stream.configmap.yaml.j2 │ │ ├── eda-api.ingress.yaml.j2 │ │ ├── eda-event-stream.ingress.yaml.j2 │ │ ├── eda-ui.ingress.yaml.j2 │ │ ├── eda-api.configmap.yaml.j2 │ │ └── redirect-page.configmap.html.j2 │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ ├── set_bundle_cacert.yml │ │ ├── load_route_tls_secret.yml │ │ ├── cleanup_migration_references.yml │ │ ├── set_images.yml │ │ ├── create_admin_user.yml │ │ ├── combine_defaults.yml │ │ ├── admin_password_configuration.yml │ │ ├── main.yml │ │ ├── db_fields_encryption_configuration.yml │ │ ├── idle_deployment.yml │ │ ├── update_status.yml │ │ └── migrate_data.yml │ ├── vars │ │ └── main.yml │ ├── README.md │ └── meta │ │ └── main.yml ├── common │ ├── vars │ │ └── main.yml │ ├── templates │ │ ├── labels │ │ │ ├── version.yaml.j2 │ │ │ ├── additional_labels.yaml.j2 │ │ │ └── common.yaml.j2 │ │ └── service_account.yaml.j2 │ ├── defaults │ │ └── main.yml │ ├── tasks │ │ ├── patch_labels.yml │ │ └── main.yml │ └── meta │ │ └── main.yml ├── redis │ ├── vars │ │ └── main.yml │ ├── tasks │ │ ├── create_default_config.yml │ │ ├── check_external_config.yml │ │ ├── check_default_config.yml │ │ ├── main.yml │ │ └── create_managed_redis.yml │ ├── defaults │ │ └── main.yml │ ├── templates │ │ ├── redis.secret.yaml.j2 │ │ ├── redis.service.yaml.j2 │ │ └── redis.deployment.yaml.j2 │ └── meta │ │ └── main.yml ├── backup │ ├── tasks │ │ ├── main.yml │ │ ├── cleanup.yml │ │ ├── delete_backup.yml │ │ ├── error_handling.yml │ │ ├── update_status.yml │ │ ├── finalizer.yml │ │ ├── dump_secret.yml │ │ ├── dump_generated_secret.yml │ │ ├── eda-cro.yml │ │ ├── secrets.yml │ │ ├── creation.yml │ │ └── init.yml │ ├── vars │ │ └── main.yml │ ├── templates │ │ ├── event.yml.j2 │ │ ├── backup_pvc.yml.j2 │ │ └── management-pod.yml.j2 │ ├── meta │ │ └── main.yml │ └── defaults │ │ └── main.yml ├── postgres │ ├── vars │ │ └── main.yml │ ├── tasks │ │ ├── combine_defaults.yml │ │ ├── scale_down_deployment.yml │ │ ├── main.yml │ │ ├── set_images.yml │ │ ├── set_variables.yml │ │ ├── create_managed_postgres.yml │ │ └── set_configuration_secret.yml │ ├── templates │ │ ├── postgres.secret.yaml.j2 │ │ ├── postgres_upgrade_secret.yaml.j2 │ │ ├── postgres.configmap.yaml.j2 │ │ └── postgres.service.yaml.j2 │ ├── defaults │ │ └── main.yml │ └── meta │ │ └── main.yml └── restore │ ├── templates │ ├── eda_object.yml.j2 │ ├── event.yml.j2 │ ├── secrets.yml.j2 │ └── management-pod.yml.j2 │ ├── vars │ └── main.yml │ ├── tasks │ ├── error_handling.yml │ ├── update_status.yml │ ├── cleanup.yml │ ├── import_vars.yml │ ├── deploy_eda.yml │ ├── main.yml │ └── secrets.yml │ ├── meta │ └── main.yml │ └── defaults │ └── main.yml ├── playbooks ├── .placeholder └── eda.yml ├── config ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── scorecard │ ├── bases │ │ └── config.yaml │ ├── patches │ │ ├── basic.config.yaml │ │ └── olm.config.yaml │ └── kustomization.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── default │ ├── manager_config_patch.yaml │ ├── kustomization.yaml │ └── manager_auth_proxy_patch.yaml ├── samples │ ├── kustomization.yaml │ ├── eda_v1alpha1_edabackup.yaml │ ├── eda_v1alpha1_edarestore.yaml │ └── eda_v1alpha1_eda.yaml ├── testing │ ├── manager_image.yaml │ ├── pull_policy │ │ ├── Never.yaml │ │ ├── Always.yaml │ │ └── IfNotPresent.yaml │ ├── debug_logs_patch.yaml │ └── kustomization.yaml ├── manifests │ └── kustomization.yaml ├── crd │ └── kustomization.yaml └── rbac │ ├── edabackup_viewer_role.yaml │ ├── edarestore_viewer_role.yaml │ ├── service_account.yaml │ ├── edabackup_editor_role.yaml │ ├── edarestore_editor_role.yaml │ ├── auth_proxy_client_clusterrole.yaml │ ├── role_binding.yaml │ ├── auth_proxy_role_binding.yaml │ ├── leader_election_role_binding.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_service.yaml │ ├── eda_viewer_role.yaml │ ├── kustomization.yaml │ ├── eda_editor_role.yaml │ ├── leader_election_role.yaml │ └── role.yaml ├── molecule ├── default │ ├── create.yml │ ├── converge.yml │ ├── tasks │ │ └── eda_test.yml │ ├── destroy.yml │ ├── kustomize.yml │ ├── prepare.yml │ ├── molecule.yml │ └── verify.yml └── kind │ ├── create.yml │ ├── destroy.yml │ ├── converge.yml │ └── molecule.yml ├── .dockerignore ├── dev ├── secrets │ ├── admin-password-secret.yml │ ├── custom-db-fields-encryption-secret.yml │ └── custom-pg-secret.yml ├── eda-cr │ ├── eda-minimal-openshift-cr.yml │ ├── lightweight-eda.yml │ ├── eda-db-configuration.yml │ ├── eda-resource-quota-cr.yml │ ├── eda-k8s-ing.yml │ ├── eda-k8s-nodeport-cr.yml │ └── eda-openshift-cr.yml ├── samples │ └── awx-connection-secret.yml └── catalogsource.yaml ├── vendor └── galaxy.ansible.com │ ├── cloud │ └── common │ │ └── cloud-common-2.1.1.tar.gz │ ├── kubernetes │ └── core │ │ └── kubernetes-core-3.2.0.tar.gz │ └── operator_sdk │ └── util │ └── operator_sdk-util-0.4.0.tar.gz ├── requirements.yml ├── eda-demo.yml ├── .ci ├── eda_v1alpha1_edabackup.ci.yaml ├── eda_v1alpha1_eda.ingress.ci.yaml ├── eda_v1alpha1_edarestore.ci.yaml ├── eda-external-database.secret.yaml ├── eda_v1alpha1_eda.externaldb.ci.yaml └── eda_v1alpha1_eda.default.ci.yaml ├── docs ├── user-guide │ ├── advanced-configuration │ │ ├── no-log.md │ │ ├── settings.md │ │ ├── trusting-a-custom-certificate-authority.md │ │ ├── assigning-eda-pods-to-specific-nodes.md │ │ └── deploying-a-specific-version.md │ └── redis-configuration.md ├── create-awx-token.md ├── development.md ├── kustomize-install.md ├── maintainers │ └── release.md ├── minikube-test-cluster.md ├── upgrade │ └── upgrading.md └── single-command-install.md ├── watches.yaml ├── .gitignore ├── PROJECT ├── down.sh ├── Dockerfile ├── .github └── workflows │ ├── build-operator-image.yaml │ └── stage.yaml └── CONTRIBUTING.md /roles/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playbooks/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /roles/eda/files/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /roles/eda/templates/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /roles/common/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for EDA 3 | -------------------------------------------------------------------------------- /roles/redis/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for EDA 3 | -------------------------------------------------------------------------------- /roles/eda/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for EDA 3 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /roles/common/templates/labels/version.yaml.j2: -------------------------------------------------------------------------------- 1 | app.kubernetes.io/version: '{{ _image.split(':')[-1] | truncate(63, True, '', 0) }}' 2 | -------------------------------------------------------------------------------- /molecule/default/create.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | tasks: [] 7 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /roles/common/templates/labels/additional_labels.yaml.j2: -------------------------------------------------------------------------------- 1 | {% for item in additional_labels_items | default([]) %} 2 | {{ item.key }}: '{{ item.value }}' 3 | {% endfor %} 4 | -------------------------------------------------------------------------------- /dev/secrets/admin-password-secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: custom-admin-password 6 | stringData: 7 | password: password 8 | 9 | -------------------------------------------------------------------------------- /dev/secrets/custom-db-fields-encryption-secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: custom-secret-key 6 | stringData: 7 | secret_key: supersecuresecretkey 8 | -------------------------------------------------------------------------------- /vendor/galaxy.ansible.com/cloud/common/cloud-common-2.1.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/eda-server-operator/HEAD/vendor/galaxy.ansible.com/cloud/common/cloud-common-2.1.1.tar.gz -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /vendor/galaxy.ansible.com/kubernetes/core/kubernetes-core-3.2.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/eda-server-operator/HEAD/vendor/galaxy.ansible.com/kubernetes/core/kubernetes-core-3.2.0.tar.gz -------------------------------------------------------------------------------- /requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | collections: 3 | - name: operator_sdk.util 4 | version: "0.5.0" 5 | - name: kubernetes.core 6 | version: "3.2.0" 7 | - name: cloud.common 8 | version: "3.0.0" 9 | -------------------------------------------------------------------------------- /vendor/galaxy.ansible.com/operator_sdk/util/operator_sdk-util-0.4.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/eda-server-operator/HEAD/vendor/galaxy.ansible.com/operator_sdk/util/operator_sdk-util-0.4.0.tar.gz -------------------------------------------------------------------------------- /roles/backup/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Run creation tasks 3 | include_tasks: creation.yml 4 | when: not finalizer_run 5 | 6 | - name: Run finalizer tasks 7 | include_tasks: finalizer.yml 8 | when: finalizer_run 9 | -------------------------------------------------------------------------------- /roles/postgres/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for PostgreSQL database 3 | 4 | supported_pg_version: 15 5 | _previous_upgraded_pg_version: 0 6 | _postgres_data_path: '/var/lib/pgsql/data/userdata' 7 | old_postgres_pod: [] 8 | -------------------------------------------------------------------------------- /roles/postgres/tasks/combine_defaults.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Combine default and custom variables from the CR for each component 4 | set_fact: 5 | combined_database: "{{ _database | combine (database, recursive=True) }}" 6 | -------------------------------------------------------------------------------- /roles/backup/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | deployment_type: "eda" 3 | _postgres_image: quay.io/sclorg/postgresql-15-c9s 4 | _postgres_image_version: latest 5 | backup_complete: false 6 | database_type: "unmanaged" 7 | supported_pg_version: 15 8 | -------------------------------------------------------------------------------- /eda-demo.yml: -------------------------------------------------------------------------------- 1 | apiVersion: eda.ansible.com/v1alpha1 2 | kind: EDA 3 | metadata: 4 | name: eda-demo 5 | spec: 6 | no_log: false 7 | extra_settings: 8 | - setting: EDA_ALLOW_LOCAL_RESOURCE_MANAGEMENT 9 | value: true 10 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: quay.io/ansible/eda-server-operator 8 | newTag: 0.0.1 9 | -------------------------------------------------------------------------------- /dev/eda-cr/eda-minimal-openshift-cr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: eda.ansible.com/v1alpha1 3 | kind: EDA 4 | metadata: 5 | name: eda 6 | namespace: eda-demo 7 | spec: 8 | ingress_type: route 9 | automation_server_url: awx.example.com 10 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: eda-manager 11 | -------------------------------------------------------------------------------- /roles/restore/templates/eda_object.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: '{{ api_version }}' 3 | kind: EDA 4 | metadata: 5 | name: '{{ deployment_name }}' 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | spec: 8 | {{ spec | to_yaml | indent(2) }} 9 | -------------------------------------------------------------------------------- /molecule/kind/create.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | tasks: 7 | - name: Create test kind cluster 8 | command: kind create cluster --name osdk-test --kubeconfig {{ kubeconfig }} 9 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - eda_v1alpha1_eda.yaml 4 | - eda_v1alpha1_edabackup.yaml 5 | - eda_v1alpha1_edarestore.yaml 6 | #+kubebuilder:scaffold:manifestskustomizesamples 7 | -------------------------------------------------------------------------------- /roles/backup/tasks/cleanup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Delete any existing management pod 4 | k8s: 5 | name: "{{ ansible_operator_meta.name }}-db-management" 6 | kind: Pod 7 | namespace: "{{ backup_pvc_namespace }}" 8 | state: absent 9 | force: true 10 | -------------------------------------------------------------------------------- /.ci/eda_v1alpha1_edabackup.ci.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: eda.ansible.com/v1alpha1 2 | kind: EDABackup 3 | metadata: 4 | name: eda-demo-backup 5 | annotations: 6 | "ansible.sdk.operatorframework.io/verbosity": "5" 7 | spec: 8 | no_log: false 9 | deployment_name: eda-demo 10 | -------------------------------------------------------------------------------- /dev/secrets/custom-pg-secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: custom-pg-secret 6 | stringData: 7 | database: 'eda' 8 | host: 'ca-postgres' 9 | password: 'test' 10 | port: '5432' 11 | type: 'managed' 12 | username: 'eda' 13 | 14 | -------------------------------------------------------------------------------- /config/testing/manager_image.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | spec: 8 | template: 9 | spec: 10 | containers: 11 | - name: eda-manager 12 | image: testing 13 | -------------------------------------------------------------------------------- /dev/samples/awx-connection-secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: awx-connection-secret 6 | stringData: 7 | url: 'https://example-awx-awx.apps.aap-dev.ocp4.testing.ansible.com' 8 | token: 'redacted-token-value' 9 | ssl_verify: 'no' 10 | -------------------------------------------------------------------------------- /config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # These resources constitute the fully configured set of manifests 2 | # used to generate the 'manifests/' directory in a bundle. 3 | resources: 4 | - bases/eda-server-operator.clusterserviceversion.yaml 5 | - ../default 6 | - ../samples 7 | - ../scorecard 8 | -------------------------------------------------------------------------------- /config/testing/pull_policy/Never.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | spec: 8 | template: 9 | spec: 10 | containers: 11 | - name: eda-manager 12 | imagePullPolicy: Never 13 | -------------------------------------------------------------------------------- /roles/backup/tasks/delete_backup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Cleanup backup associated with this option if enabled 3 | k8s_exec: 4 | namespace: "{{ backup_pvc_namespace }}" 5 | pod: "{{ ansible_operator_meta.name }}-db-management" 6 | command: >- 7 | bash -c 'rm -rf {{ backup_dir }}' 8 | -------------------------------------------------------------------------------- /config/testing/pull_policy/Always.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | spec: 8 | template: 9 | spec: 10 | containers: 11 | - name: eda-manager 12 | imagePullPolicy: Always 13 | -------------------------------------------------------------------------------- /.ci/eda_v1alpha1_eda.ingress.ci.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: eda.ansible.com/v1alpha1 2 | kind: EDA 3 | metadata: 4 | name: eda-demo 5 | annotations: 6 | "ansible.sdk.operatorframework.io/verbosity": "5" 7 | spec: 8 | no_log: false 9 | automation_server_url: http://foo.bar 10 | ingress_type: ingress 11 | -------------------------------------------------------------------------------- /config/testing/pull_policy/IfNotPresent.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | spec: 8 | template: 9 | spec: 10 | containers: 11 | - name: eda-manager 12 | imagePullPolicy: IfNotPresent 13 | -------------------------------------------------------------------------------- /.ci/eda_v1alpha1_edarestore.ci.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: eda.ansible.com/v1alpha1 2 | kind: EDARestore 3 | metadata: 4 | name: eda-demo-restore 5 | annotations: 6 | "ansible.sdk.operatorframework.io/verbosity": "5" 7 | spec: 8 | no_log: false 9 | deployment_name: eda-demo 10 | backup_name: eda-demo-backup 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.27.0 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /.ci/eda-external-database.secret.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: 'eda-demo-external-database' 6 | stringData: 7 | host: 'eda-postgresql' 8 | port: '5555' 9 | database: 'eda' 10 | username: 'eda' 11 | password: 'eda' 12 | sslmode: 'prefer' 13 | type: 'unmanaged' 14 | type: Opaque 15 | -------------------------------------------------------------------------------- /config/testing/debug_logs_patch.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | spec: 8 | template: 9 | spec: 10 | containers: 11 | - name: eda-manager 12 | env: 13 | - name: ANSIBLE_DEBUG_LOGS 14 | value: "TRUE" 15 | -------------------------------------------------------------------------------- /roles/restore/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | deployment_type: "eda" 4 | supported_pg_version: 15 5 | _postgres_image: quay.io/sclorg/postgresql-15-c9s 6 | _postgres_image_version: latest 7 | 8 | backup_api_version: '{{ deployment_type }}.ansible.com/v1alpha1' 9 | backup_kind: 'EDABackup' 10 | 11 | force_drop_db: false 12 | pg_drop_create: '' 13 | -------------------------------------------------------------------------------- /.ci/eda_v1alpha1_eda.externaldb.ci.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: eda.ansible.com/v1alpha1 2 | kind: EDA 3 | metadata: 4 | name: eda-demo 5 | annotations: 6 | "ansible.sdk.operatorframework.io/verbosity": "5" 7 | spec: 8 | no_log: false 9 | automation_server_url: http://foo.bar 10 | database: 11 | database_secret: eda-demo-external-database 12 | -------------------------------------------------------------------------------- /roles/backup/tasks/error_handling.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Determine the timestamp 4 | set_fact: 5 | now: '{{ lookup("pipe", "date +%FT%TZ") }}' 6 | 7 | - name: Emit ocp event with error 8 | k8s: 9 | kind: Event 10 | namespace: "{{ ansible_operator_meta.namespace }}" 11 | definition: "{{ lookup('template', 'event.yml.j2') }}" 12 | -------------------------------------------------------------------------------- /roles/restore/tasks/error_handling.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Determine the timestamp 4 | set_fact: 5 | now: '{{ lookup("pipe", "date +%FT%TZ") }}' 6 | 7 | - name: Emit ocp event with error 8 | k8s: 9 | kind: Event 10 | namespace: "{{ ansible_operator_meta.namespace }}" 11 | definition: "{{ lookup('template', 'event.yml.j2') }}" 12 | -------------------------------------------------------------------------------- /dev/catalogsource.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: operators.coreos.com/v1alpha1 3 | kind: CatalogSource 4 | metadata: 5 | name: dev-eda-operator 6 | namespace: partner-eda 7 | spec: 8 | displayName: 'Ansible Dev EDA Operator' 9 | image: "quay.io/ansible/eda-server-operator-catalog:0.0.2" 10 | publisher: 'Ansible EDA Dev Team' 11 | sourceType: grpc 12 | -------------------------------------------------------------------------------- /config/samples/eda_v1alpha1_edabackup.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: eda.ansible.com/v1alpha1 2 | kind: EDABackup 3 | metadata: 4 | name: example-eda-backup 5 | spec: 6 | deployment_name: example-eda 7 | backup_resource_requirements: 8 | limits: 9 | cpu: "1000m" 10 | memory: "4096Mi" 11 | requests: 12 | cpu: "25m" 13 | memory: "32Mi" 14 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/no-log.md: -------------------------------------------------------------------------------- 1 | ## No Log 2 | 3 | Configure no_log for tasks with no_log 4 | 5 | | Name | Description | Default | 6 | | ------ | -------------------- | ------- | 7 | | no_log | No log configuration | 'true' | 8 | 9 | Example configuration of `no_log` parameter 10 | 11 | ```yaml 12 | spec: 13 | no_log: true 14 | ``` 15 | -------------------------------------------------------------------------------- /roles/restore/tasks/update_status.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Update CR Restore status 4 | operator_sdk.util.k8s_status: 5 | api_version: '{{ api_version }}' 6 | kind: "{{ kind }}" 7 | name: "{{ ansible_operator_meta.name }}" 8 | namespace: "{{ ansible_operator_meta.namespace }}" 9 | status: 10 | restoreComplete: true 11 | when: eda_restore_complete is defined 12 | -------------------------------------------------------------------------------- /config/samples/eda_v1alpha1_edarestore.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: eda.ansible.com/v1alpha1 2 | kind: EDARestore 3 | metadata: 4 | name: eda-restore-sample 5 | spec: 6 | deployment_name: example-eda-2 7 | backup_name: example-eda-backup 8 | restore_resource_requirements: 9 | limits: 10 | cpu: "1000m" 11 | memory: "4096Mi" 12 | requests: 13 | cpu: "25m" 14 | memory: "32Mi" 15 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/eda.ansible.com_edas.yaml 6 | - bases/eda.ansible.com_edabackups.yaml 7 | - bases/eda.ansible.com_edarestores.yaml 8 | #+kubebuilder:scaffold:crdkustomizeresource 9 | -------------------------------------------------------------------------------- /.ci/eda_v1alpha1_eda.default.ci.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: eda.ansible.com/v1alpha1 2 | kind: EDA 3 | metadata: 4 | name: eda-demo 5 | annotations: 6 | "ansible.sdk.operatorframework.io/verbosity": "5" 7 | spec: 8 | no_log: false 9 | automation_server_url: http://foo.bar 10 | ui_disabled: true 11 | 12 | database: 13 | postgres_extra_settings: 14 | - setting: max_connections 15 | value: '499' 16 | -------------------------------------------------------------------------------- /molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: localhost 4 | connection: local 5 | gather_facts: no 6 | collections: 7 | - kubernetes.core 8 | 9 | tasks: 10 | - name: Create Namespace 11 | k8s: 12 | api_version: v1 13 | kind: Namespace 14 | name: '{{ namespace }}' 15 | 16 | - import_tasks: kustomize.yml 17 | vars: 18 | state: present 19 | -------------------------------------------------------------------------------- /config/rbac/edabackup_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view edabackups. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: edabackup-viewer-role 6 | rules: 7 | - apiGroups: 8 | - eda.ansible.com 9 | resources: 10 | - edabackups 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - eda.ansible.com 17 | resources: 18 | - edabackups/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/edarestore_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view edarestores. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: edarestore-viewer-role 6 | rules: 7 | - apiGroups: 8 | - eda.ansible.com 9 | resources: 10 | - edarestores 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - eda.ansible.com 17 | resources: 18 | - edarestores/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: serviceaccount 6 | app.kubernetes.io/instance: controller-manager-sa 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: eda-server-operator 9 | app.kubernetes.io/part-of: eda-server-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /roles/common/templates/labels/common.yaml.j2: -------------------------------------------------------------------------------- 1 | # https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ 2 | app.kubernetes.io/name: '{{ ansible_operator_meta.name }}' 3 | app.kubernetes.io/part-of: '{{ ansible_operator_meta.name }}' 4 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 5 | app.kubernetes.io/operator-version: '{{ lookup("env", "OPERATOR_VERSION") }}' 6 | {{ lookup("template", "../common/templates/labels/additional_labels.yaml.j2") }} 7 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | #+kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /roles/backup/tasks/update_status.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # The backup directory in this status can be referenced when restoring 4 | - name: Update CR Backup status 5 | operator_sdk.util.k8s_status: 6 | api_version: '{{ api_version }}' 7 | kind: "{{ kind }}" 8 | name: "{{ ansible_operator_meta.name }}" 9 | namespace: "{{ ansible_operator_meta.namespace }}" 10 | status: 11 | backupDirectory: "{{ backup_dir }}" 12 | backupClaim: "{{ backup_claim }}" 13 | when: backup_complete 14 | -------------------------------------------------------------------------------- /config/rbac/edabackup_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit edabackups. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: edabackup-editor-role 6 | rules: 7 | - apiGroups: 8 | - eda.ansible.com 9 | resources: 10 | - edabackups 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - eda.ansible.com 21 | resources: 22 | - edabackups/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /molecule/kind/destroy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Destroy 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | collections: 7 | - kubernetes.core 8 | 9 | tasks: 10 | - name: Destroy test kind cluster 11 | command: kind delete cluster --name osdk-test --kubeconfig {{ kubeconfig }} 12 | 13 | - name: Unset pull policy 14 | command: '{{ kustomize }} edit remove patch pull_policy/{{ operator_pull_policy }}.yaml' 15 | args: 16 | chdir: '{{ config_dir }}/testing' 17 | -------------------------------------------------------------------------------- /roles/backup/templates/event.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Event 4 | metadata: 5 | name: backup-error.{{ now }} 6 | namespace: "{{ ansible_operator_meta.namespace }}" 7 | involvedObject: 8 | apiVersion: eda.ansible.com/v1alpha1 9 | kind: {{ kind }} 10 | name: {{ ansible_operator_meta.name }} 11 | namespace: "{{ ansible_operator_meta.namespace }}" 12 | message: {{ error_msg }} 13 | reason: BackupFailed 14 | type: Warning 15 | firstTimestamp: {{ now }} 16 | lastTimestamp: {{ now }} 17 | count: 1 18 | -------------------------------------------------------------------------------- /roles/restore/templates/event.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Event 4 | metadata: 5 | name: restore-error.{{ now }} 6 | namespace: "{{ ansible_operator_meta.namespace }}" 7 | involvedObject: 8 | apiVersion: eda.ansible.com/v1alpha1 9 | kind: {{ kind }} 10 | name: {{ ansible_operator_meta.name }} 11 | namespace: "{{ ansible_operator_meta.namespace }}" 12 | message: {{ error_msg }} 13 | reason: RestoreFailed 14 | type: Warning 15 | firstTimestamp: {{ now }} 16 | lastTimestamp: {{ now }} 17 | count: 1 18 | -------------------------------------------------------------------------------- /config/rbac/edarestore_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit edarestores. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: edarestore-editor-role 6 | rules: 7 | - apiGroups: 8 | - eda.ansible.com 9 | resources: 10 | - edarestores 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - eda.ansible.com 21 | resources: 22 | - edarestores/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: metrics-reader 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: eda-server-operator 9 | app.kubernetes.io/part-of: eda-server-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: metrics-reader 12 | rules: 13 | - nonResourceURLs: 14 | - "/metrics" 15 | verbs: 16 | - get 17 | -------------------------------------------------------------------------------- /watches.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - version: v1alpha1 3 | group: eda.ansible.com 4 | kind: EDA 5 | playbook: playbooks/eda.yml 6 | 7 | - version: v1alpha1 8 | group: eda.ansible.com 9 | kind: EDABackup 10 | role: backup 11 | snakeCaseParameters: False 12 | finalizer: 13 | name: eda.ansible.com/finalizer 14 | role: backup 15 | vars: 16 | finalizer_run: true 17 | 18 | - version: v1alpha1 19 | group: eda.ansible.com 20 | kind: EDARestore 21 | role: restore 22 | snakeCaseParameters: False 23 | 24 | #+kubebuilder:scaffold:watch 25 | -------------------------------------------------------------------------------- /roles/postgres/templates/postgres.secret.yaml.j2: -------------------------------------------------------------------------------- 1 | # Postgres Secret. 2 | --- 3 | apiVersion: v1 4 | kind: Secret 5 | metadata: 6 | name: '{{ ansible_operator_meta.name }}-postgres-configuration' 7 | namespace: '{{ ansible_operator_meta.namespace }}' 8 | stringData: 9 | password: '{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}' 10 | username: '{{ database_username }}' 11 | database: '{{ database_name }}' 12 | port: '5432' 13 | host: {{ ansible_operator_meta.name }}-postgres-{{ supported_pg_version }} 14 | type: 'managed' 15 | -------------------------------------------------------------------------------- /config/testing/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: osdk-test 3 | 4 | namePrefix: osdk- 5 | 6 | # Labels to add to all resources and selectors. 7 | #commonLabels: 8 | # someName: someValue 9 | 10 | 11 | apiVersion: kustomize.config.k8s.io/v1beta1 12 | kind: Kustomization 13 | resources: 14 | - ../crd 15 | - ../rbac 16 | - ../manager 17 | images: 18 | - name: testing 19 | newName: testing-operator 20 | patches: 21 | - path: manager_image.yaml 22 | - path: debug_logs_patch.yaml 23 | - path: ../default/manager_auth_proxy_patch.yaml 24 | -------------------------------------------------------------------------------- /molecule/default/tasks/eda_test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create the eda.ansible.com/v1alpha1.EDA 3 | k8s: 4 | state: present 5 | namespace: '{{ namespace }}' 6 | definition: "{{ lookup('template', '/'.join([samples_dir, cr_file])) | from_yaml }}" 7 | wait: yes 8 | wait_timeout: 300 9 | wait_condition: 10 | type: Successful 11 | status: "True" 12 | vars: 13 | cr_file: 'eda_v1alpha1_eda.yaml' 14 | 15 | - name: Add assertions here 16 | assert: 17 | that: false 18 | fail_msg: FIXME Add real assertions for your operator 19 | -------------------------------------------------------------------------------- /roles/eda/tasks/set_bundle_cacert.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Retrieve bundle Certificate Authority Secret 3 | k8s_info: 4 | kind: Secret 5 | namespace: '{{ ansible_operator_meta.namespace }}' 6 | name: '{{ bundle_cacert_secret }}' 7 | register: bundle_cacert 8 | no_log: "{{ no_log }}" 9 | 10 | - name: Load bundle Certificate Authority Secret content 11 | set_fact: 12 | bundle_ca_crt: '{{ bundle_cacert["resources"][0]["data"]["bundle-ca.crt"] | b64decode }}' 13 | no_log: "{{ no_log }}" 14 | when: '"bundle-ca.crt" in bundle_cacert["resources"][0]["data"]' 15 | -------------------------------------------------------------------------------- /roles/restore/tasks/cleanup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Delete any existing management pod 4 | k8s: 5 | name: "{{ ansible_operator_meta.name }}-db-management" 6 | kind: Pod 7 | namespace: "{{ backup_pvc_namespace }}" 8 | state: absent 9 | force: true 10 | 11 | - name: Cleanup temp spec file 12 | file: 13 | path: "{{ tmp_spec.path }}" 14 | state: absent 15 | when: tmp_spec.path is defined 16 | 17 | - name: Cleanup temp secret vars file 18 | file: 19 | path: "{{ secret_vars.path }}" 20 | state: absent 21 | when: secret_vars.path is defined 22 | -------------------------------------------------------------------------------- /roles/backup/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: Ansible 4 | description: EDA role for EDA Operator for Kubernetes. 5 | company: Red Hat, Inc. 6 | 7 | license: MIT 8 | 9 | min_ansible_version: 2.8 10 | 11 | platforms: 12 | - name: EL 13 | versions: 14 | - all 15 | - name: Debian 16 | versions: 17 | - all 18 | 19 | galaxy_tags: 20 | - controller 21 | - eda 22 | - ansible 23 | - backup 24 | - automation 25 | 26 | dependencies: [] 27 | 28 | collections: 29 | - kubernetes.core 30 | - operator_sdk.util 31 | -------------------------------------------------------------------------------- /roles/redis/tasks/create_default_config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create default Redis configuration secret 3 | kubernetes.core.k8s: 4 | state: present 5 | apply: true 6 | wait: true 7 | definition: "{{ lookup('template', 'redis.secret.yaml.j2') | from_yaml }}" 8 | register: result 9 | no_log: "{{ no_log }}" 10 | 11 | - name: Set Redis configuration values for the default Redis configuration 12 | ansible.builtin.set_fact: 13 | redis_type: "managed" 14 | redis_config_secret: "{{ ansible_operator_meta.name }}-redis-configuration" 15 | when: result is succeeded 16 | -------------------------------------------------------------------------------- /roles/restore/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: Ansible 4 | description: EDA role for EDA Operator for Kubernetes. 5 | company: Red Hat, Inc. 6 | 7 | license: MIT 8 | 9 | min_ansible_version: 2.8 10 | 11 | platforms: 12 | - name: EL 13 | versions: 14 | - all 15 | - name: Debian 16 | versions: 17 | - all 18 | 19 | galaxy_tags: 20 | - controller 21 | - eda 22 | - ansible 23 | - restore 24 | - automation 25 | 26 | dependencies: [] 27 | 28 | collections: 29 | - kubernetes.core 30 | - operator_sdk.util 31 | -------------------------------------------------------------------------------- /roles/redis/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for EDA 3 | 4 | deployment_type: eda 5 | 6 | image_pull_policy: IfNotPresent 7 | image_pull_secrets: [] 8 | 9 | _redis_image: quay.io/sclorg/redis-6-c9s 10 | _redis_image_version: c9s 11 | 12 | redis_type: '' 13 | redis_config_secret: '' 14 | 15 | redis: {} 16 | _redis: 17 | replicas: 1 18 | resource_requirements: 19 | requests: 20 | cpu: 50m 21 | memory: 100Mi 22 | node_selector: {} 23 | tolerations: [] 24 | 25 | # Labels defined on the resource, which should be propagated to child resources 26 | additional_labels: [] 27 | -------------------------------------------------------------------------------- /roles/backup/tasks/finalizer.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Look up details for this backup object 3 | k8s_info: 4 | api_version: "{{ api_version }}" 5 | kind: "{{ kind }}" 6 | name: "{{ ansible_operator_meta.name }}" 7 | namespace: "{{ ansible_operator_meta.namespace }}" 8 | register: this_backup 9 | 10 | - block: 11 | - include_tasks: init.yml 12 | 13 | - include_tasks: delete_backup.yml 14 | 15 | - include_tasks: cleanup.yml 16 | vars: 17 | backup_dir: "{{ this_backup['resources'][0]['status']['backupDirectory'] | default() }}" 18 | when: 19 | - clean_backup_on_delete 20 | - backup_dir | length > 0 21 | -------------------------------------------------------------------------------- /molecule/default/destroy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Destroy 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | collections: 7 | - kubernetes.core 8 | 9 | tasks: 10 | - import_tasks: kustomize.yml 11 | vars: 12 | state: absent 13 | 14 | - name: Destroy Namespace 15 | k8s: 16 | api_version: v1 17 | kind: Namespace 18 | name: '{{ namespace }}' 19 | state: absent 20 | 21 | - name: Unset pull policy 22 | command: '{{ kustomize }} edit remove patch pull_policy/{{ operator_pull_policy }}.yaml' 23 | args: 24 | chdir: '{{ config_dir }}/testing' 25 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: eda-manager-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: eda-server-operator 9 | app.kubernetes.io/part-of: eda-server-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: eda-manager-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: Role 15 | name: eda-manager-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /roles/postgres/templates/postgres_upgrade_secret.yaml.j2: -------------------------------------------------------------------------------- 1 | # Postgres Secret. 2 | --- 3 | apiVersion: v1 4 | kind: Secret 5 | metadata: 6 | name: '{{ ansible_operator_meta.name }}-postgres-configuration' 7 | namespace: '{{ ansible_operator_meta.namespace }}' 8 | labels: 9 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 10 | stringData: 11 | password: '{{ eda_postgres_pass }}' 12 | username: '{{ eda_postgres_user }}' 13 | database: '{{ eda_postgres_database }}' 14 | port: '{{ eda_postgres_port }}' 15 | host: '{{ ansible_operator_meta.name }}-postgres-{{ supported_pg_version }}' 16 | type: 'managed' 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | .cache/ 9 | bin 10 | testbin/* 11 | hacking/ 12 | /bundle 13 | /bundle_tmp* 14 | /bundle.Dockerfile 15 | Dockerfile.cross 16 | 17 | # Development directory and tools 18 | hacking/* 19 | kustomization.yaml 20 | 21 | # Test binary, build with `go test -c` 22 | *.test 23 | 24 | # Output of the go coverage tool, specifically when used with LiteIDE 25 | *.out 26 | 27 | # Kubernetes Generated files - skip generated files, except for vendored files 28 | 29 | !vendor/**/zz_generated.* 30 | 31 | # editor and IDE paraphernalia 32 | .idea 33 | *.swp 34 | *.swo 35 | *~ 36 | .vscode/ 37 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: proxy-rolebinding 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: eda-server-operator 9 | app.kubernetes.io/part-of: eda-server-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: proxy-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: rolebinding 6 | app.kubernetes.io/instance: leader-election-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: eda-server-operator 9 | app.kubernetes.io/part-of: eda-server-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: leader-election-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: Role 15 | name: leader-election-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /roles/redis/templates/redis.secret.yaml.j2: -------------------------------------------------------------------------------- 1 | # Redis EDA Secret. 2 | --- 3 | apiVersion: v1 4 | kind: Secret 5 | metadata: 6 | labels: 7 | app.kubernetes.io/name: 'redis' 8 | app.kubernetes.io/instance: 'redis-{{ ansible_operator_meta.name }}' 9 | app.kubernetes.io/component: cache 10 | app.kubernetes.io/part-of: '{{ deployment_type }}' 11 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 12 | name: '{{ ansible_operator_meta.name }}-redis-configuration' 13 | namespace: '{{ ansible_operator_meta.namespace }}' 14 | stringData: 15 | password: '' 16 | username: '' 17 | port: '6379' 18 | host: {{ ansible_operator_meta.name }}-redis-svc 19 | type: 'managed' 20 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: proxy-role 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: eda-server-operator 9 | app.kubernetes.io/part-of: eda-server-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-role 12 | rules: 13 | - apiGroups: 14 | - authentication.k8s.io 15 | resources: 16 | - tokenreviews 17 | verbs: 18 | - create 19 | - apiGroups: 20 | - authorization.k8s.io 21 | resources: 22 | - subjectaccessreviews 23 | verbs: 24 | - create 25 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: service 7 | app.kubernetes.io/instance: controller-manager-metrics-service 8 | app.kubernetes.io/component: kube-rbac-proxy 9 | app.kubernetes.io/created-by: eda-server-operator 10 | app.kubernetes.io/part-of: eda-server-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: controller-manager-metrics-service 13 | namespace: system 14 | spec: 15 | ports: 16 | - name: https 17 | port: 8443 18 | protocol: TCP 19 | targetPort: https 20 | selector: 21 | control-plane: controller-manager 22 | -------------------------------------------------------------------------------- /molecule/kind/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: localhost 4 | connection: local 5 | gather_facts: no 6 | 7 | tasks: 8 | - name: Build operator image 9 | docker_image: 10 | build: 11 | path: '{{ project_dir }}' 12 | pull: no 13 | name: '{{ operator_image }}' 14 | tag: latest 15 | push: no 16 | source: build 17 | force_source: yes 18 | 19 | - name: Load image into kind cluster 20 | command: kind load docker-image --name osdk-test '{{ operator_image }}' 21 | register: result 22 | changed_when: '"not yet present" in result.stdout' 23 | 24 | - import_playbook: ../default/converge.yml 25 | -------------------------------------------------------------------------------- /roles/eda/templates/secrets/admin_password_secret.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: '{{ ansible_operator_meta.name }}-admin-password' 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | labels: 8 | app.kubernetes.io/name: '{{ ansible_operator_meta.name }}' 9 | app.kubernetes.io/part-of: '{{ ansible_operator_meta.name }}' 10 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 11 | app.kubernetes.io/component: '{{ deployment_type }}' 12 | app.kubernetes.io/operator-version: '{{ lookup("env", "OPERATOR_VERSION") }}' 13 | stringData: 14 | password: '{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}' 15 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: ansible.com 2 | layout: 3 | - ansible.sdk.operatorframework.io/v1 4 | plugins: 5 | manifests.sdk.operatorframework.io/v2: {} 6 | scorecard.sdk.operatorframework.io/v2: {} 7 | projectName: eda-server-operator 8 | resources: 9 | - api: 10 | crdVersion: v1 11 | namespaced: true 12 | domain: ansible.com 13 | group: eda 14 | kind: EDA 15 | version: v1alpha1 16 | - api: 17 | crdVersion: v1 18 | namespaced: true 19 | domain: ansible.com 20 | group: eda 21 | kind: EDABackup 22 | version: v1alpha1 23 | - api: 24 | crdVersion: v1 25 | namespaced: true 26 | domain: ansible.com 27 | group: eda 28 | kind: EDARestore 29 | version: v1alpha1 30 | version: "3" 31 | -------------------------------------------------------------------------------- /down.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # EDA Operator down.sh 3 | 4 | # -- Usage 5 | # NAMESPACE=eda ./down.sh 6 | 7 | # -- Variables 8 | NAMESPACE=${NAMESPACE:-eda} 9 | TAG=${TAG:-dev} 10 | QUAY_USER=${QUAY_USER:-developer} 11 | IMG=quay.io/$QUAY_USER/eda-server-operator:$TAG 12 | EDA_CR=${EDA_CR:-eda} 13 | 14 | 15 | # -- Delete Backups 16 | kubectl delete edabackup --all 17 | 18 | # -- Delete Restores 19 | kubectl delete edarestore --all 20 | 21 | # Delete old operator deployment 22 | kubectl delete deployment eda-server-operator-controller-manager 23 | 24 | # Deploy Operator 25 | make undeploy IMG=$IMG NAMESPACE=$NAMESPACE 26 | 27 | # Remove PVCs 28 | kubectl delete pvc postgres-15-$EDA_CR-postgres-15-0 29 | 30 | -------------------------------------------------------------------------------- /roles/eda/templates/secrets/db_fields_encryption.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: '{{ ansible_operator_meta.name }}-db-fields-encryption-secret' 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | labels: 8 | app.kubernetes.io/name: '{{ ansible_operator_meta.name }}' 9 | app.kubernetes.io/part-of: '{{ ansible_operator_meta.name }}' 10 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 11 | app.kubernetes.io/component: '{{ deployment_type }}' 12 | app.kubernetes.io/operator-version: '{{ lookup("env", "OPERATOR_VERSION") }}' 13 | stringData: 14 | secret_key: '{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}' 15 | -------------------------------------------------------------------------------- /docs/create-awx-token.md: -------------------------------------------------------------------------------- 1 | # Create an AWX Access Token 2 | 3 | To use EDA to launch automation jobs in AWX, you need create an access token in AWX. 4 | 5 | ### Create an AWX Access Token 6 | 7 | Create an OAuth2 token for your user in the AWX UI. 8 | 9 | 1. Navigate to the Users page in the AWX UI 10 | 2. Select the username you wish to create a token for 11 | 3. Click on tokens, then the green plus icon 12 | 4. Application can be left empty, input a description and select the read/write scope. 13 | 14 | > Alternatively, you can create one at the command-line using the `create_oauth2_token` manage command ([docs](https://docs.ansible.com/automation-controller/latest/html/administration/tower-manage.html#create-oauth2-token)) 15 | -------------------------------------------------------------------------------- /roles/eda/templates/eda-event-stream.service.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: "{{ event_stream_server_name }}" 6 | namespace: "{{ ansible_operator_meta.namespace }}" 7 | labels: 8 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 9 | app.kubernetes.io/component: '{{ deployment_type }}-event-stream' 10 | spec: 11 | ports: 12 | - port: {{ event_stream_nginx_port }} 13 | protocol: TCP 14 | targetPort: {{ event_stream_nginx_port }} 15 | selector: 16 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 17 | app.kubernetes.io/component: '{{ deployment_type }}-event-stream' 18 | -------------------------------------------------------------------------------- /config/rbac/eda_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view edas. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: eda-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: eda-server-operator 10 | app.kubernetes.io/part-of: eda-server-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: eda-viewer-role 13 | rules: 14 | - apiGroups: 15 | - eda.ansible.com 16 | resources: 17 | - edas 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - eda.ansible.com 24 | resources: 25 | - edas/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /dev/eda-cr/lightweight-eda.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: eda.ansible.com/v1alpha1 3 | kind: EDA 4 | metadata: 5 | name: eda 6 | spec: 7 | extra_settings: 8 | - setting: EDA_ALLOW_LOCAL_RESOURCE_MANAGEMENT 9 | value: true 10 | - setting: GIT_SSL_NO_VERIFY 11 | value: "true" 12 | 13 | api: 14 | replicas: 1 15 | resource_requirements: 16 | requests: {} 17 | ui: 18 | replicas: 1 19 | resource_requirements: 20 | requests: {} 21 | schedular: 22 | replicas: 1 23 | resource_requirements: 24 | requests: {} 25 | default_worker: 26 | replicas: 2 27 | resource_requirements: 28 | requests: {} 29 | activation_worker: 30 | replicas: 3 31 | resource_requirements: 32 | requests: {} 33 | -------------------------------------------------------------------------------- /roles/redis/tasks/check_external_config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check for an external Redis configuration 3 | kubernetes.core.k8s_info: 4 | kind: Secret 5 | namespace: '{{ ansible_operator_meta.namespace }}' 6 | name: '{{ redis.redis_secret }}' 7 | register: _redis_configuration 8 | when: 9 | - redis is defined 10 | - redis | length 11 | - redis.redis_secret is defined 12 | - redis.redis_secret | length 13 | no_log: "{{ no_log }}" 14 | 15 | - name: Set Redis configuration values for an external Redis configuration 16 | ansible.builtin.set_fact: 17 | redis_type: 'unmanaged' 18 | redis_config_secret: "{{ _redis_configuration['resources'][0]['metadata']['name'] }}" 19 | when: _redis_configuration['resources'][0] is defined 20 | -------------------------------------------------------------------------------- /molecule/default/kustomize.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Build kustomize testing overlay 3 | # load_restrictor must be set to none so we can load patch files from the default overlay 4 | command: '{{ kustomize }} build --load-restrictor LoadRestrictionsNone' 5 | args: 6 | chdir: '{{ config_dir }}/testing' 7 | register: resources 8 | changed_when: false 9 | 10 | - name: Set resources to {{ state }} 11 | k8s: 12 | definition: '{{ item }}' 13 | state: '{{ state }}' 14 | wait: no 15 | loop: '{{ resources.stdout | from_yaml_all | list }}' 16 | 17 | - name: Wait for resources to get to {{ state }} 18 | k8s: 19 | definition: '{{ item }}' 20 | state: '{{ state }}' 21 | wait: yes 22 | loop: '{{ resources.stdout | from_yaml_all | list }}' 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/operator-framework/ansible-operator:v1.36.1 2 | 3 | ARG DEFAULT_EDA_VERSION 4 | ARG DEFAULT_EDA_UI_VERSION 5 | ARG OPERATOR_VERSION 6 | ENV DEFAULT_EDA_VERSION=${DEFAULT_EDA_VERSION} 7 | ENV DEFAULT_EDA_UI_VERSION=${DEFAULT_EDA_UI_VERSION} 8 | 9 | ENV OPERATOR_VERSION=${OPERATOR_VERSION} 10 | 11 | COPY requirements.yml ${HOME}/requirements.yml 12 | RUN ansible-galaxy collection install -r ${HOME}/requirements.yml \ 13 | && chmod -R ug+rwx ${HOME}/.ansible 14 | 15 | COPY watches.yaml ${HOME}/watches.yaml 16 | COPY roles/ ${HOME}/roles/ 17 | COPY playbooks/ ${HOME}/playbooks/ 18 | 19 | ENTRYPOINT ["/tini", "--", "/usr/local/bin/ansible-operator", "run", \ 20 | "--watches-file=./watches.yaml", \ 21 | "--reconcile-period=0s" \ 22 | ] 23 | -------------------------------------------------------------------------------- /roles/common/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for EDA 3 | 4 | deployment_type: eda 5 | kind: 'EDA' 6 | api_version: '{{ deployment_type }}.ansible.com/v1alpha1' 7 | 8 | # Labels defined on the resource, which should be propagated to child resources 9 | additional_labels: [] 10 | 11 | # Add annotations to the service account. Specify as literal block. E.g.: 12 | # service_account_annotations: | 13 | # eks.amazonaws.com/role-arn: arn:aws:iam:::role/ 14 | service_account_annotations: '' 15 | 16 | # This should be true if using the operator loop. 17 | # Set to false to run a role locally for testing. 18 | set_self_labels: true 19 | 20 | # Used to determine some cluster specific logic 21 | is_k8s: false 22 | is_openshift: false 23 | 24 | -------------------------------------------------------------------------------- /roles/restore/tasks/import_vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Import eda_object variables 4 | block: 5 | - name: Get EDA object definition from pvc 6 | k8s_exec: 7 | namespace: "{{ backup_pvc_namespace }}" 8 | pod: "{{ ansible_operator_meta.name }}-db-management" 9 | command: >- 10 | bash -c "cat '{{ backup_dir }}/eda_object'" 11 | register: eda_object 12 | 13 | - name: Create temp file for spec dict 14 | tempfile: 15 | state: file 16 | register: tmp_spec 17 | 18 | - name: Write spec vars to temp file 19 | copy: 20 | content: "{{ eda_object.stdout }}" 21 | dest: "{{ tmp_spec.path }}" 22 | mode: '0644' 23 | 24 | - name: Include spec vars to save them as a dict 25 | include_vars: "{{ tmp_spec.path }}" 26 | -------------------------------------------------------------------------------- /roles/eda/tasks/load_route_tls_secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Retrieve Route TLS Secret 3 | k8s_info: 4 | kind: Secret 5 | namespace: '{{ ansible_operator_meta.namespace }}' 6 | name: '{{ route_tls_secret }}' 7 | register: route_tls 8 | no_log: "{{ no_log }}" 9 | 10 | - name: Load Route TLS Secret content 11 | set_fact: 12 | route_tls_key: '{{ route_tls["resources"][0]["data"]["tls.key"] | b64decode }}' 13 | route_tls_crt: '{{ route_tls["resources"][0]["data"]["tls.crt"] | b64decode }}' 14 | no_log: "{{ no_log }}" 15 | 16 | - name: Load Route TLS Secret content 17 | set_fact: 18 | route_ca_crt: '{{ route_tls["resources"][0]["data"]["ca.crt"] | b64decode }}' 19 | no_log: "{{ no_log }}" 20 | when: '"ca.crt" in route_tls["resources"][0]["data"]' 21 | ... 22 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /roles/postgres/tasks/scale_down_deployment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Get list of deployments matching label selector 4 | kubernetes.core.k8s_info: 5 | api_version: apps/v1 6 | kind: Deployment 7 | namespace: "{{ ansible_operator_meta.namespace }}" 8 | label_selectors: 9 | - "app.kubernetes.io/name={{ ansible_operator_meta.name }}" 10 | register: matching_deployments 11 | 12 | - name: Scale down matching deployments for migration 13 | kubernetes.core.k8s_scale: 14 | api_version: apps/v1 15 | kind: Deployment 16 | name: "{{ item.metadata.name }}" 17 | namespace: "{{ ansible_operator_meta.namespace }}" 18 | replicas: 0 19 | wait: yes 20 | loop: "{{ matching_deployments.resources }}" 21 | when: matching_deployments.resources | length > 0 22 | 23 | -------------------------------------------------------------------------------- /config/rbac/eda_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit edas. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: eda-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: eda-server-operator 10 | app.kubernetes.io/part-of: eda-server-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: eda-editor-role 13 | rules: 14 | - apiGroups: 15 | - eda.ansible.com 16 | resources: 17 | - edas 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - eda.ansible.com 28 | resources: 29 | - edas/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /roles/restore/templates/secrets.yml.j2: -------------------------------------------------------------------------------- 1 | {% for secret in secrets %} 2 | --- 3 | apiVersion: v1 4 | kind: Secret 5 | metadata: 6 | name: '{{ secrets[secret]['name'] }}' 7 | namespace: '{{ ansible_operator_meta.namespace }}' 8 | labels: 9 | app.kubernetes.io/name: '{{ ansible_operator_meta.name }}' 10 | app.kubernetes.io/part-of: '{{ ansible_operator_meta.name }}' 11 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 12 | app.kubernetes.io/component: '{{ deployment_type }}' 13 | app.kubernetes.io/operator-version: '{{ lookup("env", "OPERATOR_VERSION") }}' 14 | type: '{{ secrets[secret]['type'] }}' 15 | stringData: 16 | {% for key, value in secrets[secret]['data'].items() %} 17 | {{ key }}: |- 18 | {{ value | b64decode | indent(4) }} 19 | {% endfor %} 20 | 21 | {% endfor %} 22 | -------------------------------------------------------------------------------- /roles/postgres/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Configure PostgreSQL database and required resources 3 | 4 | - name: Combine default and custom vars for each component 5 | include_tasks: combine_defaults.yml 6 | 7 | - name: Determine and set postgres configuration secret and variables 8 | import_tasks: set_configuration_secret.yml 9 | 10 | - name: Set default postgres image to be used 11 | import_tasks: set_images.yml 12 | 13 | - name: Set variables to be used in Postgres templates 14 | import_tasks: set_variables.yml 15 | 16 | # Managed Database block 17 | - name: Check PostgreSQL version to determine if an upgrade is needed 18 | import_tasks: check_postgres_version.yml 19 | when: managed_database 20 | 21 | - name: Create managed Postgres StatefulSet if no external db is defined 22 | import_tasks: create_managed_postgres.yml 23 | when: managed_database 24 | -------------------------------------------------------------------------------- /roles/restore/tasks/deploy_eda.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Combine spec_overrides with spec 3 | set_fact: 4 | spec: "{{ spec | default({}) | combine(spec_overrides) }}" 5 | no_log: "{{ no_log }}" 6 | 7 | - name: Deploy EDA 8 | k8s: 9 | state: "{{ state | default('present') }}" 10 | namespace: "{{ ansible_operator_meta.namespace }}" 11 | apply: yes 12 | definition: "{{ lookup('template', 'eda_object.yml.j2') }}" 13 | wait: true 14 | wait_condition: 15 | type: "Running" 16 | status: "True" 17 | 18 | - name: Remove ownerReferences to prevent garbage collection of new EDA CRO 19 | k8s: 20 | definition: 21 | apiVersion: '{{ api_version }}' 22 | kind: EDA 23 | metadata: 24 | name: '{{ deployment_name }}' 25 | namespace: '{{ ansible_operator_meta.namespace }}' 26 | ownerReferences: null 27 | -------------------------------------------------------------------------------- /roles/backup/templates/backup_pvc.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: PersistentVolumeClaim 4 | metadata: 5 | name: {{ deployment_name }}-backup-claim 6 | namespace: "{{ backup_pvc_namespace }}" 7 | ownerReferences: null 8 | labels: 9 | app.kubernetes.io/name: '{{ ansible_operator_meta.name }}' 10 | app.kubernetes.io/part-of: '{{ ansible_operator_meta.name }}' 11 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 12 | app.kubernetes.io/component: '{{ deployment_type }}' 13 | app.kubernetes.io/operator-version: '{{ lookup("env", "OPERATOR_VERSION") }}' 14 | spec: 15 | accessModes: 16 | - ReadWriteOnce 17 | {% if backup_storage_class is defined %} 18 | storageClassName: {{ backup_storage_class }} 19 | {% endif %} 20 | resources: 21 | requests: 22 | storage: {{ backup_storage_requirements | default('5Gi', true) }} 23 | -------------------------------------------------------------------------------- /roles/postgres/tasks/set_images.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Set postgres image in the following precedence order: 3 | # 1. User defined postgres_image on CR spec 4 | # 2. RELATED_IMAGE_XXX env var if set 5 | # 3. Default _postgres_image variable 6 | 7 | - name: Set default postgres image 8 | set_fact: 9 | _default_postgres_image: "{{ _postgres_image }}:{{_postgres_image_version }}" 10 | 11 | - name: Set user provided postgres image 12 | set_fact: 13 | _custom_postgres_image: "{{ postgres_image }}:{{ postgres_image_version }}" 14 | when: 15 | - postgres_image | default([]) | length 16 | - postgres_image_version is defined and postgres_image_version != '' 17 | 18 | - name: Set Postgres image URL 19 | set_fact: 20 | _postgres_image: "{{ _custom_postgres_image | default(lookup('env', 'RELATED_IMAGE_EDA_POSTGRES')) | default(_default_postgres_image, true) }}" 21 | -------------------------------------------------------------------------------- /roles/redis/tasks/check_default_config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check for the default Redis configuration secret 3 | kubernetes.core.k8s_info: 4 | kind: Secret 5 | namespace: '{{ ansible_operator_meta.namespace }}' 6 | name: '{{ ansible_operator_meta.name }}-redis-configuration' 7 | register: _redis_configuration 8 | no_log: "{{ no_log }}" 9 | 10 | # The default configuration should generally be a 'managed' configuration but the cost 11 | # of dynamically setting the variable here isn't high and will help avoid odd issues. 12 | - name: Set Redis configuration for the default Redis configuration 13 | ansible.builtin.set_fact: 14 | redis_type: "{{ _redis_configuration['resources'][0]['data']['type'] | default('') | b64decode }}" 15 | redis_config_secret: "{{ _redis_configuration['resources'][0]['metadata']['name'] }}" 16 | when: _redis_configuration['resources'][0] is defined 17 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/settings.md: -------------------------------------------------------------------------------- 1 | # EDA Application Settings 2 | 3 | 4 | Here is an example of how to configure settings for the EDA application via the EDA custom resource. This will override the default application settings if set. 5 | 6 | ```yaml 7 | --- 8 | apiVersion: eda.ansible.com/v1alpha1 9 | kind: EDA 10 | metadata: 11 | name: eda 12 | spec: 13 | ... 14 | extra_settings: 15 | - setting: EDA_APP_LOG_LEVEL 16 | value: "DEBUG" 17 | 18 | ``` 19 | 20 | 21 | 22 | ## Commonly Customized Settings 23 | 24 | Below is a table of the setting name, default value, and a description of it's purpose: 25 | 26 | | Setting Name | Default Value | Description | 27 | |--------------------------|----------------|-------------------------------------------------| 28 | | EDA_APP_LOG_LEVEL | "INFO" | Log level | 29 | -------------------------------------------------------------------------------- /roles/postgres/templates/postgres.configmap.yaml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: '{{ ansible_operator_meta.name }}-postgres-extra-settings' 5 | namespace: '{{ ansible_operator_meta.namespace }}' 6 | labels: 7 | app.kubernetes.io/name: 'postgres-extra-settings-{{ supported_pg_version }}' 8 | app.kubernetes.io/instance: 'postgres-{{ supported_pg_version }}-{{ ansible_operator_meta.name }}' 9 | app.kubernetes.io/component: 'database' 10 | app.kubernetes.io/part-of: '{{ ansible_operator_meta.name }}' 11 | app.kubernetes.io/managed-by: 'eda-operator' 12 | data: 13 | 99-overrides.conf: | 14 | {% for pg_setting in combined_database.postgres_extra_settings %} 15 | {% if pg_setting.value is string %} 16 | {{ pg_setting.setting }} = '{{ pg_setting.value }}' 17 | {% else %} 18 | {{ pg_setting.setting }} = {{ pg_setting.value }} 19 | {% endif %} 20 | {% endfor %} 21 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | app.kubernetes.io/name: servicemonitor 9 | app.kubernetes.io/instance: controller-manager-metrics-monitor 10 | app.kubernetes.io/component: metrics 11 | app.kubernetes.io/created-by: eda-server-operator 12 | app.kubernetes.io/part-of: eda-server-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: controller-manager-metrics-monitor 15 | namespace: system 16 | spec: 17 | endpoints: 18 | - path: /metrics 19 | port: https 20 | scheme: https 21 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 22 | tlsConfig: 23 | insecureSkipVerify: true 24 | selector: 25 | matchLabels: 26 | control-plane: controller-manager 27 | -------------------------------------------------------------------------------- /roles/postgres/templates/postgres.service.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: "{{ ansible_operator_meta.name }}-postgres-{{ supported_pg_version }}" 6 | namespace: "{{ ansible_operator_meta.namespace }}" 7 | labels: 8 | app.kubernetes.io/name: 'postgres-{{ supported_pg_version }}' 9 | app.kubernetes.io/instance: 'postgres-{{ supported_pg_version }}-{{ ansible_operator_meta.name }}' 10 | app.kubernetes.io/component: 'database' 11 | app.kubernetes.io/managed-by: 'eda-operator' 12 | spec: 13 | ports: 14 | - name: "5432" 15 | port: 5432 16 | targetPort: 5432 17 | clusterIP: None 18 | selector: 19 | app.kubernetes.io/name: 'postgres-{{ supported_pg_version }}' 20 | app.kubernetes.io/instance: 'postgres-{{ supported_pg_version }}-{{ ansible_operator_meta.name }}' 21 | app.kubernetes.io/component: 'database' 22 | app.kubernetes.io/managed-by: 'eda-operator' 23 | -------------------------------------------------------------------------------- /roles/restore/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Required: specify name of eda deployment to restore to 3 | deployment_name: '' 4 | kind: 'EDARestore' 5 | api_version: '{{ deployment_type }}.ansible.com/v1alpha1' 6 | 7 | # Required: specify a pre-created PVC (name) to restore from 8 | backup_pvc: '' 9 | backup_pvc_namespace: '{{ ansible_operator_meta.namespace }}' 10 | 11 | # Required: backup name, found on the edabackup object 12 | backup_dir: '' 13 | 14 | # Default cluster name 15 | cluster_name: 'cluster.local' 16 | 17 | # Set no_log settings on certain tasks 18 | no_log: true 19 | 20 | # Default resource requirements 21 | restore_resource_requirements: 22 | limits: 23 | cpu: "1000m" 24 | memory: "4096Mi" 25 | requests: 26 | cpu: "25m" 27 | memory: "32Mi" 28 | 29 | # Maintain some of the recommended `app.kubernetes.io/*` labels on the resource (self) 30 | set_self_labels: true 31 | 32 | spec_overrides: {} 33 | ... 34 | -------------------------------------------------------------------------------- /roles/redis/templates/redis.service.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: "{{ ansible_operator_meta.name }}-redis-svc" 6 | namespace: "{{ ansible_operator_meta.namespace }}" 7 | labels: 8 | app.kubernetes.io/name: 'redis' 9 | app.kubernetes.io/instance: 'redis-{{ ansible_operator_meta.name }}' 10 | app.kubernetes.io/component: cache 11 | app.kubernetes.io/part-of: '{{ deployment_type }}' 12 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 13 | spec: 14 | selector: 15 | app.kubernetes.io/name: 'redis' 16 | app.kubernetes.io/instance: 'redis-{{ ansible_operator_meta.name }}' 17 | app.kubernetes.io/component: cache 18 | app.kubernetes.io/part-of: '{{ deployment_type }}' 19 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 20 | ports: 21 | - protocol: TCP 22 | targetPort: 6379 23 | name: redis-6379 24 | port: 6379 25 | -------------------------------------------------------------------------------- /molecule/default/prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Prepare 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | 7 | tasks: 8 | - name: Ensure operator image is set 9 | fail: 10 | msg: | 11 | You must specify the OPERATOR_IMAGE environment variable in order to run the 12 | 'default' scenario 13 | when: not operator_image 14 | 15 | - name: Set testing image 16 | command: '{{ kustomize }} edit set image testing={{ operator_image }}' 17 | args: 18 | chdir: '{{ config_dir }}/testing' 19 | 20 | - name: Set pull policy 21 | command: '{{ kustomize }} edit add patch --path pull_policy/{{ operator_pull_policy }}.yaml' 22 | args: 23 | chdir: '{{ config_dir }}/testing' 24 | 25 | - name: Set testing namespace 26 | command: '{{ kustomize }} edit set namespace {{ namespace }}' 27 | args: 28 | chdir: '{{ config_dir }}/testing' 29 | -------------------------------------------------------------------------------- /playbooks/eda.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | gather_facts: no 4 | collections: 5 | - kubernetes.core 6 | - operator_sdk.util 7 | vars: 8 | no_log: true 9 | pre_tasks: 10 | - name: Verify imagePullSecrets 11 | k8s_info: 12 | kind: Secret 13 | namespace: '{{ ansible_operator_meta.namespace }}' 14 | name: redhat-operators-pull-secret 15 | register: _rh_ops_secret 16 | no_log: "{{ no_log }}" 17 | - name: Create imagePullSecret 18 | k8s: 19 | state: present 20 | definition: 21 | apiVersion: v1 22 | kind: Secret 23 | metadata: 24 | name: redhat-operators-pull-secret 25 | namespace: '{{ ansible_operator_meta.namespace }}' 26 | stringData: 27 | operator: eda 28 | when: 29 | - (_rh_ops_secret is not defined) or not (_rh_ops_secret['resources'] | length) 30 | roles: 31 | - eda 32 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: role 7 | app.kubernetes.io/instance: leader-election-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: eda-server-operator 10 | app.kubernetes.io/part-of: eda-server-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: leader-election-role 13 | rules: 14 | - apiGroups: 15 | - "" 16 | resources: 17 | - configmaps 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - create 23 | - update 24 | - patch 25 | - delete 26 | - apiGroups: 27 | - coordination.k8s.io 28 | resources: 29 | - leases 30 | verbs: 31 | - get 32 | - list 33 | - watch 34 | - create 35 | - update 36 | - patch 37 | - delete 38 | - apiGroups: 39 | - "" 40 | resources: 41 | - events 42 | verbs: 43 | - create 44 | - patch 45 | -------------------------------------------------------------------------------- /roles/backup/tasks/dump_secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Get Secret Name 4 | set_fact: 5 | _name: "{{ eda_spec.spec[item] | default('') }}" 6 | 7 | - name: Backup secret if defined 8 | block: 9 | - name: Get secret 10 | k8s_info: 11 | version: v1 12 | kind: Secret 13 | namespace: '{{ ansible_operator_meta.namespace }}' 14 | name: "{{ _name }}" 15 | register: _secret 16 | no_log: "{{ no_log }}" 17 | 18 | - name: Set secret key 19 | set_fact: 20 | _data: "{{ _secret['resources'][0]['data'] }}" 21 | _type: "{{ _secret['resources'][0]['type'] }}" 22 | no_log: "{{ no_log }}" 23 | 24 | - name: Create and Add secret names and data to dictionary 25 | set_fact: 26 | secret_dict: "{{ secret_dict | default({}) | combine({item: { 'name': _name, 'data': _data, 'type': _type }}) }}" 27 | no_log: "{{ no_log }}" 28 | when: _name != '' 29 | -------------------------------------------------------------------------------- /roles/postgres/tasks/set_variables.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Set variables now that pg_config secret is determined 3 | - name: Store Database Configuration vars for StatefulSet template 4 | set_fact: 5 | eda_postgres_user: "{{ pg_config['resources'][0]['data']['username'] | b64decode }}" 6 | eda_postgres_pass: "{{ pg_config['resources'][0]['data']['password'] | b64decode }}" 7 | eda_postgres_database: "{{ pg_config['resources'][0]['data']['database'] | b64decode }}" 8 | eda_postgres_port: "{{ pg_config['resources'][0]['data']['port'] | b64decode }}" 9 | eda_postgres_host: "{{ pg_config['resources'][0]['data']['host'] | b64decode }}" 10 | eda_postgres_sslmode: "{{ pg_config['resources'][0]['data']['sslmode'] | default('prefer'|b64encode) | b64decode }}" 11 | no_log: "{{ no_log }}" 12 | 13 | - name: Determine if database is managed based on secret values 14 | set_fact: 15 | managed_database: "{{ pg_config['resources'][0]['data']['type'] | default('') | b64decode == 'managed' }}" 16 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: delegated 6 | lint: | 7 | set -e 8 | yamllint -d "{extends: relaxed, rules: {line-length: {max: 120}}}" . 9 | platforms: 10 | - name: cluster 11 | groups: 12 | - k8s 13 | provisioner: 14 | name: ansible 15 | lint: | 16 | set -e 17 | ansible-lint 18 | inventory: 19 | group_vars: 20 | all: 21 | namespace: ${TEST_OPERATOR_NAMESPACE:-osdk-test} 22 | host_vars: 23 | localhost: 24 | ansible_python_interpreter: '{{ ansible_playbook_python }}' 25 | config_dir: ${MOLECULE_PROJECT_DIRECTORY}/config 26 | samples_dir: ${MOLECULE_PROJECT_DIRECTORY}/config/samples 27 | operator_image: ${OPERATOR_IMAGE:-""} 28 | operator_pull_policy: ${OPERATOR_PULL_POLICY:-"Always"} 29 | kustomize: ${KUSTOMIZE_PATH:-kustomize} 30 | env: 31 | K8S_AUTH_KUBECONFIG: ${KUBECONFIG:-"~/.kube/config"} 32 | verifier: 33 | name: ansible 34 | lint: | 35 | set -e 36 | ansible-lint 37 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: eda-server-operator-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: eda-server-operator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #labels: 13 | #- includeSelectors: true 14 | # pairs: 15 | # someName: someValue 16 | 17 | resources: 18 | - ../crd 19 | - ../rbac 20 | - ../manager 21 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 22 | #- ../prometheus 23 | 24 | # Protect the /metrics endpoint by putting it behind auth. 25 | # If you want your controller-manager to expose the /metrics 26 | # endpoint w/o any authn/z, please comment the following line. 27 | apiVersion: kustomize.config.k8s.io/v1beta1 28 | kind: Kustomization 29 | patches: 30 | - path: manager_auth_proxy_patch.yaml 31 | -------------------------------------------------------------------------------- /roles/redis/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check for an external Redis cache 3 | ansible.builtin.include_tasks: check_external_config.yml 4 | when: redis_type | length == 0 or redis_type == 'unmanaged' 5 | 6 | - name: Check for the default Redis configuration 7 | ansible.builtin.include_tasks: check_default_config.yml 8 | when: redis_type | length == 0 or redis_type == 'managed' 9 | 10 | # This task should only be activated when we don't have an external Redis 11 | # configuration AND we have never configured a managed Redis deployment. 12 | - name: Create a default Redis configuration 13 | ansible.builtin.include_tasks: create_default_config.yml 14 | when: redis_type | length == 0 15 | 16 | # This tasks combines builds the values for creating a managed Redis 17 | # system. It is important to keep these tasks together to keep 18 | # the reconciliation loop tight if we are using an external Redis cache. 19 | - name: Create a managed Redis deployment 20 | ansible.builtin.include_tasks: create_managed_redis.yml 21 | when: redis_type == 'managed' 22 | -------------------------------------------------------------------------------- /roles/eda/tasks/cleanup_migration_references.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This task file removes the old_postgres_configuration_secret reference from the EDA CR 3 | # after a successful migration to avoid keeping unnecessary references 4 | 5 | - name: Get current EDA CR 6 | kubernetes.core.k8s_info: 7 | api_version: "{{ api_version }}" 8 | kind: "{{ kind }}" 9 | name: "{{ ansible_operator_meta.name }}" 10 | namespace: "{{ ansible_operator_meta.namespace }}" 11 | register: eda_cr 12 | 13 | - name: Remove old_postgres_configuration_secret from EDA CR 14 | kubernetes.core.k8s: 15 | state: present 16 | definition: 17 | apiVersion: "{{ api_version }}" 18 | kind: "{{ kind }}" 19 | metadata: 20 | name: "{{ ansible_operator_meta.name }}" 21 | namespace: "{{ ansible_operator_meta.namespace }}" 22 | spec: "{{ eda_cr.resources[0].spec | combine({'old_postgres_configuration_secret': null}) }}" 23 | when: 24 | - eda_cr.resources | length > 0 25 | - eda_cr.resources[0].spec.old_postgres_configuration_secret is defined 26 | -------------------------------------------------------------------------------- /roles/eda/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for EDA 3 | 4 | api_server_name: "{{ ansible_operator_meta.name }}-api" 5 | api_django_port: 8002 # port for Django app for nginx to proxy requests to 6 | api_nginx_port: 8000 # nginx port for api endpoints and static files 7 | websocket_server_name: "{{ ansible_operator_meta.name }}-daphne" 8 | websocket_port: 8001 9 | media_dir: /var/lib/eda/files 10 | static_path: /var/lib/eda/static 11 | bundle_ca_crt: '' 12 | 13 | event_stream_nginx_port: 8000 14 | event_stream_server_name: "{{ ansible_operator_meta.name }}-event-stream" 15 | event_stream_django_port: 8002 16 | 17 | # timeout defaults for nginx and gunicorn_workers 18 | client_request_timeout: 30 19 | gunicorn_timeout: '{{ (([(client_request_timeout | int), 10] | max) / 3) | int }}' 20 | gunicorn_timeout_grace_period: 2 21 | eda_nginx_read_timeout: '{{ (([(client_request_timeout | int), 10] | max) / 2) | int }}' 22 | 23 | # Define which address to bind in the api, depending on the enabled IP version 24 | api_bind_address: "{{ (ipv6_disabled | bool) | ternary('0.0.0.0', '[::]') }}" 25 | -------------------------------------------------------------------------------- /roles/backup/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Required: specify name of eda deployment to backup from 3 | deployment_name: '' 4 | kind: 'EDABackup' 5 | api_version: '{{ deployment_type }}.ansible.com/v1alpha1' 6 | 7 | # Specify a pre-created PVC (name) to backup to 8 | backup_pvc: '' 9 | backup_pvc_namespace: "{{ ansible_operator_meta.namespace }}" 10 | 11 | # Size of backup PVC if created dynamically 12 | backup_storage_requirements: '' 13 | 14 | # Set no_log settings on certain tasks 15 | no_log: true 16 | 17 | # Variable to set when you want backups to be cleaned up when the CRD object is deleted 18 | clean_backup_on_delete: false 19 | 20 | # Variable to signal that this role is being run as a finalizer 21 | finalizer_run: false 22 | 23 | # Default resource requirements 24 | backup_resource_requirements: 25 | limits: 26 | cpu: "1000m" 27 | memory: "4096Mi" 28 | requests: 29 | cpu: "25m" 30 | memory: "32Mi" 31 | # Allow additional parameters to be added to the pg_dump backup command 32 | pg_dump_suffix: '' 33 | 34 | # Maintain some of the recommended `app.kubernetes.io/*` labels on the resource (self) 35 | set_self_labels: true 36 | ... 37 | -------------------------------------------------------------------------------- /dev/eda-cr/eda-db-configuration.yml: -------------------------------------------------------------------------------- 1 | apiVersion: eda.ansible.com/v1alpha1 2 | kind: EDA 3 | metadata: 4 | name: eda 5 | spec: 6 | automation_server_url: https://awx-awx.apps.aap-dev.ocp4.testing.ansible.com 7 | automation_server_ssl_verify: 'no' 8 | service_type: ClusterIP 9 | ingress_type: Route 10 | no_log: false 11 | image_pull_policy: Always 12 | image_pull_secrets: 13 | - redhat-operators-pull-secret 14 | 15 | # Disable UI 16 | ui_disabled: false 17 | 18 | # -- Example extra settings 19 | extra_settings: 20 | - setting: DEFAULT_PULL_POLICY 21 | value: "Always" 22 | 23 | database: 24 | # postgres_configuration_secret: eda-pod-external-database # requires creation of this secret first 25 | postgres_extra_settings: 26 | - setting: max_connections 27 | value: '1000' 28 | resource_requirements: 29 | requests: 30 | cpu: 50m 31 | memory: 128Mi 32 | limits: 33 | cpu: 100m 34 | memory: 256Mi 35 | storage_requirements: 36 | requests: 37 | storage: 8Gi 38 | # postgres_storage_class: fast-ssd # requires storage class with this name on the cluster to work 39 | 40 | -------------------------------------------------------------------------------- /roles/redis/tasks/create_managed_redis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set default redis image 3 | ansible.builtin.set_fact: 4 | _default_redis_image: "{{ _redis_image }}:{{ _redis_image_version }}" 5 | 6 | - name: Set user provided redis image 7 | ansible.builtin.set_fact: 8 | _custom_redis_image: "{{ redis_image }}:{{ redis_image_version }}" 9 | when: 10 | - redis_image | default([]) | length 11 | - redis_image_version is defined or redis_image_version != '' 12 | 13 | - name: Set Redis image URL 14 | ansible.builtin.set_fact: 15 | _redis_image: "{{ _custom_redis_image | default(lookup('env', 'RELATED_IMAGE_EDA_REDIS')) | default(_default_redis_image, true) }}" 16 | 17 | - name: Combine the default settings with custom values from the resource 18 | ansible.builtin.set_fact: 19 | combined_redis: "{{ _redis | combine(redis, recursive=True) }}" 20 | 21 | - name: Redis Deployment & Service 22 | kubernetes.core.k8s: 23 | state: present 24 | apply: true 25 | wait: false 26 | definition: "{{ lookup('template', 'templates/' + item + '.yaml.j2') | from_yaml }}" 27 | loop: 28 | - 'redis.service' 29 | - 'redis.deployment' 30 | no_log: "{{ no_log }}" 31 | -------------------------------------------------------------------------------- /roles/eda/tasks/set_images.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # API Image 4 | - name: Set default eda api container image 5 | set_fact: 6 | _default_image: "{{ _image }}:{{ _image_version }}" 7 | 8 | - name: Set user provided image 9 | set_fact: 10 | _custom_image: "{{ image }}:{{ image_version }}" 11 | when: 12 | - image | default([]) | length 13 | - image_version is defined or image_version != '' 14 | 15 | - name: Set image URL 16 | set_fact: 17 | _image: >- 18 | {{ _custom_image | 19 | default(lookup('env', 'RELATED_IMAGE_EDA')) | 20 | default(_default_image, true) }} 21 | 22 | # UI Image 23 | - name: Set default eda ui container image 24 | set_fact: 25 | _default_image_web: "{{ _image_web }}:{{ _image_web_version }}" 26 | 27 | - name: Set user provided image 28 | set_fact: 29 | _custom_image_web: "{{ image_web }}:{{ image_web_version }}" 30 | when: 31 | - image_web | default([]) | length 32 | - image_web_version is defined or image_web_version != '' 33 | 34 | - name: Set image URL 35 | set_fact: 36 | _image_web: >- 37 | {{ _custom_image_web | 38 | default(lookup('env', 'RELATED_IMAGE_EDA_UI')) | 39 | default(_default_image_web, true) }} 40 | -------------------------------------------------------------------------------- /roles/backup/tasks/dump_generated_secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Get secret name 4 | set_fact: 5 | _name: "{{ this_eda['resources'][0]['status'][item] }}" 6 | 7 | - name: Fail if status is not set on EDA CR 8 | block: 9 | - name: Set error message 10 | set_fact: 11 | error_msg: "{{ item }} status is not set on EDA object yet" 12 | 13 | - name: Handle error 14 | import_tasks: error_handling.yml 15 | 16 | - name: Fail early if secret name status is not set 17 | fail: 18 | msg: "{{ error_msg }}" 19 | when: _name is not defined or _name == '' 20 | 21 | - name: Get secret 22 | k8s_info: 23 | version: v1 24 | kind: Secret 25 | namespace: '{{ ansible_operator_meta.namespace }}' 26 | name: "{{ _name }}" 27 | register: _secret 28 | no_log: "{{ no_log }}" 29 | 30 | - name: Set secret data 31 | set_fact: 32 | _data: "{{ _secret['resources'][0]['data'] }}" 33 | _type: "{{ _secret['resources'][0]['type'] }}" 34 | no_log: "{{ no_log }}" 35 | 36 | - name: Create and Add secret names and data to dictionary 37 | set_fact: 38 | secret_dict: "{{ secret_dict | default({}) | combine({ item: {'name': _name, 'data': _data, 'type': _type }}) }}" 39 | no_log: "{{ no_log }}" 40 | -------------------------------------------------------------------------------- /roles/backup/templates/management-pod.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: {{ ansible_operator_meta.name }}-db-management 6 | namespace: "{{ backup_pvc_namespace }}" 7 | labels: 8 | app.kubernetes.io/name: '{{ ansible_operator_meta.name }}' 9 | app.kubernetes.io/part-of: '{{ ansible_operator_meta.name }}' 10 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 11 | app.kubernetes.io/component: '{{ deployment_type }}' 12 | app.kubernetes.io/operator-version: '{{ lookup("env", "OPERATOR_VERSION") }}' 13 | spec: 14 | containers: 15 | - name: {{ ansible_operator_meta.name }}-db-management 16 | image: "{{ _postgres_image }}" 17 | imagePullPolicy: Always 18 | command: ["sleep", "infinity"] 19 | volumeMounts: 20 | - name: {{ ansible_operator_meta.name }}-backup 21 | mountPath: /backups 22 | readOnly: false 23 | {% if backup_resource_requirements is defined %} 24 | resources: 25 | {{ backup_resource_requirements | to_nice_yaml(indent=2) | indent(width=6, first=False) }} 26 | {%- endif %} 27 | volumes: 28 | - name: {{ ansible_operator_meta.name }}-backup 29 | persistentVolumeClaim: 30 | claimName: {{ backup_claim }} 31 | readOnly: false 32 | restartPolicy: Never 33 | -------------------------------------------------------------------------------- /roles/restore/templates/management-pod.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: {{ ansible_operator_meta.name }}-db-management 6 | namespace: "{{ backup_pvc_namespace }}" 7 | labels: 8 | app.kubernetes.io/name: '{{ ansible_operator_meta.name }}' 9 | app.kubernetes.io/part-of: '{{ ansible_operator_meta.name }}' 10 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 11 | app.kubernetes.io/component: '{{ deployment_type }}' 12 | app.kubernetes.io/operator-version: '{{ lookup("env", "OPERATOR_VERSION") }}' 13 | spec: 14 | containers: 15 | - name: {{ ansible_operator_meta.name }}-db-management 16 | image: "{{ _postgres_image }}" 17 | imagePullPolicy: Always 18 | command: ["sleep", "infinity"] 19 | volumeMounts: 20 | - name: {{ ansible_operator_meta.name }}-backup 21 | mountPath: /backups 22 | readOnly: false 23 | {% if restore_resource_requirements is defined %} 24 | resources: 25 | {{ restore_resource_requirements | to_nice_yaml(indent=2) | indent(width=6, first=False) }} 26 | {%- endif %} 27 | volumes: 28 | - name: {{ ansible_operator_meta.name }}-backup 29 | persistentVolumeClaim: 30 | claimName: {{ backup_pvc }} 31 | readOnly: false 32 | restartPolicy: Never 33 | -------------------------------------------------------------------------------- /molecule/kind/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: delegated 6 | lint: | 7 | set -e 8 | yamllint -d "{extends: relaxed, rules: {line-length: {max: 120}}}" . 9 | platforms: 10 | - name: cluster 11 | groups: 12 | - k8s 13 | provisioner: 14 | name: ansible 15 | playbooks: 16 | prepare: ../default/prepare.yml 17 | verify: ../default/verify.yml 18 | lint: | 19 | set -e 20 | ansible-lint 21 | inventory: 22 | group_vars: 23 | all: 24 | namespace: ${TEST_OPERATOR_NAMESPACE:-osdk-test} 25 | host_vars: 26 | localhost: 27 | ansible_python_interpreter: '{{ ansible_playbook_python }}' 28 | config_dir: ${MOLECULE_PROJECT_DIRECTORY}/config 29 | samples_dir: ${MOLECULE_PROJECT_DIRECTORY}/config/samples 30 | project_dir: ${MOLECULE_PROJECT_DIRECTORY} 31 | operator_image: testing-operator 32 | operator_pull_policy: "Never" 33 | kubeconfig: "{{ lookup('env', 'KUBECONFIG') }}" 34 | kustomize: ${KUSTOMIZE_PATH:-kustomize} 35 | env: 36 | K8S_AUTH_KUBECONFIG: ${MOLECULE_EPHEMERAL_DIRECTORY}/kubeconfig 37 | KUBECONFIG: ${MOLECULE_EPHEMERAL_DIRECTORY}/kubeconfig 38 | verifier: 39 | name: ansible 40 | lint: | 41 | set -e 42 | ansible-lint 43 | -------------------------------------------------------------------------------- /.github/workflows/build-operator-image.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Build Operator Image 4 | 5 | on: 6 | push: 7 | branches: [main] 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | name: Push main image 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Log into registry ghcr.io 17 | uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 18 | with: 19 | registry: ghcr.io 20 | username: ${{ github.actor }} 21 | password: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | - name: Log into registry quay.io 24 | uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 25 | with: 26 | registry: quay.io/ansible/ 27 | username: ${{ secrets.QUAY_USER }} 28 | password: ${{ secrets.QUAY_TOKEN }} 29 | 30 | - name: Build and Store Image @ghcr 31 | run: | 32 | IMG=ghcr.io/${{ github.repository_owner }}/eda-server-operator:${{ github.sha }} make docker-buildx 33 | 34 | - name: Publish Image to quay.io/ansible/eda-server-operator:main 35 | run: | 36 | docker buildx imagetools create ghcr.io/${{ github.repository_owner }}/eda-server-operator:${{ github.sha }} --tag quay.io/ansible/eda-server-operator:main 37 | -------------------------------------------------------------------------------- /roles/eda/templates/eda-api.service.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: "{{ api_server_name }}" 6 | namespace: "{{ ansible_operator_meta.namespace }}" 7 | labels: 8 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 9 | app.kubernetes.io/component: '{{ deployment_type }}-api' 10 | spec: 11 | ports: 12 | - port: {{ api_nginx_port }} 13 | protocol: TCP 14 | targetPort: {{ api_nginx_port }} 15 | selector: 16 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 17 | app.kubernetes.io/component: '{{ deployment_type }}-api' 18 | 19 | --- 20 | apiVersion: v1 21 | kind: Service 22 | metadata: 23 | name: "{{ websocket_server_name }}" 24 | namespace: "{{ ansible_operator_meta.namespace }}" 25 | labels: 26 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 27 | app.kubernetes.io/component: '{{ deployment_type }}-daphne' 28 | spec: 29 | ports: 30 | - port: {{ websocket_port }} 31 | protocol: TCP 32 | targetPort: {{ websocket_port }} 33 | selector: 34 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 35 | app.kubernetes.io/component: '{{ deployment_type }}-api' 36 | -------------------------------------------------------------------------------- /roles/common/tasks/patch_labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Patching labels to EDA resource 4 | k8s: 5 | state: present 6 | definition: 7 | apiVersion: '{{ api_version }}' 8 | kind: '{{ kind }}' 9 | name: '{{ ansible_operator_meta.name }}' 10 | namespace: '{{ ansible_operator_meta.namespace }}' 11 | metadata: 12 | name: '{{ ansible_operator_meta.name }}' 13 | namespace: '{{ ansible_operator_meta.namespace }}' 14 | labels: '{{ lookup("template", "../common/templates/labels/common.yaml.j2") | from_yaml }}' 15 | when: set_self_labels | bool 16 | 17 | - name: Build `additional_labels_items` labels from `additional_labels` 18 | block: 19 | - name: Look up details for this deployment 20 | k8s_info: 21 | api_version: "{{ api_version }}" 22 | kind: "{{ kind }}" 23 | name: "{{ ansible_operator_meta.name }}" 24 | namespace: "{{ ansible_operator_meta.namespace }}" 25 | register: this_eda 26 | 27 | - name: Select resource labels which are in `additional_labels` 28 | set_fact: 29 | additional_labels_items: >- 30 | {{ this_eda['resources'][0]['metadata']['labels'] 31 | | dict2items | selectattr('key', 'in', additional_labels) 32 | | list 33 | }} 34 | when: this_eda['resources'][0]['metadata']['labels'] 35 | when: additional_labels | length 36 | -------------------------------------------------------------------------------- /roles/eda/tasks/create_admin_user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check if there are any super users defined. 3 | kubernetes.core.k8s_exec: 4 | namespace: "{{ ansible_operator_meta.namespace }}" 5 | pod: "{{ eda_api_pod_name }}" 6 | container: "eda-api" 7 | command: >- 8 | bash -c "echo 'from django.contrib.auth import get_user_model; 9 | User = get_user_model(); 10 | nsu = User.objects.filter(is_superuser=True, username=\"{{ admin_user }}\").count(); 11 | exit(0 if nsu > 0 else 1)' 12 | | aap-eda-manage shell" 13 | ignore_errors: true 14 | register: users_result 15 | changed_when: users_result.return_code > 0 16 | 17 | - name: Create super user via Django if it doesn't exist. 18 | kubernetes.core.k8s_exec: 19 | namespace: "{{ ansible_operator_meta.namespace }}" 20 | pod: "{{ eda_api_pod_name }}" 21 | container: "eda-api" 22 | command: bash -c "DJANGO_SUPERUSER_PASSWORD={{ admin_password }} ANSIBLE_REVERSE_RESOURCE_SYNC=false aap-eda-manage createsuperuser --username={{ admin_user | quote }} --email={{ admin_email | quote }} --noinput" 23 | register: result 24 | changed_when: "'That username is already taken' not in result.stderr" 25 | failed_when: "'That username is already taken' not in result.stderr and 'Superuser created successfully' not in result.stdout" 26 | no_log: "{{ no_log }}" 27 | when: users_result.return_code > 0 28 | -------------------------------------------------------------------------------- /dev/eda-cr/eda-resource-quota-cr.yml: -------------------------------------------------------------------------------- 1 | apiVersion: eda.ansible.com/v1alpha1 2 | kind: EDA 3 | metadata: 4 | name: eda-prod 5 | spec: 6 | automation_server_url: https://awx-awx.apps.aap-dev.ocp4.testing.ansible.com 7 | automation_server_ssl_verify: 'no' 8 | service_type: ClusterIP 9 | ingress_type: Route 10 | no_log: false 11 | image_pull_policy: Always 12 | extra_settings: 13 | - setting: EDA_ALLOW_LOCAL_RESOURCE_MANAGEMENT 14 | value: true 15 | 16 | api: 17 | replicas: 1 18 | resource_requirements: 19 | requests: 20 | cpu: 50m 21 | memory: 350Mi 22 | ui: 23 | replicas: 1 24 | resource_requirements: 25 | requests: 26 | cpu: 25m 27 | memory: 64Mi 28 | scheduler: 29 | replicas: 1 30 | resource_requirements: 31 | requests: 32 | cpu: 50m 33 | memory: 256Mi 34 | database: 35 | resource_requirements: 36 | requests: 37 | cpu: 50m 38 | memory: 128Mi 39 | worker: 40 | replicas: 5 41 | resource_requirements: 42 | requests: 43 | cpu: 25m 44 | memory: 200Mi 45 | 46 | default_worker: 47 | replicas: 2 48 | resource_requirements: 49 | requests: 50 | cpu: 25m 51 | memory: 200Mi 52 | activation_worker: 53 | replicas: 5 54 | resource_requirements: 55 | requests: 56 | cpu: 25m 57 | memory: 150Mi 58 | -------------------------------------------------------------------------------- /roles/backup/tasks/eda-cro.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Get EDA custom resource object 4 | k8s_info: 5 | version: v1alpha1 6 | kind: EDA 7 | namespace: '{{ ansible_operator_meta.namespace }}' 8 | name: '{{ deployment_name }}' 9 | register: _eda_cro 10 | 11 | - name: Set EDA object 12 | set_fact: 13 | _eda: "{{ this_eda['resources'][0]['spec'] }}" 14 | _db_secret_value: | 15 | {"database_secret": "{{ this_eda['resources'][0]['status']['databaseConfigurationSecret'] }}"} 16 | 17 | - name: Set names of backed up secrets in the CR spec 18 | set_fact: 19 | _eda: "{{ _eda | combine ({ item.key : item.value }) }}" 20 | with_items: 21 | - {"key": "db_fields_encryption_secret", "value": "{{ this_eda['resources'][0]['status']['dbFieldsEncryptionSecret'] }}"} 22 | - {"key": "admin_password_secret", "value": "{{ this_eda['resources'][0]['status']['adminPasswordSecret'] }}"} 23 | - {"key": "database", "value": "{{ _db_secret_value }}" } 24 | 25 | - name: Set EDA object 26 | set_fact: 27 | eda_spec: 28 | spec: "{{ _eda }}" 29 | previous_deployment_name: "{{ this_eda['resources'][0]['metadata']['name'] }}" 30 | 31 | - name: Write eda object to pvc 32 | k8s_cp: 33 | namespace: "{{ backup_pvc_namespace }}" 34 | pod: "{{ ansible_operator_meta.name }}-db-management" 35 | remote_path: "{{ backup_dir }}/eda_object" 36 | content: "{{ eda_spec | to_yaml }}" 37 | -------------------------------------------------------------------------------- /roles/backup/tasks/secrets.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Dump (generated) secret names from statuses and data into file 4 | include_tasks: dump_generated_secret.yml 5 | with_items: 6 | - dbFieldsEncryptionSecret 7 | - adminPasswordSecret 8 | - databaseConfigurationSecret 9 | 10 | - name: Dump secret names from eda spec and data into file 11 | include_tasks: dump_secret.yml 12 | loop: 13 | - route_tls_secret 14 | - ingress_tls_secret 15 | - ldap_cacert_secret 16 | - bundle_cacert_secret 17 | - ee_pull_credentials_secret 18 | 19 | # image_pull_secret is deprecated in favor of image_pull_secrets 20 | - name: Dump image_pull_secret into file 21 | include_tasks: dump_secret.yml 22 | with_items: 23 | - image_pull_secret 24 | when: image_pull_secret is defined 25 | 26 | - name: Dump image_pull_secrets into file 27 | include_tasks: dump_secret.yml 28 | with_items: 29 | - image_pull_secrets 30 | when: image_pull_secrets | default([]) | length 31 | 32 | - name: Nest secrets under a single variable 33 | set_fact: 34 | secrets: {"secrets": '{{ secret_dict }}'} 35 | no_log: "{{ no_log }}" 36 | 37 | - name: Write postgres configuration to pvc 38 | k8s_cp: 39 | namespace: "{{ backup_pvc_namespace }}" 40 | pod: "{{ ansible_operator_meta.name }}-db-management" 41 | remote_path: "{{ backup_dir }}/secrets.yml" 42 | content: "{{ secrets | to_yaml }}" 43 | no_log: "{{ no_log }}" 44 | -------------------------------------------------------------------------------- /docs/user-guide/redis-configuration.md: -------------------------------------------------------------------------------- 1 | ### Redis Configuration 2 | 3 | #### Redis Version 4 | 5 | The default Redis version for the version of EDA bundled with the latest version of the eda-server-operator is Redis 6. 6 | 7 | #### External Redis Service 8 | 9 | EDA can be configured to use an external redis cache by creating a secret which holds the configuration values for the external Redis instance. 10 | 11 | The secret should be formatted as follows: 12 | 13 | ```yaml 14 | --- 15 | apiVersion: v1 16 | kind: Secret 17 | metadata: 18 | name: -redis-configuration 19 | namespace: 20 | stringData: 21 | host: 22 | port: 23 | redis_tls: 24 | database: 25 | cluster_endpoint: 26 | username: 27 | password: 28 | type: unmanaged 29 | type: Opaque 30 | ``` 31 | 32 | The secret should be specified on the EDA customer resource using the following: 33 | 34 | ```yaml 35 | --- 36 | spec: 37 | ... 38 | redis: 39 | redis_secret: 40 | ``` 41 | 42 | #### Redis Cluster 43 | 44 | The format of the cluster_endpoint field is: 45 | 46 | ":[,:]*" 47 | 48 | This field is required if the external redis service is a cluster. 49 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.27.0 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.27.0 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.27.0 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.27.0 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.27.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /roles/eda/tasks/combine_defaults.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Combine default variables for components 4 | set_fact: 5 | combined_api: "{{ _api | combine(api, recursive=True) }}" 6 | combined_ui: "{{ _ui | combine(ui, recursive=True) }}" 7 | combined_scheduler: "{{ _scheduler | combine(scheduler, recursive=True) }}" 8 | combined_event_stream: "{{ _event_stream | combine(event_stream, recursive=True) }}" 9 | 10 | # Backwards compatibility support for worker parameters 11 | - name: Set defaults for workers # (overridden by worker, default_worker, and activation_worker) 12 | set_fact: 13 | combined_default_worker: "{{ _default_worker }}" 14 | combined_activation_worker: "{{ _activation_worker }}" 15 | 16 | - name: Combine worker params 17 | set_fact: 18 | combined_default_worker: "{{ _worker | combine(worker, recursive=True) }}" 19 | combined_activation_worker: "{{ _worker | combine(worker, recursive=True) }}" 20 | when: worker is defined 21 | 22 | - name: Set default worker parameters when worker is not defined 23 | set_fact: 24 | combined_default_worker: "{{ _default_worker | combine(default_worker, recursive=True) }}" 25 | when: 26 | - default_worker is defined 27 | 28 | - name: Set activation worker parameters when worker is not defined 29 | set_fact: 30 | combined_activation_worker: "{{ _activation_worker | combine(activation_worker, recursive=True) }}" 31 | when: 32 | - activation_worker is defined 33 | -------------------------------------------------------------------------------- /roles/eda/README.md: -------------------------------------------------------------------------------- 1 | Role Name 2 | ========= 3 | 4 | A brief description of the role goes here. 5 | 6 | Requirements 7 | ------------ 8 | 9 | Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, 10 | if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. 11 | 12 | Role Variables 13 | -------------- 14 | 15 | A description of the settable variables for this role should go here, including any variables that are in 16 | defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables 17 | that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well 18 | 19 | Dependencies 20 | ------------ 21 | 22 | A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set 23 | for other roles, or variables that are used from other roles. 24 | 25 | Example Playbook 26 | ---------------- 27 | 28 | Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for 29 | users too: 30 | 31 | - hosts: servers 32 | roles: 33 | - { role: username.rolename, x: 42 } 34 | 35 | License 36 | ------- 37 | 38 | BSD 39 | 40 | Author Information 41 | ------------------ 42 | 43 | An optional section for the role authors to include contact information, or a website (HTML is not allowed). 44 | -------------------------------------------------------------------------------- /config/samples/eda_v1alpha1_eda.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: eda.ansible.com/v1alpha1 2 | kind: EDA 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: eda 6 | app.kubernetes.io/instance: eda-sample 7 | app.kubernetes.io/part-of: eda-server-operator 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: eda-server-operator 10 | name: eda-sample 11 | spec: 12 | no_log: true 13 | image_pull_policy: IfNotPresent 14 | service_type: ClusterIP 15 | ingress_type: Route 16 | api: 17 | replicas: 1 18 | resource_requirements: 19 | requests: 20 | cpu: 150m 21 | memory: 256Mi 22 | default_worker: 23 | replicas: 2 24 | resource_requirements: 25 | requests: 26 | cpu: 150m 27 | memory: 256Mi 28 | activation_worker: 29 | replicas: 2 30 | resource_requirements: 31 | requests: 32 | cpu: 150m 33 | memory: 256Mi 34 | scheduler: 35 | replicas: 2 36 | resource_requirements: 37 | requests: 38 | cpu: 150m 39 | memory: 256Mi 40 | ui: 41 | replicas: 1 42 | resource_requirements: 43 | requests: 44 | cpu: 100m 45 | memory: 256Mi 46 | redis: 47 | replicas: 1 48 | resource_requirements: 49 | requests: 50 | cpu: 150m 51 | memory: 256Mi 52 | database: 53 | resource_requirements: 54 | requests: 55 | cpu: 200m 56 | memory: 512Mi 57 | storage_requirements: 58 | requests: 59 | storage: 20Gi 60 | -------------------------------------------------------------------------------- /roles/backup/tasks/creation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Patching labels to {{ kind }} kind 3 | k8s: 4 | state: present 5 | definition: 6 | apiVersion: "{{ api_version }}" 7 | kind: "{{ kind }}" 8 | name: "{{ ansible_operator_meta.name }}" 9 | namespace: "{{ ansible_operator_meta.namespace }}" 10 | metadata: 11 | name: "{{ ansible_operator_meta.name }}" 12 | namespace: "{{ ansible_operator_meta.namespace }}" 13 | labels: 14 | app.kubernetes.io/name: "{{ ansible_operator_meta.name }}" 15 | app.kubernetes.io/part-of: "{{ ansible_operator_meta.name }}" 16 | app.kubernetes.io/managed-by: "{{ deployment_type }}-operator" 17 | app.kubernetes.io/component: "{{ deployment_type }}" 18 | app.kubernetes.io/operator-version: '{{ lookup("env", "OPERATOR_VERSION") }}' 19 | when: set_self_labels | bool 20 | 21 | - name: Look up details for this backup object 22 | k8s_info: 23 | api_version: "{{ api_version }}" 24 | kind: "{{ kind }}" 25 | name: "{{ ansible_operator_meta.name }}" 26 | namespace: "{{ ansible_operator_meta.namespace }}" 27 | register: this_backup 28 | 29 | - block: 30 | - include_tasks: init.yml 31 | 32 | - include_tasks: postgres.yml 33 | 34 | - include_tasks: eda-cro.yml 35 | 36 | - include_tasks: secrets.yml 37 | 38 | - name: Set flag signifying this backup was successful 39 | set_fact: 40 | backup_complete: true 41 | 42 | - include_tasks: cleanup.yml 43 | 44 | when: 45 | - this_backup['resources'][0]['status']['backupDirectory'] is not defined 46 | 47 | - name: Update status variables 48 | include_tasks: update_status.yml 49 | -------------------------------------------------------------------------------- /roles/restore/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Patching labels to {{ kind }} kind 3 | k8s: 4 | state: present 5 | definition: 6 | apiVersion: '{{ api_version }}' 7 | kind: '{{ kind }}' 8 | name: '{{ ansible_operator_meta.name }}' 9 | namespace: '{{ ansible_operator_meta.namespace }}' 10 | metadata: 11 | name: '{{ ansible_operator_meta.name }}' 12 | namespace: '{{ ansible_operator_meta.namespace }}' 13 | labels: 14 | app.kubernetes.io/name: '{{ ansible_operator_meta.name }}' 15 | app.kubernetes.io/part-of: '{{ ansible_operator_meta.name }}' 16 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 17 | app.kubernetes.io/component: '{{ deployment_type }}' 18 | app.kubernetes.io/operator-version: '{{ lookup("env", "OPERATOR_VERSION") }}' 19 | when: set_self_labels | bool 20 | 21 | - name: Look up details for this restore object 22 | k8s_info: 23 | api_version: "{{ api_version }}" 24 | kind: "{{ kind }}" 25 | name: "{{ ansible_operator_meta.name }}" 26 | namespace: "{{ ansible_operator_meta.namespace }}" 27 | register: this_restore 28 | 29 | - block: 30 | - include_tasks: init.yml 31 | 32 | - include_tasks: import_vars.yml 33 | 34 | - include_tasks: secrets.yml 35 | 36 | - include_tasks: deploy_eda.yml 37 | 38 | - include_tasks: postgres.yml 39 | 40 | - name: Set flag signifying this restore was successful 41 | set_fact: 42 | eda_restore_complete: True 43 | 44 | - include_tasks: cleanup.yml 45 | 46 | when: 47 | - this_restore['resources'][0]['status']['restoreComplete'] is not defined 48 | 49 | - name: Update status variables 50 | include_tasks: update_status.yml 51 | -------------------------------------------------------------------------------- /roles/common/templates/service_account.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: '{{ ansible_operator_meta.name }}' 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | labels: 8 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 9 | {% if service_account_annotations %} 10 | annotations: 11 | {{ service_account_annotations | indent(width=4) }} 12 | {% endif %} 13 | --- 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | kind: Role 16 | metadata: 17 | name: '{{ ansible_operator_meta.name }}' 18 | namespace: '{{ ansible_operator_meta.namespace }}' 19 | labels: 20 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 21 | rules: 22 | - apiGroups: [""] # "" indicates the core API group 23 | resources: ["pods", "pods/log", "jobs", "secrets", "services"] 24 | verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] 25 | - apiGroups: ["batch", "extensions"] 26 | resources: ["jobs"] 27 | verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] 28 | - apiGroups: ["networking.k8s.io"] 29 | resources: ["ingresses"] 30 | verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] 31 | 32 | --- 33 | kind: RoleBinding 34 | apiVersion: rbac.authorization.k8s.io/v1 35 | metadata: 36 | name: '{{ ansible_operator_meta.name }}' 37 | namespace: '{{ ansible_operator_meta.namespace }}' 38 | labels: 39 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 40 | subjects: 41 | - kind: ServiceAccount 42 | name: '{{ ansible_operator_meta.name }}' 43 | roleRef: 44 | apiGroup: rbac.authorization.k8s.io 45 | kind: Role 46 | name: '{{ ansible_operator_meta.name }}' 47 | -------------------------------------------------------------------------------- /molecule/default/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: localhost 4 | connection: local 5 | gather_facts: no 6 | collections: 7 | - kubernetes.core 8 | 9 | vars: 10 | ctrl_label: control-plane=controller-manager 11 | 12 | tasks: 13 | - block: 14 | - name: Import all test files from tasks/ 15 | include_tasks: '{{ item }}' 16 | with_fileglob: 17 | - tasks/*_test.yml 18 | rescue: 19 | - name: Retrieve relevant resources 20 | k8s_info: 21 | api_version: '{{ item.api_version }}' 22 | kind: '{{ item.kind }}' 23 | namespace: '{{ namespace }}' 24 | loop: 25 | - api_version: v1 26 | kind: Pod 27 | - api_version: apps/v1 28 | kind: Deployment 29 | - api_version: v1 30 | kind: Secret 31 | - api_version: v1 32 | kind: ConfigMap 33 | register: debug_resources 34 | 35 | - name: Retrieve Pod logs 36 | k8s_log: 37 | name: '{{ item.metadata.name }}' 38 | namespace: '{{ namespace }}' 39 | container: eda-manager 40 | loop: "{{ q('k8s', api_version='v1', kind='Pod', namespace=namespace, label_selector=ctrl_label) }}" 41 | register: debug_logs 42 | 43 | - name: Output gathered resources 44 | debug: 45 | var: debug_resources 46 | 47 | - name: Output gathered logs 48 | debug: 49 | var: item.log_lines 50 | loop: '{{ debug_logs.results }}' 51 | 52 | - name: Re-emit failure 53 | vars: 54 | failed_task: 55 | result: '{{ ansible_failed_result }}' 56 | fail: 57 | msg: '{{ failed_task }}' 58 | -------------------------------------------------------------------------------- /roles/postgres/tasks/create_managed_postgres.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Create Managed Postgres StatefulSet, add labels, and wait for migrations to complete 4 | 5 | - name: Set Default label selector for custom resource generated postgres 6 | set_fact: 7 | postgres_label_selector: "app.kubernetes.io/instance=postgres-{{ supported_pg_version }}-{{ ansible_operator_meta.name }}" 8 | when: postgres_label_selector is not defined 9 | 10 | - name: Create postgresql.conf ConfigMap 11 | k8s: 12 | apply: true 13 | definition: "{{ lookup('template', 'postgres.configmap.yaml.j2') }}" 14 | when: combined_database.postgres_extra_settings | length 15 | 16 | - name: Apply database resources 17 | k8s: 18 | apply: yes 19 | definition: "{{ lookup('template', item + '.yaml.j2') }}" 20 | wait: no 21 | register: postgres_resources_result 22 | loop: 23 | - 'postgres.service' 24 | - 'postgres.statefulset' 25 | no_log: "{{ no_log }}" 26 | 27 | - name: Get the postgres pod information 28 | k8s_info: 29 | kind: Pod 30 | namespace: "{{ ansible_operator_meta.namespace }}" 31 | label_selectors: 32 | - "{{ postgres_label_selector }}" 33 | field_selectors: 34 | - status.phase=Running 35 | register: postgres_pod 36 | 37 | - name: Wait for Database to initialize if managed DB 38 | k8s_info: 39 | kind: Pod 40 | namespace: '{{ ansible_operator_meta.namespace }}' 41 | label_selectors: 42 | - "{{ postgres_label_selector }}" 43 | field_selectors: 44 | - status.phase=Running 45 | register: postgres_pod 46 | until: 47 | - "postgres_pod['resources'] | length" 48 | - "postgres_pod['resources'][0]['status']['phase'] == 'Running'" 49 | - "postgres_pod['resources'][0]['status']['containerStatuses'][0]['ready'] == true" 50 | delay: 5 51 | retries: 60 52 | -------------------------------------------------------------------------------- /dev/eda-cr/eda-k8s-ing.yml: -------------------------------------------------------------------------------- 1 | apiVersion: eda.ansible.com/v1alpha1 2 | kind: EDA 3 | metadata: 4 | name: eda 5 | spec: 6 | automation_server_url: https://awx-awx.apps.aap-dev.ocp4.testing.ansible.com 7 | automation_server_ssl_verify: 'no' 8 | service_type: ClusterIP 9 | ingress_type: Ingress 10 | no_log: false 11 | image_pull_policy: Always 12 | image_pull_secrets: 13 | - redhat-operators-pull-secret 14 | admin_password_secret: custom-admin-password 15 | 16 | # -- Example image overrides 17 | # image: quay.io/ansible/eda-server 18 | # image_version: main 19 | # image_web: quay.io/ansible/eda-ui 20 | # image_web_version: 2.4.300 21 | # image_web: quay.io/ansible/eda-ui 22 | # image_web_version: latest 23 | # image: quay.io/developer/eda-server 24 | # image_version: dev 25 | 26 | # -- Example extra settings 27 | extra_settings: 28 | - setting: EDA_ALLOW_LOCAL_RESOURCE_MANAGEMENT 29 | value: true 30 | - setting: DEFAULT_PULL_POLICY 31 | value: "Always" 32 | 33 | # CA Bundle 34 | bundle_cacert_secret: my-custom-certs 35 | 36 | # -- Resource Requirements 37 | api: 38 | replicas: 1 39 | resource_requirements: 40 | requests: {} 41 | ui: 42 | replicas: 1 43 | resource_requirements: 44 | requests: {} 45 | scheduler: 46 | replicas: 1 47 | resource_requirements: 48 | requests: {} 49 | default_worker: 50 | replicas: 2 51 | resource_requirements: 52 | requests: {} 53 | activation_worker: 54 | replicas: 3 55 | resource_requirements: 56 | requests: {} 57 | 58 | database: 59 | resource_requirements: 60 | requests: 61 | cpu: 50m 62 | memory: 128Mi 63 | # worker: 64 | # replicas: 2 65 | # resource_requirements: 66 | # requests: 67 | # cpu: 50m 68 | # memory: 128Mi 69 | -------------------------------------------------------------------------------- /dev/eda-cr/eda-k8s-nodeport-cr.yml: -------------------------------------------------------------------------------- 1 | apiVersion: eda.ansible.com/v1alpha1 2 | kind: EDA 3 | metadata: 4 | name: eda 5 | spec: 6 | automation_server_url: https://awx-awx.apps.aap-dev.ocp4.testing.ansible.com 7 | automation_server_ssl_verify: 'no' 8 | service_type: NodePort 9 | ingress_type: None 10 | no_log: false 11 | image_pull_policy: Always 12 | image_pull_secrets: 13 | - redhat-operators-pull-secret 14 | admin_password_secret: custom-admin-password 15 | 16 | # -- Example image overrides 17 | # image: quay.io/ansible/eda-server 18 | # image_version: main 19 | # image_web: quay.io/ansible/eda-ui 20 | # image_web_version: 2.4.300 21 | # image_web: quay.io/ansible/eda-ui 22 | # image_web_version: latest 23 | # image: quay.io/developer/eda-server 24 | # image_version: dev 25 | 26 | # -- Example extra settings 27 | extra_settings: 28 | - setting: EDA_ALLOW_LOCAL_RESOURCE_MANAGEMENT 29 | value: true 30 | - setting: DEFAULT_PULL_POLICY 31 | value: "Always" 32 | 33 | # CA Bundle 34 | bundle_cacert_secret: my-custom-certs 35 | 36 | # -- Resource Requirements 37 | api: 38 | replicas: 1 39 | resource_requirements: 40 | requests: {} 41 | ui: 42 | replicas: 1 43 | resource_requirements: 44 | requests: {} 45 | scheduler: 46 | replicas: 1 47 | resource_requirements: 48 | requests: {} 49 | default_worker: 50 | replicas: 2 51 | resource_requirements: 52 | requests: {} 53 | activation_worker: 54 | replicas: 3 55 | resource_requirements: 56 | requests: {} 57 | 58 | database: 59 | resource_requirements: 60 | requests: 61 | cpu: 50m 62 | memory: 128Mi 63 | # worker: 64 | # replicas: 2 65 | # resource_requirements: 66 | # requests: 67 | # cpu: 50m 68 | # memory: 128Mi 69 | -------------------------------------------------------------------------------- /dev/eda-cr/eda-openshift-cr.yml: -------------------------------------------------------------------------------- 1 | apiVersion: eda.ansible.com/v1alpha1 2 | kind: EDA 3 | metadata: 4 | name: eda 5 | spec: 6 | automation_server_url: https://awx-awx.apps.aap-dev.ocp4.testing.ansible.com 7 | automation_server_ssl_verify: 'no' 8 | service_type: ClusterIP 9 | ingress_type: Route 10 | no_log: false 11 | image_pull_policy: Always 12 | image_pull_secrets: 13 | - redhat-operators-pull-secret 14 | admin_password_secret: custom-admin-password 15 | 16 | # Disable UI 17 | ui_disabled: false 18 | 19 | # -- Example image overrides 20 | # image: quay.io/ansible/eda-server 21 | # image_version: main 22 | # image_web: quay.io/ansible/eda-ui 23 | # image_web_version: 2.4.300 24 | # image_web: quay.io/ansible/eda-ui 25 | # image_web_version: latest 26 | # image: quay.io/developer/eda-server 27 | # image_version: dev 28 | 29 | # -- Example extra settings 30 | extra_settings: 31 | - setting: EDA_ALLOW_LOCAL_RESOURCE_MANAGEMENT 32 | value: true 33 | - setting: DEFAULT_PULL_POLICY 34 | value: "Always" 35 | 36 | # CA Bundle 37 | bundle_cacert_secret: my-custom-certs 38 | 39 | # -- Resource Requirements 40 | api: 41 | replicas: 1 42 | resource_requirements: 43 | requests: {} 44 | ui: 45 | replicas: 1 46 | resource_requirements: 47 | requests: {} 48 | scheduler: 49 | replicas: 1 50 | resource_requirements: 51 | requests: {} 52 | default_worker: 53 | replicas: 2 54 | resource_requirements: 55 | requests: {} 56 | activation_worker: 57 | replicas: 3 58 | resource_requirements: 59 | requests: {} 60 | 61 | database: 62 | resource_requirements: 63 | requests: 64 | cpu: 50m 65 | memory: 128Mi 66 | # worker: 67 | # replicas: 2 68 | # resource_requirements: 69 | # requests: 70 | # cpu: 50m 71 | # memory: 128Mi 72 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | # Development Guide 2 | 3 | There are development scripts and yaml exaples in the [`dev/`](../dev) directory that, along with the up.sh and down.sh scripts in the root of the repo, can be used to build, deploy and test changes made to the eda-server-operator. 4 | 5 | 6 | ## Build and Deploy 7 | 8 | 9 | If you clone the repo, and make sure you are logged in at the CLI with oc and your cluster, you can run: 10 | 11 | ``` 12 | export QUAY_USER=username 13 | export NAMESPACE=eda 14 | export TAG=test 15 | ./up.sh 16 | ``` 17 | 18 | You can add those variables to your .bashrc file so that you can just run `./up.sh` in the future. 19 | 20 | > Note: the first time you run this, it will create quay.io repos on your fork. You will need to either make those public, or create a global pull secret on your Openshift cluster. 21 | 22 | To get the URL, if on **Openshift**, run: 23 | 24 | ``` 25 | $ oc get route 26 | ``` 27 | 28 | On **k8s with ingress**, run: 29 | 30 | ``` 31 | $ kubectl get ing 32 | ``` 33 | 34 | On **k8s with nodeport**, run: 35 | 36 | ``` 37 | $ kubectl get svc 38 | ``` 39 | 40 | The URL is then `http://:` 41 | 42 | > Note: NodePort will only work if you expose that port on your underlying k8s node, or are accessing it from localhost. 43 | 44 | By default, the usename and password will be admin and password if using the `up.sh` script because it pre-creates a custom admin password k8s secret and specifies it on the EDA custom resource spec. Without that, a password would have been generated and stored in a k8s secret named -admin-password. 45 | 46 | ## Clean up 47 | 48 | 49 | Same thing for cleanup, just run ./down.sh and it will clean up your namespace on that cluster 50 | 51 | 52 | ``` 53 | ./down.sh 54 | ``` 55 | 56 | ## Running CI tests locally 57 | 58 | 59 | ``` 60 | make lint 61 | ``` 62 | 63 | More tests coming soon... 64 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | affinity: 12 | nodeAffinity: 13 | requiredDuringSchedulingIgnoredDuringExecution: 14 | nodeSelectorTerms: 15 | - matchExpressions: 16 | - key: kubernetes.io/arch 17 | operator: In 18 | values: 19 | - amd64 20 | - arm64 21 | - ppc64le 22 | - s390x 23 | - key: kubernetes.io/os 24 | operator: In 25 | values: 26 | - linux 27 | containers: 28 | - name: kube-rbac-proxy 29 | securityContext: 30 | allowPrivilegeEscalation: false 31 | capabilities: 32 | drop: 33 | - "ALL" 34 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.15.0 35 | args: 36 | - "--secure-listen-address=0.0.0.0:8443" 37 | - "--upstream=http://127.0.0.1:8080/" 38 | - "--logtostderr=true" 39 | - "--v=0" 40 | ports: 41 | - containerPort: 8443 42 | protocol: TCP 43 | name: https 44 | resources: 45 | limits: 46 | cpu: 500m 47 | memory: 128Mi 48 | requests: 49 | cpu: 5m 50 | memory: 64Mi 51 | - name: eda-manager 52 | args: 53 | - "--health-probe-bind-address=:6789" 54 | - "--metrics-bind-address=127.0.0.1:8080" 55 | - "--leader-elect" 56 | - "--leader-election-id=eda-server-operator" 57 | -------------------------------------------------------------------------------- /roles/eda/tasks/admin_password_configuration.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check for specified admin password configuration 3 | k8s_info: 4 | kind: Secret 5 | namespace: '{{ ansible_operator_meta.namespace }}' 6 | name: '{{ admin_password_secret }}' 7 | register: _custom_admin_password 8 | no_log: "{{ no_log }}" 9 | when: admin_password_secret | length 10 | 11 | - name: Check for default admin password configuration 12 | k8s_info: 13 | kind: Secret 14 | namespace: '{{ ansible_operator_meta.namespace }}' 15 | name: '{{ ansible_operator_meta.name }}-admin-password' 16 | register: _default_admin_password 17 | no_log: "{{ no_log }}" 18 | 19 | - name: Set admin password secret 20 | set_fact: 21 | _admin_password_secret: '{{ _custom_admin_password["resources"] | default([]) | length | ternary(_custom_admin_password, _default_admin_password) }}' 22 | no_log: "{{ no_log }}" 23 | 24 | - block: 25 | - name: Create admin password secret 26 | kubernetes.core.k8s: 27 | apply: true 28 | definition: "{{ lookup('template', 'secrets/admin_password_secret.yaml.j2') }}" 29 | no_log: "{{ no_log }}" 30 | 31 | - name: Read admin password secret 32 | kubernetes.core.k8s_info: 33 | kind: Secret 34 | namespace: '{{ ansible_operator_meta.namespace }}' 35 | name: '{{ ansible_operator_meta.name }}-admin-password' 36 | register: _generated_admin_password 37 | no_log: "{{ no_log }}" 38 | when: not _admin_password_secret['resources'] | default([]) | length 39 | 40 | - name: Set admin password secret 41 | set_fact: 42 | __admin_password_secret: '{{ _generated_admin_password["resources"] | default([]) | length | ternary(_generated_admin_password, _admin_password_secret) }}' 43 | no_log: "{{ no_log }}" 44 | 45 | - name: Store admin password 46 | set_fact: 47 | admin_password: "{{ __admin_password_secret['resources'][0]['data']['password'] | b64decode }}" 48 | no_log: "{{ no_log }}" 49 | -------------------------------------------------------------------------------- /roles/postgres/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for EDA 3 | deployment_type: eda 4 | database_name: "{{ deployment_type }}" 5 | database_username: "{{ deployment_type }}" 6 | 7 | _postgres_image: quay.io/sclorg/postgresql-15-c9s 8 | _postgres_image_version: latest 9 | 10 | # Add a nodeSelector for the Postgres pods. 11 | # It must match a node's labels for the pod to be scheduled on that node. 12 | # Specify as literal block. E.g.: 13 | # postgres: 14 | # postgres_selector: | 15 | # disktype: ssd 16 | # kubernetes.io/arch: amd64 17 | # kubernetes.io/os: linux 18 | 19 | # Add node tolerations for the Postgres pods. 20 | # Specify as literal block. E.g.: 21 | # postgres: 22 | # postgres_tolerations: | 23 | # - key: "dedicated" 24 | # operator: "Equal" 25 | # value: "EDA" 26 | # effect: "NoSchedule" 27 | 28 | 29 | # Assign a preexisting priority class to the postgres pod 30 | database: {} 31 | _database: 32 | resource_requirements: 33 | requests: 34 | cpu: 50m 35 | memory: 100Mi 36 | storage_requirements: 37 | requests: 38 | storage: 8Gi 39 | node_selector: {} 40 | priority_class: '' 41 | tolerations: [] 42 | postgres_extra_args: '' # Deprecated 43 | postgres_extra_settings: [] # Define postgresql.conf configurations 44 | postgres_keep_pvc_after_upgrade: true # Specify whether or not to keep the old PVC after PostgreSQL upgrades 45 | postgres_data_volume_init: false 46 | postgres_init_container_commands: | 47 | chown 26:0 /var/lib/pgsql/data 48 | chmod 700 /var/lib/pgsql/data 49 | database_secret: '' # Name of k8s secret containing postgres host, username, password, database, port, and type 50 | 51 | 52 | image_pull_policy: IfNotPresent 53 | image_pull_secrets: [] 54 | 55 | # Labels defined on the resource, which should be propagated to child resources 56 | additional_labels: [] 57 | 58 | # Enable PostgreSQL SCRAM-SHA-256 migration 59 | postgres_scram_migration_enabled: true 60 | -------------------------------------------------------------------------------- /docs/kustomize-install.md: -------------------------------------------------------------------------------- 1 | # Install EDA Server Operator with Kustomize 2 | 3 | Some folks may prefer to install the EDA Server Operator using [kustomize](https://kustomize.io/) directly with a personalized kustomize file. This allows you to easily modify configuration files including the operator's manager deployment itself. To do so, follow the instructions below. 4 | 5 | 6 | 1. Create a `kustomization.yaml` file with the the following contents. Be sure to change `newTag` to the latest released tag, or the tag you would like to deploy. 7 | 8 | ```yaml 9 | apiVersion: kustomize.config.k8s.io/v1beta1 10 | kind: Kustomization 11 | resources: 12 | - config/default 13 | 14 | # Set the image tags to match the git version from above 15 | images: 16 | - name: quay.io/ansible/eda-server-operator 17 | newTag: 0.0.1 18 | 19 | # Specify a custom namespace in which to install EDA 20 | namespace: eda 21 | ``` 22 | 23 | 2. Then kustomize and apply it by running: 24 | 25 | ``` 26 | kustomize build . | kubectl apply -f - 27 | ``` 28 | > **TIP:** If you need to change any of the default settings for the operator (such as resources.limits), you can add [patches](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/patches/) at the bottom of your kustomization.yaml file. There is also some useful information in [this ansible operator turotial](https://sdk.operatorframework.io/docs/building-operators/ansible/tutorial). 29 | 30 | 31 | ### Install Latest 32 | 33 | It is possible to install the latest available changes from the `main` branch using this approach as well. To do so, you will want a kustomization.yaml file like this: 34 | 35 | ```yaml 36 | apiVersion: kustomize.config.k8s.io/v1beta1 37 | kind: Kustomization 38 | resources: 39 | - github.com/ansible/eda-server-operator/config/default 40 | 41 | images: 42 | - name: quay.io/ansible/eda-server-operator 43 | newTag: main 44 | 45 | # Specify a custom namespace in which to install EDA 46 | namespace: eda 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/trusting-a-custom-certificate-authority.md: -------------------------------------------------------------------------------- 1 | ## Trusting a Custom Certificate Authority 2 | 3 | In cases which you need to trust a custom Certificate Authority, there are few variables you can customize for the `eda-server-operator`. 4 | 5 | Trusting a custom Certificate Authority allows the EDA to access network services configured with SSL certificates issued locally, such as cloning a project from from an internal Git server via HTTPS. If it is needed, you will likely see errors like this when doing project syncs: 6 | 7 | ```bash 8 | fatal: unable to access 'https://private.repo./mine/ansible-rulebook.git': SSL certificate problem: unable to get local issuer certificate 9 | ``` 10 | 11 | 12 | | Name | Description | Default | 13 | | -------------------------------- | ---------------------------------------- | --------| 14 | | bundle_cacert_secret | Certificate Authority secret name | '' | 15 | Please note the `eda-server-operator` will look for the data field `ldap-ca.crt` in the specified secret when using the `ldap_cacert_secret`, whereas the data field `bundle-ca.crt` is required for `bundle_cacert_secret` parameter. 16 | 17 | Example of customization could be: 18 | 19 | ```yaml 20 | --- 21 | spec: 22 | ... 23 | bundle_cacert_secret: -custom-certs 24 | ``` 25 | 26 | Create the secret with CLI: 27 | 28 | * Certificate Authority secret 29 | 30 | ``` 31 | # kubectl create secret generic -custom-certs \ 32 | --from-file=ldap-ca.crt= \ 33 | --from-file=bundle-ca.crt= 34 | ``` 35 | 36 | 37 | Alternatively, you can also create the secret with `kustomization.yaml` file: 38 | 39 | ```yaml 40 | .... 41 | 42 | secretGenerator: 43 | - name: -custom-certs 44 | files: 45 | - bundle-ca.crt= 46 | options: 47 | disableNameSuffixHash: true 48 | 49 | ... 50 | ``` 51 | 52 | -------------------------------------------------------------------------------- /roles/eda/templates/eda-ui.service.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: "{{ ansible_operator_meta.name }}-ui" 6 | namespace: "{{ ansible_operator_meta.namespace }}" 7 | labels: 8 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 9 | app.kubernetes.io/component: '{{ deployment_type }}-ui' 10 | {% if service_annotations %} 11 | annotations: 12 | {{ service_annotations | indent(width=4) }} 13 | {% endif %} 14 | spec: 15 | ports: 16 | {% if service_type | lower == "nodeport" %} 17 | - port: 80 18 | protocol: TCP 19 | targetPort: 8080 20 | name: http 21 | {% if nodeport_port is defined %} 22 | nodePort: {{ nodeport_port }} 23 | {% endif %} 24 | {% elif service_type | lower != 'loadbalancer' and loadbalancer_protocol | lower != 'https' %} 25 | - port: 80 26 | protocol: TCP 27 | targetPort: 8080 28 | name: http 29 | {% endif %} 30 | {% if ingress_type | lower == 'route' and route_tls_termination_mechanism | lower == 'passthrough' %} 31 | - port: 443 32 | protocol: TCP 33 | targetPort: 8853 # TODO: Use something like 8053 and update nginx.con 34 | name: https 35 | {% endif %} 36 | {% if service_type | lower == 'loadbalancer' and loadbalancer_protocol | lower == 'https' %} 37 | - port: {{ loadbalancer_port }} 38 | protocol: TCP 39 | targetPort: 8080 40 | name: https 41 | {% elif service_type | lower == 'loadbalancer' and loadbalancer_protocol | lower != 'https' %} 42 | - port: {{ loadbalancer_port }} 43 | protocol: TCP 44 | targetPort: 8080 45 | name: http 46 | {% endif %} 47 | selector: 48 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 49 | app.kubernetes.io/component: '{{ deployment_type }}-ui' 50 | {% if service_type | lower == "nodeport" %} 51 | type: NodePort 52 | {% elif service_type | lower == "loadbalancer" %} 53 | type: LoadBalancer 54 | {% else %} 55 | type: ClusterIP 56 | {% endif %} 57 | -------------------------------------------------------------------------------- /roles/postgres/tasks/set_configuration_secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Determine and set postgres configuration secret 3 | - name: Check for specified PostgreSQL configuration 4 | k8s_info: 5 | kind: Secret 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | name: '{{ database.database_secret }}' 8 | register: _custom_pg_config_resources 9 | when: 10 | - database is defined 11 | - database | length 12 | - database.database_secret is defined 13 | - database.database_secret | length 14 | no_log: "{{ no_log }}" 15 | 16 | - name: Check for default PostgreSQL configuration 17 | k8s_info: 18 | kind: Secret 19 | namespace: '{{ ansible_operator_meta.namespace }}' 20 | name: '{{ ansible_operator_meta.name }}-postgres-configuration' 21 | register: _default_pg_config_resources 22 | no_log: "{{ no_log }}" 23 | 24 | - name: Set PostgreSQL configuration based on if user secret exists 25 | set_fact: 26 | _pg_config: '{{ _custom_pg_config_resources["resources"] | default([]) | length | ternary(_custom_pg_config_resources, _default_pg_config_resources) }}' 27 | no_log: "{{ no_log }}" 28 | 29 | - block: 30 | - name: Create Database configuration 31 | k8s: 32 | apply: true 33 | definition: "{{ lookup('template', 'postgres.secret.yaml.j2') }}" 34 | no_log: "{{ no_log }}" 35 | 36 | - name: Read Database Configuration 37 | k8s_info: 38 | kind: Secret 39 | namespace: '{{ ansible_operator_meta.namespace }}' 40 | name: '{{ ansible_operator_meta.name }}-postgres-configuration' 41 | register: _generated_pg_config_resources 42 | no_log: "{{ no_log }}" 43 | when: not _pg_config['resources'] | default([]) | length 44 | 45 | - name: Set PostgreSQL Configuration 46 | set_fact: 47 | pg_config: '{{ _generated_pg_config_resources["resources"] | default([]) | length | ternary(_generated_pg_config_resources, _pg_config) }}' 48 | no_log: "{{ no_log }}" 49 | 50 | - name: Set actual postgres configuration secret used 51 | set_fact: 52 | __database_secret: "{{ pg_config['resources'][0]['metadata']['name'] }}" 53 | -------------------------------------------------------------------------------- /roles/eda/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for EDA 3 | 4 | - name: Combine default and custom vars for each component 5 | include_tasks: combine_defaults.yml 6 | 7 | - name: Configure cluster 8 | include_role: 9 | name: common 10 | 11 | - name: Idle EDA 12 | include_tasks: idle_deployment.yml 13 | when: idle_deployment | bool 14 | 15 | - name: Setup Redis 16 | include_role: 17 | name: redis 18 | 19 | - name: Check for old postgres configuration 20 | kubernetes.core.k8s_info: 21 | kind: Secret 22 | namespace: '{{ ansible_operator_meta.namespace }}' 23 | name: '{{ old_postgres_configuration_secret }}' 24 | register: old_pg_config 25 | no_log: "{{ no_log }}" 26 | when: old_postgres_configuration_secret is defined 27 | 28 | - name: Setup PostgreSQL Database 29 | include_role: 30 | name: postgres 31 | 32 | - name: Migrate data from old database 33 | include_tasks: migrate_data.yml 34 | when: 35 | - old_pg_config is defined 36 | - old_pg_config['resources'] | default([]) | length > 0 37 | register: migration_result 38 | 39 | - name: Cleanup migration references 40 | include_tasks: cleanup_migration_references.yml 41 | when: 42 | - migration_result is defined 43 | - migration_result is success 44 | - eda_migrated_from_secret is defined 45 | 46 | - name: Set EDA app images 47 | include_tasks: set_images.yml 48 | 49 | - name: Set Bundle Certificate Authority 50 | include_tasks: set_bundle_cacert.yml 51 | when: 52 | - bundle_cacert_secret | length 53 | 54 | - name: Create DB fields encryption 55 | include_tasks: db_fields_encryption_configuration.yml 56 | 57 | - name: Create admin password 58 | include_tasks: admin_password_configuration.yml 59 | 60 | - name: Load Route TLS certificate 61 | include_tasks: load_route_tls_secret.yml 62 | when: 63 | - ingress_type | lower == 'route' 64 | - route_tls_secret | length 65 | 66 | - name: Deploy EDA 67 | include_tasks: deploy_eda.yml 68 | 69 | - name: Create admin user 70 | include_tasks: create_admin_user.yml 71 | when: eda_api_pod_name | length 72 | 73 | - name: Update status variables 74 | include_tasks: update_status.yml 75 | -------------------------------------------------------------------------------- /roles/common/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get information about the cluster 3 | set_fact: 4 | api_groups: "{{ lookup('k8s', cluster_info='api_groups') }}" 5 | when: 6 | - not is_openshift | bool 7 | - not is_k8s | bool 8 | 9 | - name: Determine the cluster type 10 | set_fact: 11 | is_openshift: "{{ True if 'route.openshift.io' in api_groups else False }}" 12 | is_k8s: "{{ False if 'route.openshift.io' in api_groups else True }}" 13 | when: 14 | - not is_openshift | bool 15 | - not is_k8s | bool 16 | 17 | # Indicate what kind of cluster we are in (OpenShift or Kubernetes). 18 | - debug: 19 | msg: "CLUSTER TYPE: is_openshift={{ is_openshift }}; is_k8s={{ is_k8s }}" 20 | 21 | # tasks file for EDA 22 | 23 | # get and set annotations passed via CR 24 | - name: Look up details for this deployment 25 | k8s_info: 26 | api_version: "{{ api_version }}" 27 | kind: "{{ kind }}" 28 | name: "{{ ansible_operator_meta.name }}" 29 | namespace: "{{ ansible_operator_meta.namespace }}" 30 | register: this_eda 31 | 32 | - name: set annotations based on this_eda 33 | set_fact: 34 | this_annotations: "{{ this_eda['resources'][0]['metadata']['annotations'] | default({}) }}" 35 | 36 | - name: set client_request_timeout based on annotation 37 | set_fact: 38 | client_request_timeout: "{{ (this_annotations['aap.ansible.io/client-request-timeout'][:-1]) | int }}" 39 | client_request_timeout_overidden: true 40 | when: 41 | - "'aap.ansible.io/client-request-timeout' in this_annotations" 42 | - this_annotations['aap.ansible.io/client-request-timeout'] is match('^\\d+s$') 43 | 44 | - name: client_request_timeout has been changed 45 | debug: 46 | msg: "client_request_timeout's default 30s value has been overriden by the annotation 'aap.ansible.io/client-request-timeout' to {{ client_request_timeout }}s" 47 | when: client_request_timeout_overidden | default(false) 48 | 49 | - name: Create EDA ServiceAccount 50 | k8s: 51 | apply: yes 52 | definition: "{{ lookup('template', item + '.yaml.j2') }}" 53 | loop: 54 | - 'service_account' 55 | no_log: "{{ no_log }}" 56 | 57 | - name: Patch Labels 58 | include_tasks: patch_labels.yml 59 | -------------------------------------------------------------------------------- /roles/eda/tasks/db_fields_encryption_configuration.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check for specified DB fields encryption configuration 3 | kubernetes.core.k8s_info: 4 | kind: Secret 5 | namespace: '{{ ansible_operator_meta.namespace }}' 6 | name: '{{ db_fields_encryption_secret }}' 7 | register: _custom_db_fields_encryption 8 | no_log: "{{ no_log }}" 9 | when: db_fields_encryption_secret | length 10 | 11 | - name: Check for default DB fields encryption configuration 12 | kubernetes.core.k8s_info: 13 | kind: Secret 14 | namespace: '{{ ansible_operator_meta.namespace }}' 15 | name: '{{ ansible_operator_meta.name }}-db-fields-encryption-secret' 16 | register: _default_db_fields_encryption 17 | no_log: "{{ no_log }}" 18 | 19 | - name: Set DB fields encryption secret 20 | ansible.builtin.set_fact: 21 | _db_fields_encryption_secret: '{{ _custom_db_fields_encryption["resources"] | default([]) | length | ternary(_custom_db_fields_encryption, _default_db_fields_encryption) }}' 22 | no_log: "{{ no_log }}" 23 | 24 | - block: 25 | - name: Create DB fields encryption secret 26 | kubernetes.core.k8s: 27 | apply: true 28 | definition: "{{ lookup('template', 'secrets/db_fields_encryption.yaml.j2') }}" 29 | no_log: "{{ no_log }}" 30 | 31 | - name: Read DB fields encryption secret 32 | kubernetes.core.k8s_info: 33 | kind: Secret 34 | namespace: '{{ ansible_operator_meta.namespace }}' 35 | name: '{{ ansible_operator_meta.name }}-db-fields-encryption-secret' 36 | register: _generated_db_fields_encryption 37 | no_log: "{{ no_log }}" 38 | when: not _db_fields_encryption_secret['resources'] | default([]) | length 39 | 40 | - name: Set DB fields encryption secret 41 | ansible.builtin.set_fact: 42 | db_fields_encryption: '{{ _generated_db_fields_encryption["resources"] | default([]) | length | ternary(_generated_db_fields_encryption, _db_fields_encryption_secret) }}' 43 | no_log: "{{ no_log }}" 44 | 45 | - name: Store DB fields encryption secret name 46 | ansible.builtin.set_fact: 47 | db_fields_encryption_secret_name: "{{ db_fields_encryption['resources'][0]['metadata']['name'] }}" 48 | no_log: "{{ no_log }}" 49 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/assigning-eda-pods-to-specific-nodes.md: -------------------------------------------------------------------------------- 1 | # Assigning EDA pods to specific nodes 2 | 3 | You can constrain the EDA pods created by the operator to run on a certain subset of nodes. `node_selector` and `tolerations` contrain the EDA pods to run only on the nodes that match the specified key/value pairs. 4 | 5 | Each component of EDA has its own `node_selector` and `tolerations` values. The supported components are `ui`, `api`, `default_worker`, `activation_worker`, `worker`, `scheduler`, `redis` and `database`. 6 | 7 | | Name | Description | Type | Default | 8 | |---|---|---|---| 9 | | node_selector | Pods' nodeSelector. | `dictionary` | `{}` | 10 | | tolerations | Pods' tolerations | `list` | `[]` | 11 | 12 | ## Example 13 | 14 | ``` 15 | --- 16 | spec: 17 | ... 18 | ui: 19 | node_selector: 20 | system/dedicated: ui-node-group 21 | tolerations: 22 | - key: "system/dedicated" 23 | operator: "Equal" 24 | value: "ui-node-group" 25 | effect: "NoSchedule" 26 | - key: "application/type" 27 | operator: "Equal" 28 | value: "frontend" 29 | effect: "NoSchedule" 30 | api: 31 | node_selector: 32 | system/dedicated: api-node-group 33 | tolerations: 34 | - key: "system/dedicated" 35 | operator: "Equal" 36 | value: "api-node-group" 37 | effect: "NoSchedule" 38 | default_worker: 39 | node_selector: 40 | system/dedicated: worker-node-group 41 | tolerations: 42 | - key: "system/dedicated" 43 | operator: "Equal" 44 | value: "worker-node-group" 45 | effect: "NoSchedule" 46 | database: 47 | node_selector: 48 | system/dedicated: database-node-group 49 | tolerations: 50 | - key: "system/dedicated" 51 | operator: "Equal" 52 | value: "database-node-group" 53 | effect: "NoSchedule" 54 | ... 55 | ``` 56 | 57 | For more information about how node selectors and tolerations work, refer to Kubernetes docs on [nodeSelector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) and [Taints and Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/). -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 4 | ## Development Environment 5 | 6 | There are a couple ways to make and test changes to an Ansible operator. The easiest way is to build and deploy the operator from your branch using the make targets. This is closed to how the operator will be used, and is what is documented below. However, it may be useful to run the EDA Operator roles directly on your local machine for faster iteration. This involves a bit more set up, and is described in the [Debugging docs](./docs/debugging.md). 7 | 8 | First, you need to have a k8s cluster up. If you don't already have a k8s cluster, you can use minikube to start a lightweight k8s cluster locally by following these [minikube test cluster docs](./docs/minikube-test-cluster.md). 9 | 10 | 11 | 12 | ### Build Operator Image 13 | 14 | Clone the eda-server-operator 15 | 16 | ``` 17 | git clone git@github.com:ansible/eda-server-operator.git 18 | ``` 19 | 20 | Create an image repo in your user called `eda-server-operator` on [quay.io](https://quay.io) or your preferred image registry. 21 | 22 | Build & push the operator image 23 | 24 | ``` 25 | export QUAY_USER=username 26 | export TAG=feature-branch 27 | make docker-build docker-push IMG=quay.io/$QUAY_USER/eda-server-operator:$TAG 28 | ``` 29 | 30 | 31 | ### Deploy EDA Operator 32 | 33 | 34 | 1. Log in to your K8s or Openshift cluster. 35 | 36 | ``` 37 | kubectl login 38 | ``` 39 | 40 | 2. Run the `make deploy` target 41 | 42 | ``` 43 | NAMESPACE=eda IMG=quay.io/$QUAY_USER/eda-server-operator:$TAG make deploy 44 | ``` 45 | > **Note** The `latest` tag on the quay.io/ansible/eda-server-operator repo is the latest _released_ (tagged) version and the `main` tag is built from the HEAD of the `main` branch. To deploy with the latest code in `main` branch, check out the main branch, and use `IMG=quay.io/ansible/eda-server-operator:main` instead. 46 | 47 | 48 | ### Create an EDA CR 49 | 50 | Create a yaml file defining the EDA custom resource 51 | 52 | ```yaml 53 | # eda.yaml 54 | apiVersion: eda.ansible.com/v1alpha1 55 | kind: EDA 56 | metadata: 57 | name: my-eda 58 | spec: 59 | automation_server_url: https://awx-host 60 | ``` 61 | 62 | 3. Now apply this yaml 63 | 64 | ```bash 65 | $ kubectl apply -f eda.yaml 66 | ``` 67 | -------------------------------------------------------------------------------- /roles/common/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: Ansible 4 | description: Common role for EDA operator configuration 5 | company: Red Hat, Inc. 6 | 7 | # If the issue tracker for your role is not on github, uncomment the 8 | # next line and provide a value 9 | # issue_tracker_url: http://example.com/issue/tracker 10 | 11 | # Some suggested licenses: 12 | # - BSD (default) 13 | # - MIT 14 | # - GPLv2 15 | # - GPLv3 16 | # - Apache 17 | # - CC-BY 18 | license: MIT 19 | 20 | min_ansible_version: 2.9 21 | 22 | # If this a Container Enabled role, provide the minimum Ansible Container version. 23 | # min_ansible_container_version: 24 | 25 | # Optionally specify the branch Galaxy will use when accessing the GitHub 26 | # repo for this role. During role install, if no tags are available, 27 | # Galaxy will use this branch. During import Galaxy will access files on 28 | # this branch. If Travis integration is configured, only notifications for this 29 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 30 | # (usually master) will be used. 31 | #github_branch: 32 | 33 | # 34 | # Provide a list of supported platforms, and for each platform a list of versions. 35 | # If you don't wish to enumerate all versions for a particular platform, use 'all'. 36 | # To view available platforms and versions (or releases), visit: 37 | # https://galaxy.ansible.com/api/v1/platforms/ 38 | # 39 | # platforms: 40 | # - name: Fedora 41 | # versions: 42 | # - all 43 | # - 25 44 | # - name: SomePlatform 45 | # versions: 46 | # - all 47 | # - 1.0 48 | # - 7 49 | # - 99.99 50 | 51 | galaxy_tags: [] 52 | # List tags for your role here, one per line. A tag is a keyword that describes 53 | # and categorizes the role. Users find roles by searching for tags. Be sure to 54 | # remove the '[]' above, if you add tags to this list. 55 | # 56 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 57 | # Maximum 20 tags per role. 58 | 59 | dependencies: [] 60 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 61 | # if you add dependencies to this list. 62 | collections: 63 | - operator_sdk.util 64 | - kubernetes.core 65 | -------------------------------------------------------------------------------- /roles/redis/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: Ansible 4 | description: Redis role for EDA operator 5 | company: Red Hat, Inc. 6 | 7 | # If the issue tracker for your role is not on github, uncomment the 8 | # next line and provide a value 9 | # issue_tracker_url: http://example.com/issue/tracker 10 | 11 | # Some suggested licenses: 12 | # - BSD (default) 13 | # - MIT 14 | # - GPLv2 15 | # - GPLv3 16 | # - Apache 17 | # - CC-BY 18 | license: license (GPLv2, CC-BY, etc) 19 | 20 | min_ansible_version: 2.9 21 | 22 | # If this a Container Enabled role, provide the minimum Ansible Container version. 23 | # min_ansible_container_version: 24 | 25 | # Optionally specify the branch Galaxy will use when accessing the GitHub 26 | # repo for this role. During role install, if no tags are available, 27 | # Galaxy will use this branch. During import Galaxy will access files on 28 | # this branch. If Travis integration is configured, only notifications for this 29 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 30 | # (usually master) will be used. 31 | #github_branch: 32 | 33 | # 34 | # Provide a list of supported platforms, and for each platform a list of versions. 35 | # If you don't wish to enumerate all versions for a particular platform, use 'all'. 36 | # To view available platforms and versions (or releases), visit: 37 | # https://galaxy.ansible.com/api/v1/platforms/ 38 | # 39 | # platforms: 40 | # - name: Fedora 41 | # versions: 42 | # - all 43 | # - 25 44 | # - name: SomePlatform 45 | # versions: 46 | # - all 47 | # - 1.0 48 | # - 7 49 | # - 99.99 50 | 51 | galaxy_tags: [] 52 | # List tags for your role here, one per line. A tag is a keyword that describes 53 | # and categorizes the role. Users find roles by searching for tags. Be sure to 54 | # remove the '[]' above, if you add tags to this list. 55 | # 56 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 57 | # Maximum 20 tags per role. 58 | 59 | dependencies: [] 60 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 61 | # if you add dependencies to this list. 62 | collections: 63 | - operator_sdk.util 64 | - kubernetes.core 65 | -------------------------------------------------------------------------------- /roles/eda/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: Ansible 4 | description: Application role for EDA operator configuration 5 | company: Red Hat, Inc. 6 | 7 | # If the issue tracker for your role is not on github, uncomment the 8 | # next line and provide a value 9 | # issue_tracker_url: http://example.com/issue/tracker 10 | 11 | # Some suggested licenses: 12 | # - BSD (default) 13 | # - MIT 14 | # - GPLv2 15 | # - GPLv3 16 | # - Apache 17 | # - CC-BY 18 | license: license (GPLv2, CC-BY, etc) 19 | 20 | min_ansible_version: 2.9 21 | 22 | # If this a Container Enabled role, provide the minimum Ansible Container version. 23 | # min_ansible_container_version: 24 | 25 | # Optionally specify the branch Galaxy will use when accessing the GitHub 26 | # repo for this role. During role install, if no tags are available, 27 | # Galaxy will use this branch. During import Galaxy will access files on 28 | # this branch. If Travis integration is configured, only notifications for this 29 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 30 | # (usually master) will be used. 31 | #github_branch: 32 | 33 | # 34 | # Provide a list of supported platforms, and for each platform a list of versions. 35 | # If you don't wish to enumerate all versions for a particular platform, use 'all'. 36 | # To view available platforms and versions (or releases), visit: 37 | # https://galaxy.ansible.com/api/v1/platforms/ 38 | # 39 | # platforms: 40 | # - name: Fedora 41 | # versions: 42 | # - all 43 | # - 25 44 | # - name: SomePlatform 45 | # versions: 46 | # - all 47 | # - 1.0 48 | # - 7 49 | # - 99.99 50 | 51 | galaxy_tags: [] 52 | # List tags for your role here, one per line. A tag is a keyword that describes 53 | # and categorizes the role. Users find roles by searching for tags. Be sure to 54 | # remove the '[]' above, if you add tags to this list. 55 | # 56 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 57 | # Maximum 20 tags per role. 58 | 59 | dependencies: [] 60 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 61 | # if you add dependencies to this list. 62 | collections: 63 | - operator_sdk.util 64 | - kubernetes.core 65 | -------------------------------------------------------------------------------- /roles/postgres/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: Ansible 4 | description: Postgres database role for EDA operator 5 | company: Red Hat, Inc. 6 | 7 | # If the issue tracker for your role is not on github, uncomment the 8 | # next line and provide a value 9 | # issue_tracker_url: http://example.com/issue/tracker 10 | 11 | # Some suggested licenses: 12 | # - BSD (default) 13 | # - MIT 14 | # - GPLv2 15 | # - GPLv3 16 | # - Apache 17 | # - CC-BY 18 | license: license (GPLv2, CC-BY, etc) 19 | 20 | min_ansible_version: 2.9 21 | 22 | # If this a Container Enabled role, provide the minimum Ansible Container version. 23 | # min_ansible_container_version: 24 | 25 | # Optionally specify the branch Galaxy will use when accessing the GitHub 26 | # repo for this role. During role install, if no tags are available, 27 | # Galaxy will use this branch. During import Galaxy will access files on 28 | # this branch. If Travis integration is configured, only notifications for this 29 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 30 | # (usually master) will be used. 31 | #github_branch: 32 | 33 | # 34 | # Provide a list of supported platforms, and for each platform a list of versions. 35 | # If you don't wish to enumerate all versions for a particular platform, use 'all'. 36 | # To view available platforms and versions (or releases), visit: 37 | # https://galaxy.ansible.com/api/v1/platforms/ 38 | # 39 | # platforms: 40 | # - name: Fedora 41 | # versions: 42 | # - all 43 | # - 25 44 | # - name: SomePlatform 45 | # versions: 46 | # - all 47 | # - 1.0 48 | # - 7 49 | # - 99.99 50 | 51 | galaxy_tags: [] 52 | # List tags for your role here, one per line. A tag is a keyword that describes 53 | # and categorizes the role. Users find roles by searching for tags. Be sure to 54 | # remove the '[]' above, if you add tags to this list. 55 | # 56 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 57 | # Maximum 20 tags per role. 58 | 59 | dependencies: [] 60 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 61 | # if you add dependencies to this list. 62 | collections: 63 | - operator_sdk.util 64 | - kubernetes.core 65 | -------------------------------------------------------------------------------- /docs/maintainers/release.md: -------------------------------------------------------------------------------- 1 | # EDA Server Operator Release Guide 2 | 3 | This document provides step-by-step instructions for releasing a new version of the EDA Server Operator. It includes tagging a new release, building and pushing images, and updating release artifacts. 4 | 5 | ## Release Workflow 6 | 7 | ### 1. Trigger the Release GitHub Action 8 | 9 | The release process is automated through a GitHub Action (GHA) workflow. You can trigger this workflow manually via the GitHub UI. 10 | 11 | - Navigate to the 'Actions' tab in the GitHub repository. 12 | - Select the 'Stage Release' workflow. 13 | - Click on 'Run workflow' dropdown. 14 | - Enter the new version number (e.g., `1.2.3`) in the 'Release Version' input box. 15 | - Click 'Run workflow'. 16 | 17 | ### 2. Monitor the Workflow 18 | 19 | - Monitor the workflow for completion. 20 | - The workflow will handle: 21 | - Tagging the release. 22 | - Building and pushing operator image for multiple platforms. 23 | - Generating the `operator.yaml` file. 24 | - Creating a draft release and attaching the `operator.yaml` as an artifact. 25 | 26 | ### 3. Publish the Release 27 | 28 | Once the draft release is created, you need to publish it: 29 | 30 | - Go to the 'Releases' section in the GitHub repository. 31 | - Open the draft release created by the GitHub Action. 32 | - Review and edit the release notes as necessary. Add notable changes, deprecation warnings, and useful upgrade information for users. 33 | - Once satisfied, publish the release. This will trigger the 'Promote Operator Release' GHA, which will publish the operator image to quay.io as well as build and push the bundle and catalog images. 34 | 35 | ### 4. Post-Release Checks 36 | 37 | - Ensure that the images are correctly tagged on Quay. 38 | - Verify that the `operator.yaml` file is attached to the release and is correct. 39 | 40 | ## Troubleshooting 41 | 42 | If you encounter issues during the release process: 43 | 44 | - Check the GitHub Action logs for any errors or warnings. 45 | - Verify that all prerequisites are met. 46 | - For more specific issues, refer to the workflow file `.github/workflows/stage.yml` for insights. 47 | 48 | ## Notes 49 | 50 | - Do not manually tag or create releases; always use the automated workflow. 51 | - Ensure that you're familiar with the semantic versioning guidelines when assigning a version number. 52 | -------------------------------------------------------------------------------- /roles/eda/templates/eda-event-stream.configmap.yaml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: '{{ ansible_operator_meta.name }}-nginx-event-stream-configmap' 5 | namespace: '{{ ansible_operator_meta.namespace }}' 6 | data: 7 | nginx_event_stream_default_conf_template: | 8 | events { 9 | worker_connections 1024; 10 | } 11 | http { 12 | map $http_x_forwarded_proto $remote_scheme { 13 | default $http_x_forwarded_proto; 14 | '' $scheme; 15 | } 16 | 17 | include mime.types; 18 | types { 19 | application/manifest+json webmanifest; 20 | } 21 | 22 | upstream eda-api-event-stream-backend { 23 | server 127.0.0.1:{{ event_stream_django_port }}; 24 | } 25 | 26 | server { 27 | listen {{ event_stream_nginx_port }}; 28 | 29 | location / { 30 | proxy_pass http://eda-api-event-stream-backend/; 31 | proxy_set_header Host $host:$server_port; 32 | proxy_set_header X-Forwarded-Proto $scheme; 33 | proxy_set_header X-Forwarded-Host $host; 34 | proxy_set_header X-Forwarded-Port $server_port; 35 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 36 | # Return 503 Service Unavailable with JSON response if gunicorn fails to respond 37 | proxy_read_timeout {{ eda_nginx_read_timeout }}s; 38 | error_page 504 =503 /json_503; 39 | error_page 502 =503 /json_503; # Optional, in case gunicorn is completely down 40 | } 41 | location = /json_503 { 42 | # Custom JSON response for 503 Service Unavailable 43 | internal; 44 | add_header Content-Type application/json; 45 | 46 | # Check if X-Request-ID is set and include it in the response 47 | if ($http_x_request_id) { 48 | return 503 '{"status": "error", "message": "Service Unavailable", "code": 503, "request_id": "$http_x_request_id"}'; 49 | } 50 | 51 | # If X-Request-ID is not set, just return the basic JSON response 52 | return 503 '{"status": "error", "message": "Service Unavailable", "code": 503}'; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /roles/eda/templates/eda-api.ingress.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if ingress_type|lower == "ingress" %} 2 | --- 3 | {% if ingress_api_version is defined %} 4 | apiVersion: '{{ ingress_api_version }}' 5 | {% endif %} 6 | kind: Ingress 7 | metadata: 8 | name: '{{ ansible_operator_meta.name }}-api-ingress' 9 | namespace: '{{ ansible_operator_meta.namespace }}' 10 | labels: 11 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 12 | {% if ingress_annotations %} 13 | annotations: 14 | {{ ingress_annotations | indent(width=4) }} 15 | {% endif %} 16 | spec: 17 | {% if ingress_class_name %} 18 | ingressClassName: '{{ ingress_class_name }}' 19 | {% endif %} 20 | rules: 21 | - http: 22 | paths: 23 | - path: '{{ ingress_path }}' 24 | pathType: '{{ ingress_path_type }}' 25 | backend: 26 | service: 27 | name: '{{ ansible_operator_meta.name }}-api' 28 | port: 29 | number: 8000 30 | {% if hostname %} 31 | host: {{ hostname }} 32 | {% endif %} 33 | {% if ingress_tls_secret %} 34 | tls: 35 | - hosts: 36 | - {{ hostname }} 37 | secretName: {{ ingress_tls_secret }} 38 | {% endif %} 39 | {% endif %} 40 | 41 | {% if ingress_type|lower == "route" %} 42 | --- 43 | {% if route_api_version is defined %} 44 | apiVersion: '{{ route_api_version }}' 45 | {% endif %} 46 | kind: Route 47 | metadata: 48 | name: '{{ ansible_operator_meta.name }}' 49 | namespace: '{{ ansible_operator_meta.namespace }}' 50 | labels: 51 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 52 | spec: 53 | {% if route_host != '' %} 54 | host: {{ route_host }} 55 | {% endif %} 56 | port: 57 | targetPort: '{{ api_nginx_port }}' 58 | tls: 59 | insecureEdgeTerminationPolicy: Redirect 60 | termination: {{ route_tls_termination_mechanism | lower }} 61 | {% if route_tls_termination_mechanism | lower == 'edge' and route_tls_secret != '' %} 62 | key: |- 63 | {{ route_tls_key | indent(width=6, first=True) }} 64 | certificate: |- 65 | {{ route_tls_crt | indent(width=6, first=True) }} 66 | {% if route_ca_crt is defined %} 67 | caCertificate: |- 68 | {{ route_ca_crt | indent(width=6, first=True) }} 69 | {% endif %} 70 | {% endif %} 71 | to: 72 | kind: Service 73 | name: {{ ansible_operator_meta.name }}-api 74 | weight: 100 75 | wildcardPolicy: None 76 | {% endif %} 77 | -------------------------------------------------------------------------------- /roles/eda/templates/eda-event-stream.ingress.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if ingress_type|lower == "ingress" %} 2 | --- 3 | {% if ingress_api_version is defined %} 4 | apiVersion: '{{ ingress_api_version }}' 5 | {% endif %} 6 | kind: Ingress 7 | metadata: 8 | name: '{{ ansible_operator_meta.name }}-event-stream-ingress' 9 | namespace: '{{ ansible_operator_meta.namespace }}' 10 | labels: 11 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 12 | {% if ingress_annotations %} 13 | annotations: 14 | {{ ingress_annotations | indent(width=4) }} 15 | {% endif %} 16 | spec: 17 | {% if ingress_class_name %} 18 | ingressClassName: '{{ ingress_class_name }}' 19 | {% endif %} 20 | rules: 21 | - http: 22 | paths: 23 | - path: '/' 24 | pathType: '{{ ingress_path_type }}' 25 | backend: 26 | service: 27 | name: '{{ ansible_operator_meta.name }}-event-stream' 28 | port: 29 | number: 8000 30 | {% if hostname %} 31 | host: {{ hostname }} 32 | {% endif %} 33 | {% if ingress_tls_secret %} 34 | tls: 35 | - hosts: 36 | - {{ hostname }} 37 | secretName: {{ ingress_tls_secret }} 38 | {% endif %} 39 | {% endif %} 40 | 41 | {% if ingress_type|lower == "route" %} 42 | --- 43 | {% if route_api_version is defined %} 44 | apiVersion: '{{ route_api_version }}' 45 | {% endif %} 46 | kind: Route 47 | metadata: 48 | name: '{{ ansible_operator_meta.name }}-event-stream' 49 | namespace: '{{ ansible_operator_meta.namespace }}' 50 | labels: 51 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 52 | spec: 53 | {% if route_host != '' %} 54 | host: {{ route_host }} 55 | {% endif %} 56 | port: 57 | targetPort: '{{ (route_tls_termination_mechanism | lower == "passthrough") | ternary("https", "http") }}' 58 | tls: 59 | insecureEdgeTerminationPolicy: Redirect 60 | termination: {{ route_tls_termination_mechanism | lower }} 61 | {% if route_tls_termination_mechanism | lower == 'edge' and route_tls_secret != '' %} 62 | key: |- 63 | {{ route_tls_key | indent(width=6, first=True) }} 64 | certificate: |- 65 | {{ route_tls_crt | indent(width=6, first=True) }} 66 | {% if route_ca_crt is defined %} 67 | caCertificate: |- 68 | {{ route_ca_crt | indent(width=6, first=True) }} 69 | {% endif %} 70 | {% endif %} 71 | to: 72 | kind: Service 73 | name: {{ ansible_operator_meta.name }}-event-stream 74 | weight: 100 75 | wildcardPolicy: None 76 | {% endif %} 77 | -------------------------------------------------------------------------------- /roles/eda/templates/eda-ui.ingress.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if ingress_type|lower == "ingress" %} 2 | --- 3 | {% if ingress_api_version is defined %} 4 | apiVersion: '{{ ingress_api_version }}' 5 | {% endif %} 6 | kind: Ingress 7 | metadata: 8 | name: '{{ ansible_operator_meta.name }}-ui-ingress' 9 | namespace: '{{ ansible_operator_meta.namespace }}' 10 | labels: 11 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 12 | {% if ingress_annotations %} 13 | annotations: 14 | {{ ingress_annotations | indent(width=4) }} 15 | {% endif %} 16 | spec: 17 | {% if ingress_class_name %} 18 | ingressClassName: '{{ ingress_class_name }}' 19 | {% endif %} 20 | rules: 21 | - http: 22 | paths: 23 | - path: '{{ ingress_path }}' 24 | pathType: '{{ ingress_path_type }}' 25 | backend: 26 | service: 27 | name: '{{ ansible_operator_meta.name }}-ui' 28 | port: 29 | number: 80 30 | - path: '/' 31 | pathType: '{{ ingress_path_type }}' 32 | backend: 33 | service: 34 | name: '{{ ansible_operator_meta.name }}-event-stream' 35 | port: 36 | number: 8000 37 | {% if hostname %} 38 | host: {{ hostname }} 39 | {% endif %} 40 | {% if ingress_tls_secret %} 41 | tls: 42 | - hosts: 43 | - {{ hostname }} 44 | secretName: {{ ingress_tls_secret }} 45 | {% endif %} 46 | {% endif %} 47 | 48 | {% if ingress_type|lower == "route" %} 49 | --- 50 | {% if route_api_version is defined %} 51 | apiVersion: '{{ route_api_version }}' 52 | {% endif %} 53 | kind: Route 54 | metadata: 55 | name: '{{ ansible_operator_meta.name }}-ui' 56 | namespace: '{{ ansible_operator_meta.namespace }}' 57 | labels: 58 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 59 | spec: 60 | {% if route_host != '' %} 61 | host: {{ route_host }} 62 | {% endif %} 63 | port: 64 | targetPort: '{{ (route_tls_termination_mechanism | lower == "passthrough") | ternary("https", "http") }}' 65 | tls: 66 | insecureEdgeTerminationPolicy: Redirect 67 | termination: {{ route_tls_termination_mechanism | lower }} 68 | {% if route_tls_termination_mechanism | lower == 'edge' and route_tls_secret != '' %} 69 | key: |- 70 | {{ route_tls_key | indent(width=6, first=True) }} 71 | certificate: |- 72 | {{ route_tls_crt | indent(width=6, first=True) }} 73 | {% if route_ca_crt is defined %} 74 | caCertificate: |- 75 | {{ route_ca_crt | indent(width=6, first=True) }} 76 | {% endif %} 77 | {% endif %} 78 | to: 79 | kind: Service 80 | name: {{ ansible_operator_meta.name }}-ui 81 | weight: 100 82 | wildcardPolicy: None 83 | {% endif %} 84 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/deploying-a-specific-version.md: -------------------------------------------------------------------------------- 1 | ## Deploying a specific version of EDA 2 | 3 | There are a few variables that are customizable for eda the image management. 4 | 5 | | Name | Description | Default | 6 | | ---------------------- | ------------------------- | -------------------------------------- | 7 | | image | Path of the image to pull | quay.io/ansible/eda-server | 8 | | image_version | Image version to pull | value of DEFAULT_EDA_VERSION or main | 9 | | image_web | Path of the image to pull | quay.io/ansible/eda-ui | 10 | | image_web_version | Image version to pull | value of DEFAULT_EDA_UI_VERSION or main | 11 | | image_pull_policy | The pull policy to adopt | IfNotPresent | 12 | | image_pull_secrets | The pull secrets to use | None | 13 | | redis_image | Path of the image to pull | redis | 14 | | redis_image_version | Image version to pull | c9s | 15 | | postgres_image | Path of the image to pull | postgres | 16 | | postgres_image_version | Image version to pull | latest | 17 | 18 | Example of customization could be: 19 | 20 | ```yaml 21 | --- 22 | spec: 23 | ... 24 | image: myorg/my-custom-eda 25 | image_version: main 26 | image_web: myorg/my-custom-eda 27 | image_web_version: main 28 | image_pull_policy: Always 29 | image_pull_secrets: 30 | - pull_secret_name 31 | ``` 32 | 33 | > **Note**: The `image` and `image_version` style variables are intended for local mirroring scenarios. Please note that using a version of EDA other than the one bundled with the `eda-server-operator` is **not** supported even though it will likely work and can be useful for pinning a version. For the default values, check the [main.yml](https://github.com/ansible/eda-server-operator/blob/main/roles/eda/defaults/main.yml) file. 34 | 35 | 36 | ### Configuring an image pull secret 37 | 38 | 1. Log in with that token, or username/password, then create a pull secret from the docker/config.json 39 | 40 | ```bash 41 | docker login quay.io -u -p 42 | ``` 43 | 44 | 2. Then, create a k8s secret from your .docker/config.json file. This pull secret should be created in the same namespace you are installing the EDA Operator. 45 | 46 | ```bash 47 | kubectl create secret generic redhat-operators-pull-secret \ 48 | --from-file=.dockerconfigjson=.docker/config.json \ 49 | --type=kubernetes.io/dockerconfigjson 50 | ``` 51 | 52 | 3. Add that image pull secret to your EDA spec 53 | 54 | ```yaml 55 | --- 56 | spec: 57 | image_pull_secrets: 58 | - redhat-operators-pull-secret 59 | ``` 60 | -------------------------------------------------------------------------------- /roles/eda/templates/eda-api.configmap.yaml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: '{{ ansible_operator_meta.name }}-nginx-api-configmap' 5 | namespace: '{{ ansible_operator_meta.namespace }}' 6 | data: 7 | nginx_api_default_conf_template: | 8 | events { 9 | worker_connections 1024; 10 | } 11 | http { 12 | map $http_x_forwarded_proto $remote_scheme { 13 | default $http_x_forwarded_proto; 14 | '' $scheme; 15 | } 16 | 17 | include mime.types; 18 | types { 19 | application/manifest+json webmanifest; 20 | } 21 | 22 | # Define the upstream gunicorn server 23 | upstream gunicorn { 24 | server 127.0.0.1:{{ api_django_port }}; 25 | } 26 | 27 | server { 28 | listen {{ api_nginx_port }}; 29 | {% if not ipv6_disabled | bool %} 30 | listen [::]:{{ api_nginx_port }}; 31 | {% endif %} 32 | location ^~ /api/eda/static/ { 33 | alias {{ static_path }}/; 34 | access_log /var/log/nginx/static_access.log; 35 | error_log /var/log/nginx/static_error.log; 36 | } 37 | location ~ ^/api/eda { 38 | proxy_pass http://gunicorn; # Forward requests to Django app 39 | proxy_set_header Host $http_host; 40 | proxy_set_header X-Forwarded-Host $host; 41 | proxy_set_header X-Forwarded-Proto $scheme; 42 | proxy_set_header X-Forwarded-Port $server_port; 43 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 44 | # Return 503 Service Unavailable with JSON response if gunicorn fails to respond 45 | proxy_read_timeout {{ eda_nginx_read_timeout }}s; 46 | error_page 504 =503 /json_503; 47 | error_page 502 =503 /json_503; # Optional, in case gunicorn is completely down 48 | } 49 | location / { 50 | root {{ static_path }}; 51 | try_files /index.html =404; 52 | access_log /var/log/nginx/proxy_access.log; 53 | error_log /var/log/nginx/proxy_error.log; 54 | } 55 | location = /json_503 { 56 | # Custom JSON response for 503 Service Unavailable 57 | internal; 58 | add_header Content-Type application/json; 59 | 60 | # Check if X-Request-ID is set and include it in the response 61 | if ($http_x_request_id) { 62 | return 503 '{"status": "error", "message": "Service Unavailable", "code": 503, "request_id": "$http_x_request_id"}'; 63 | } 64 | 65 | # If X-Request-ID is not set, just return the basic JSON response 66 | return 503 '{"status": "error", "message": "Service Unavailable", "code": 503}'; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /roles/eda/tasks/idle_deployment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Scale down EDA Deployments 3 | kubernetes.core.k8s: 4 | state: present 5 | definition: 6 | apiVersion: apps/v1 7 | kind: Deployment 8 | metadata: 9 | name: "{{ item }}" 10 | namespace: "{{ ansible_operator_meta.namespace }}" 11 | spec: 12 | replicas: 0 13 | loop: 14 | - '{{ ansible_operator_meta.name }}-activation-worker' 15 | - '{{ api_server_name }}' 16 | - '{{ ansible_operator_meta.name }}-default-worker' 17 | - '{{ event_stream_server_name }}' 18 | - '{{ ansible_operator_meta.name }}-scheduler' 19 | 20 | - name: Scale down EDA ui if enabled 21 | kubernetes.core.k8s: 22 | state: present 23 | definition: 24 | apiVersion: apps/v1 25 | kind: Deployment 26 | metadata: 27 | name: "{{ ansible_operator_meta.name }}-ui" 28 | namespace: "{{ ansible_operator_meta.namespace }}" 29 | spec: 30 | replicas: 0 31 | when: not ui_disabled 32 | 33 | - name: Check for an external Redis cache 34 | ansible.builtin.import_role: 35 | name: redis 36 | tasks_from: check_external_config 37 | when: redis_type | length == 0 or redis_type == 'unmanaged' 38 | 39 | - name: Check for the default Redis configuration 40 | ansible.builtin.import_role: 41 | name: redis 42 | tasks_from: check_default_config 43 | when: redis_type | length == 0 or redis_type == 'managed' 44 | 45 | - name: Create a default Redis configuration 46 | ansible.builtin.import_role: 47 | name: redis 48 | tasks_from: create_default_config 49 | when: redis_type | length == 0 50 | 51 | - name: Scale down Redis Deployment 52 | kubernetes.core.k8s: 53 | state: present 54 | definition: 55 | apiVersion: apps/v1 56 | kind: Deployment 57 | metadata: 58 | name: "{{ ansible_operator_meta.name }}-redis" 59 | namespace: "{{ ansible_operator_meta.namespace }}" 60 | spec: 61 | replicas: 0 62 | when: redis_type == "managed" 63 | 64 | - name: Combine postgres default and custom vars for each component 65 | ansible.builtin.import_role: 66 | name: postgres 67 | tasks_from: combine_defaults 68 | 69 | - name: Determine and set postgres configuration secret and variables 70 | ansible.builtin.import_role: 71 | name: postgres 72 | tasks_from: set_configuration_secret 73 | 74 | - name: Set variables to be used in Postgres templates 75 | ansible.builtin.import_role: 76 | name: postgres 77 | tasks_from: set_variables 78 | 79 | - name: Scale down PostgreSQL Statefulset 80 | kubernetes.core.k8s: 81 | state: present 82 | definition: 83 | apiVersion: apps/v1 84 | kind: StatefulSet 85 | metadata: 86 | name: "{{ ansible_operator_meta.name }}-postgres-{{ supported_pg_version }}" 87 | namespace: "{{ ansible_operator_meta.namespace }}" 88 | spec: 89 | replicas: 0 90 | when: managed_database 91 | 92 | - name: End Playbook 93 | ansible.builtin.meta: end_play 94 | -------------------------------------------------------------------------------- /roles/eda/tasks/update_status.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Update custom resource status 3 | operator_sdk.util.k8s_status: 4 | api_version: '{{ api_version }}' 5 | kind: "{{ kind }}" 6 | name: "{{ ansible_operator_meta.name }}" 7 | namespace: "{{ ansible_operator_meta.namespace }}" 8 | status: 9 | adminPasswordSecret: "{{ __admin_password_secret['resources'][0]['metadata']['name'] }}" 10 | adminUser: "{{ admin_user }}" 11 | databaseConfigurationSecret: "{{ pg_config['resources'][0]['metadata']['name'] }}" 12 | dbFieldsEncryptionSecret: "{{ db_fields_encryption_secret_name }}" 13 | image: "{{ _image }}" 14 | 15 | - block: 16 | - name: Retrieve instance version 17 | k8s_exec: 18 | namespace: "{{ ansible_operator_meta.namespace }}" 19 | pod: "{{ eda_api_pod_name }}" 20 | container: "eda-api" 21 | command: >- 22 | bash -c "echo 'import pkg_resources; 23 | print(pkg_resources.get_distribution(\"aap-eda\").version)' 24 | | aap-eda-manage shell" 25 | register: instance_version 26 | changed_when: false 27 | 28 | - name: Update version status 29 | operator_sdk.util.k8s_status: 30 | api_version: '{{ api_version }}' 31 | kind: "{{ kind }}" 32 | name: "{{ ansible_operator_meta.name }}" 33 | namespace: "{{ ansible_operator_meta.namespace }}" 34 | status: 35 | version: "{{ instance_version.stdout | trim }}" 36 | when: eda_api_pod_name | length 37 | 38 | - block: 39 | - name: Retrieve route URL 40 | k8s_info: 41 | api_version: 'route.openshift.io/v1' 42 | kind: Route 43 | namespace: '{{ ansible_operator_meta.namespace }}' 44 | name: "{{ ansible_operator_meta.name }}{{ '' if ui_disabled else '-ui' }}" 45 | register: route_url 46 | 47 | - name: Update URL status 48 | operator_sdk.util.k8s_status: 49 | api_version: '{{ api_version }}' 50 | kind: "{{ kind }}" 51 | name: "{{ ansible_operator_meta.name }}" 52 | namespace: "{{ ansible_operator_meta.namespace }}" 53 | status: 54 | URL: "https://{{ route_url['resources'][0]['status']['ingress'][0]['host'] }}" 55 | when: ingress_type | lower == 'route' 56 | 57 | - name: Update upgradedPostgresVersion status 58 | operator_sdk.util.k8s_status: 59 | api_version: '{{ api_version }}' 60 | kind: "{{ kind }}" 61 | name: "{{ ansible_operator_meta.name }}" 62 | namespace: "{{ ansible_operator_meta.namespace }}" 63 | status: 64 | upgradedPostgresVersion: "{{ upgraded_postgres_version | string }}" 65 | when: upgraded_postgres_version is defined 66 | 67 | - name: Update migratedFromSecret status 68 | operator_sdk.util.k8s_status: 69 | api_version: '{{ api_version }}' 70 | kind: "{{ kind }}" 71 | name: "{{ ansible_operator_meta.name }}" 72 | namespace: "{{ ansible_operator_meta.namespace }}" 73 | status: 74 | migratedFromSecret: "{{ eda_migrated_from_secret }}" 75 | when: eda_migrated_from_secret is defined 76 | -------------------------------------------------------------------------------- /docs/minikube-test-cluster.md: -------------------------------------------------------------------------------- 1 | # Creating a minikube cluster for testing 2 | 3 | If you do not have an existing cluster, the `eda-server-operator` can be deployed on a [Minikube](https://minikube.sigs.k8s.io/docs/) cluster for testing purposes. Due to different OS and hardware environments, please refer to the official Minikube documentation for further information. 4 | 5 | ``` 6 | $ minikube start --cpus=4 --memory=6g --addons=ingress 7 | 😄 minikube v1.23.2 on Fedora 34 8 | ✨ Using the docker driver based on existing profile 9 | 👍 Starting control plane node minikube in cluster minikube 10 | 🚜 Pulling base image ... 11 | 🏃 Updating the running docker "minikube" container ... 12 | 🐳 Preparing Kubernetes v1.22.2 on Docker 20.10.8 ... 13 | 🔎 Verifying Kubernetes components... 14 | ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5 15 | ▪ Using image k8s.gcr.io/ingress-nginx/controller:v1.0.0-beta.3 16 | ▪ Using image k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.0 17 | ▪ Using image k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.0 18 | 🔎 Verifying ingress addon... 19 | 🌟 Enabled addons: storage-provisioner, default-storageclass, ingress 20 | 🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default 21 | ``` 22 | 23 | Once Minikube is deployed, check if the node(s) and `kube-apiserver` communication is working as expected. 24 | 25 | ``` 26 | $ minikube kubectl -- get nodes 27 | NAME STATUS ROLES AGE VERSION 28 | minikube Ready control-plane,master 113s v1.22.2 29 | 30 | $ minikube kubectl -- get pods -A 31 | NAMESPACE NAME READY STATUS RESTARTS AGE 32 | ingress-nginx ingress-nginx-admission-create--1-kk67h 0/1 Completed 0 2m1s 33 | ingress-nginx ingress-nginx-admission-patch--1-7mp2r 0/1 Completed 1 2m1s 34 | ingress-nginx ingress-nginx-controller-69bdbc4d57-bmwg8 1/1 Running 0 2m 35 | kube-system coredns-78fcd69978-q7nmx 1/1 Running 0 2m 36 | kube-system etcd-minikube 1/1 Running 0 2m12s 37 | kube-system kube-apiserver-minikube 1/1 Running 0 2m16s 38 | kube-system kube-controller-manager-minikube 1/1 Running 0 2m12s 39 | kube-system kube-proxy-5mmnw 1/1 Running 0 2m1s 40 | kube-system kube-scheduler-minikube 1/1 Running 0 2m15s 41 | kube-system storage-provisioner 1/1 Running 0 2m11s 42 | ``` 43 | 44 | It is not required for `kubectl` to be separately installed since it comes already wrapped inside minikube. As demonstrated above, simply prefix `minikube kubectl --` before kubectl command, i.e. `kubectl get nodes` would become `minikube kubectl -- get nodes` 45 | 46 | Let's create an alias for easier usage: 47 | 48 | ``` 49 | $ alias kubectl="minikube kubectl --" 50 | ``` 51 | -------------------------------------------------------------------------------- /roles/redis/templates/redis.deployment.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: "{{ ansible_operator_meta.name }}-redis" 6 | namespace: "{{ ansible_operator_meta.namespace }}" 7 | labels: 8 | app.kubernetes.io/name: 'redis' 9 | app.kubernetes.io/instance: 'redis-{{ ansible_operator_meta.name }}' 10 | app.kubernetes.io/component: cache 11 | app.kubernetes.io/part-of: '{{ deployment_type }}' 12 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 13 | spec: 14 | replicas: {{ combined_redis.replicas }} 15 | selector: 16 | matchLabels: 17 | app.kubernetes.io/name: 'redis' 18 | app.kubernetes.io/instance: 'redis-{{ ansible_operator_meta.name }}' 19 | app.kubernetes.io/component: cache 20 | app.kubernetes.io/part-of: '{{ deployment_type }}' 21 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 22 | template: 23 | metadata: 24 | labels: 25 | app.kubernetes.io/name: 'redis' 26 | app.kubernetes.io/instance: 'redis-{{ ansible_operator_meta.name }}' 27 | app.kubernetes.io/component: cache 28 | app.kubernetes.io/part-of: '{{ deployment_type }}' 29 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 30 | spec: 31 | {% if image_pull_secrets | length > 0 %} 32 | imagePullSecrets: 33 | {% for secret in image_pull_secrets %} 34 | - name: {{ secret }} 35 | {% endfor %} 36 | {% endif %} 37 | {% if combined_redis.node_selector %} 38 | nodeSelector: 39 | {{ combined_redis.node_selector | to_nice_yaml | indent(width=8) }} 40 | {% endif %} 41 | {% if combined_redis.tolerations %} 42 | tolerations: 43 | {{ combined_redis.tolerations | to_nice_yaml | indent(width=8) }} 44 | {% endif %} 45 | containers: 46 | - name: redis 47 | image: "{{ _redis_image }}" 48 | imagePullPolicy: "IfNotPresent" 49 | volumeMounts: 50 | - readOnly: false 51 | mountPath: /var/lib/redis/data 52 | name: '{{ ansible_operator_meta.name }}-redis-data' 53 | ports: 54 | - protocol: TCP 55 | containerPort: 6379 56 | livenessProbe: 57 | enabled: true 58 | initialDelaySeconds: 5 59 | periodSeconds: 5 60 | timeoutSeconds: 5 61 | failureThreshold: 5 62 | successThreshold: 1 63 | exec: 64 | command: 65 | - /bin/sh 66 | - -i 67 | - -c 68 | - redis-cli -h 127.0.0.1 -p 6379 69 | readinessProbe: 70 | enabled: true 71 | initialDelaySeconds: 5 72 | periodSeconds: 5 73 | timeoutSeconds: 5 74 | failureThreshold: 5 75 | successThreshold: 1 76 | exec: 77 | command: 78 | - /bin/sh 79 | - -i 80 | - -c 81 | - redis-cli -h 127.0.0.1 -p 6379 82 | {% if combined_redis.resource_requirements is defined %} 83 | resources: {{ combined_redis.resource_requirements }} 84 | {% endif %} 85 | volumes: 86 | - name: {{ ansible_operator_meta.name }}-redis-data 87 | emptyDir: {} 88 | -------------------------------------------------------------------------------- /roles/backup/tasks/init.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Delete any existing management pod 3 | k8s: 4 | name: "{{ ansible_operator_meta.name }}-db-management" 5 | kind: Pod 6 | namespace: "{{ backup_pvc_namespace }}" 7 | state: absent 8 | force: true 9 | wait: true 10 | 11 | # Check to make sure provided pvc exists, error loudly if not. Otherwise, the management pod will just stay in pending state forever. 12 | - name: Check provided PVC exists 13 | k8s_info: 14 | name: "{{ backup_pvc }}" 15 | kind: PersistentVolumeClaim 16 | namespace: "{{ backup_pvc_namespace }}" 17 | register: provided_pvc 18 | when: 19 | - backup_pvc != '' 20 | 21 | - name: Surface error to user 22 | block: 23 | - name: Set error message 24 | set_fact: 25 | error_msg: "{{ backup_pvc }} does not exist, please create this pvc first." 26 | 27 | - name: Handle error 28 | import_tasks: error_handling.yml 29 | 30 | - name: Fail early if pvc is defined but does not exist 31 | fail: 32 | msg: "{{ backup_pvc }} does not exist, please create this pvc first." 33 | when: 34 | - backup_pvc != '' 35 | - provided_pvc.resources | length == 0 36 | 37 | # If backup_pvc is defined, use in management-pod.yml.j2 38 | - name: Set default pvc name 39 | set_fact: 40 | _default_backup_pvc: "{{ deployment_name }}-backup-claim" 41 | 42 | # by default, it will re-use the old pvc if already created (unless a pvc is provided) 43 | - name: Set PVC to use for backup 44 | set_fact: 45 | backup_claim: "{{ backup_pvc | default(_default_backup_pvc, true) }}" 46 | 47 | - block: 48 | - name: Create PVC for backup 49 | k8s: 50 | kind: PersistentVolumeClaim 51 | definition: "{{ lookup('template', 'backup_pvc.yml.j2') }}" 52 | 53 | - name: Remove PVC ownerReference 54 | k8s: 55 | definition: 56 | apiVersion: v1 57 | kind: PersistentVolumeClaim 58 | metadata: 59 | name: "{{ deployment_name }}-backup-claim" 60 | namespace: "{{ backup_pvc_namespace }}" 61 | ownerReferences: null 62 | when: 63 | - backup_pvc == '' or backup_pvc is not defined 64 | 65 | - name: Set default postgres image 66 | set_fact: 67 | _default_postgres_image: "{{ _postgres_image }}:{{_postgres_image_version }}" 68 | 69 | - name: Set user provided postgres image 70 | set_fact: 71 | _custom_postgres_image: "{{ postgres_image }}:{{ postgres_image_version }}" 72 | when: 73 | - postgres_image | default([]) | length 74 | - postgres_image_version is defined and postgres_image_version != '' 75 | 76 | - name: Set Postgres image URL 77 | set_fact: 78 | _postgres_image: "{{ _custom_postgres_image | default(lookup('env', 'RELATED_IMAGE_EDA_POSTGRES')) | default(_default_postgres_image, true) }}" 79 | 80 | - name: Create management pod from templated deployment config 81 | k8s: 82 | name: "{{ ansible_operator_meta.name }}-db-management" 83 | kind: Deployment 84 | state: present 85 | definition: "{{ lookup('template', 'management-pod.yml.j2') }}" 86 | wait: true 87 | 88 | - name: Look up details for this deployment 89 | k8s_info: 90 | api_version: "{{ api_version }}" 91 | kind: "EDA" 92 | name: "{{ deployment_name }}" 93 | namespace: "{{ ansible_operator_meta.namespace }}" 94 | register: this_eda 95 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: eda-manager-role 6 | rules: 7 | ## 8 | ## Base operator rules 9 | ## 10 | - apiGroups: 11 | - "" 12 | resources: 13 | - secrets 14 | - pods 15 | - pods/exec 16 | - pods/log 17 | verbs: 18 | - create 19 | - delete 20 | - get 21 | - list 22 | - patch 23 | - update 24 | - watch 25 | - apiGroups: 26 | - apps 27 | resources: 28 | - deployments 29 | - daemonsets 30 | - replicasets 31 | - statefulsets 32 | verbs: 33 | - create 34 | - delete 35 | - get 36 | - list 37 | - patch 38 | - update 39 | - watch 40 | ## 41 | ## Rules for EDA Route 42 | ## 43 | - apiGroups: 44 | - route.openshift.io 45 | resources: 46 | - routes 47 | - routes/custom-host 48 | verbs: 49 | - get 50 | - list 51 | - create 52 | - delete 53 | - patch 54 | - update 55 | - watch 56 | ## 57 | ## Rules for EDA role 58 | ## 59 | - apiGroups: 60 | - "" 61 | - "rbac.authorization.k8s.io" 62 | resources: 63 | - pods 64 | - services 65 | - services/finalizers 66 | - serviceaccounts 67 | - endpoints 68 | - persistentvolumeclaims 69 | - events 70 | - configmaps 71 | - secrets 72 | - roles 73 | - rolebindings 74 | verbs: 75 | - get 76 | - list 77 | - create 78 | - delete 79 | - patch 80 | - update 81 | - watch 82 | - apiGroups: 83 | - apps 84 | - networking.k8s.io 85 | resources: 86 | - deployments 87 | - daemonsets 88 | - replicasets 89 | - statefulsets 90 | - ingresses 91 | verbs: 92 | - get 93 | - list 94 | - create 95 | - delete 96 | - patch 97 | - update 98 | - watch 99 | - apiGroups: 100 | - apps 101 | resources: 102 | - deployments/scale 103 | verbs: 104 | - patch 105 | - apiGroups: 106 | - "" 107 | resources: 108 | - pods/exec 109 | - pods/attach 110 | - pods/log # log & attach rules needed to be able to grant them to EDA service account 111 | verbs: 112 | - create 113 | - get 114 | 115 | ## Rule for Operator SA so it can grant them to worker deployment SA 116 | - apiGroups: 117 | - "" 118 | - batch 119 | - extensions 120 | resources: 121 | - jobs 122 | verbs: 123 | - get 124 | - list 125 | - watch 126 | - create 127 | - update 128 | - patch 129 | - delete 130 | 131 | ## 132 | ## Rules for eda.ansible.com/v1alpha1, Kind: EDA 133 | ## 134 | - apiGroups: 135 | - eda.ansible.com 136 | resources: 137 | - edas 138 | - edas/status 139 | - edas/finalizers 140 | - edabackups 141 | - edabackups/status 142 | - edabackups/finalizers 143 | - edarestores 144 | - edarestores/status 145 | - edarestores/finalizers 146 | verbs: 147 | - create 148 | - delete 149 | - get 150 | - list 151 | - patch 152 | - update 153 | - watch 154 | #+kubebuilder:scaffold:rules 155 | -------------------------------------------------------------------------------- /docs/upgrade/upgrading.md: -------------------------------------------------------------------------------- 1 | ### Upgrading 2 | 3 | Before upgrading, please review the changelog for any breaking or notable changes in the releases between your current version and the one you are upgrading to. These changes can be found on the [Release page](https://github.com/ansible/eda-server-operator/releases). 4 | 5 | 6 | All operator versions pin to the most recent eda-server and ui image version at the time of the operator release by default. This is so that the application version only changes when you upgrade your operator (unless overriden). 7 | 8 | To find the version of eda-server that will be installed by the operator, check the version specified in the `DEFAULT_EDA_VERSION` and `DEFAULT_EDA_UI_VERSION` variable for that particular release. You can do so by running the following command 9 | 10 | ```shell 11 | EDA_OPERATOR_VERSION=1.0.1 12 | docker run --entrypoint="" quay.io/ansible/eda-server-operator:$EDA_OPERATOR_VERSION bash -c "env | egrep "DEFAULT_EDA_VERSION|DEFAULT_EDA_UI_VERSION" 13 | ``` 14 | 15 | Follow the installation instructions ('make deploy', 'kustomization', etc.) using the new release version to apply the new operator yaml and upgrade the operator. This will in turn also upgrade your EDA deployment. 16 | 17 | For example, if you installed with kustomize, you could modify the version in your kustomization.yaml file from 1.0.0 to 1.0.1, then apply it. 18 | 19 | ```yaml 20 | apiVersion: kustomize.config.k8s.io/v1beta1 21 | kind: Kustomization 22 | resources: 23 | - config/default 24 | 25 | # Set the image tags to match the git version from above 26 | images: 27 | - name: quay.io/ansible/eda-server-operator 28 | newTag: 1.0.1 29 | 30 | # Specify a custom namespace in which to install EDA 31 | namespace: eda 32 | ``` 33 | 34 | Then run this to apply it: 35 | 36 | ``` 37 | kustomize build . | kubectl apply -f - 38 | ``` 39 | 40 | #### Backup 41 | 42 | The first part of any upgrade should be a backup. Note, there are secrets in the pod which work in conjunction with the database. Having just a database backup without the required secrets will not be sufficient for recovering from an issue when upgrading to a new version. See the [backup role documentation](./roles/backup/README.md) for information on how to backup your database and secrets. 43 | 44 | In the event you need to recover the backup see the [restore role documentation](./roles/restore/README.md). *Before Restoring from a backup*, be sure to: 45 | * delete the old existing EDA CR 46 | * delete the persistent volume claim (PVC) for the database from the old deployment, which has a name like `postgres-15--postgres-15-0` 47 | 48 | **Note**: Do not delete the namespace/project, as that will delete the backup and the backup's PVC as well. 49 | 50 | 51 | #### PostgreSQL Upgrade Considerations 52 | 53 | If there is a PostgreSQL major version upgrade, after the data directory on the PVC is migrated to the new version, the old PVC is kept by default. 54 | This provides the ability to roll back if needed, but can take up extra storage space in your cluster unnecessarily. By default, the postgres pvc from the previous version will remain unless you manually remove it, or have the `database.postgres_keep_pvc_after_upgrade` parameter set to false. You can configure it to be deleted automatically 55 | after a successful upgrade by setting the following variable on the EDA spec. 56 | 57 | ```yaml 58 | spec: 59 | database: 60 | postgres_keep_pvc_after_upgrade: false 61 | ``` 62 | -------------------------------------------------------------------------------- /roles/eda/templates/redirect-page.configmap.html.j2: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ ansible_operator_meta.name }}-redirect-page 5 | namespace: "{{ ansible_operator_meta.namespace }}" 6 | data: 7 | redirect-page.html: | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Redirecting to Ansible Automation Platform 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 58 | 59 | 60 | 61 | 64 | 65 |

Redirecting to Ansible Automation Platform...

66 |

If you are not redirected automatically, click here to go to AAP.

67 |

68 | The API endpoints for this platform service will temporarily remain available at the URL for this service. 69 | Please use the Ansible Automation Platform API endpoints corresponding to this component in the future. 70 | These can be found at {{ public_base_url }}/api/eda. 71 |

72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /docs/single-command-install.md: -------------------------------------------------------------------------------- 1 | # Single-Command Installation Guide 2 | 3 | This document provides comprehensive instructions for the quick, single-command installation of the EDA Server Operator. Also covered are additional details such as prerequisites, uninstallation, and troubleshooting tips. 4 | 5 | ## Prerequisites 6 | Before proceeding with the installation, ensure that the following prerequisites are met: 7 | 8 | 1. **Kubernetes Cluster**: You need an active Kubernetes cluster. If you do not have one, you can set it up using platforms like Minikube, Kind, or a cloud provider like AWS, Azure, or GCP. 9 | 10 | 2. **kubectl**: The Kubernetes command-line tool, kubectl, should be installed and configured to communicate with your cluster. You can check its availability by running `kubectl version`. 11 | 12 | ## Installation 13 | The EDA Server Operator can be installed using a single command. This command applies a YAML file from the EDA Server Operator's GitHub repository directly to your Kubernetes cluster. 14 | 15 | Run the following command in your terminal to install the latest operator 16 | 17 | ```bash 18 | kubectl apply -f https://github.com/ansible/eda-server-operator/releases/latest/download/operator.yaml 19 | ``` 20 | 21 | If you want to install a specific version instead, modify the version to whichever version you want to install. For example: 22 | 23 | ```bash 24 | kubectl apply -f https://github.com/ansible/eda-server-operator/releases/download/1.0.0/operator.yaml 25 | ``` 26 | 27 | > [!Note] 28 | > This will create the EDA Server Operator resources in the eda-server-operator-system namespace. 29 | 30 | Now create your EDA custom resource by applying the `eda-demo.yml` file and you will soon have a working EDA instance! 31 | 32 | ```yaml 33 | # eda-demo.yaml 34 | apiVersion: eda.ansible.com/v1alpha1 35 | kind: EDA 36 | metadata: 37 | name: my-eda 38 | spec: 39 | automation_server_url: https://awx-host 40 | ``` 41 | 42 | ```bash 43 | kubectl apply -f eda-demo.yaml 44 | ``` 45 | 46 | See the [README.md](../README.md) for more information on configuring EDA by modifying the `spec`. 47 | 48 | ## Upgrading 49 | 50 | ## Pre-Upgrade Checklist 51 | 52 | * **Backup**: Backup your EDA instance by creating an EDABackup. 53 | * **Review Release Notes**: Check the release notes for the new version of the EDA Server Operator. This can be found on the GitHub [releases page](https://github.com/ansible/eda-server-operator/releases). Pay attention to any breaking changes, new features, or specific instructions for upgrading from your current version. 54 | 55 | ### Upgrade the Operator 56 | 57 | Check the [Releases Page](https://github.com/ansible/eda-server-operator/releases) for the latest EDA Server Operator verion. Copy the URL to the `operator.yaml` artifact for it, then apply it. 58 | 59 | For example, if upgrading to version 1.1.0, the command would be: 60 | 61 | ```bash 62 | kubectl apply -f https://github.com/ansible/eda-server-operator/releases/download/1.1.0/operator.yaml 63 | `````` 64 | 65 | Monitor the upgrade process by checking the status of the pods in the eda-server-operator-system namespace. You can use the following command: 66 | 67 | ```bash 68 | kubectl get pods -n eda-server-operator-system 69 | ``` 70 | 71 | 72 | ## Cleanup 73 | If you wish to remove the EDA Server Operator from your Kubernetes cluster, follow these steps: 74 | 75 | Run the following command: 76 | 77 | ```bash 78 | kubectl delete -f https://github.com/ansible/eda-server-operator/releases/download/1.0.0/operator.yaml 79 | ``` 80 | 81 | -------------------------------------------------------------------------------- /roles/eda/tasks/migrate_data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Set actual old postgres configuration secret name 4 | set_fact: 5 | old_postgres_configuration_name: "{{ old_pg_config['resources'][0]['metadata']['name'] }}" 6 | 7 | - name: Store Database Configuration 8 | set_fact: 9 | eda_old_postgres_user: "{{ old_pg_config['resources'][0]['data']['username'] | b64decode }}" 10 | eda_old_postgres_pass: "{{ old_pg_config['resources'][0]['data']['password'] | b64decode }}" 11 | eda_old_postgres_database: "{{ old_pg_config['resources'][0]['data']['database'] | b64decode }}" 12 | eda_old_postgres_port: "{{ old_pg_config['resources'][0]['data']['port'] | b64decode }}" 13 | eda_old_postgres_host: "{{ old_pg_config['resources'][0]['data']['host'] | b64decode }}" 14 | no_log: "{{ no_log }}" 15 | 16 | - name: Set Default label selector for custom resource generated postgres 17 | set_fact: 18 | postgres_label_selector: "app.kubernetes.io/instance=postgres-{{ supported_pg_version }}-{{ ansible_operator_meta.name }}" 19 | when: postgres_label_selector is not defined 20 | 21 | - name: Get the postgres pod information 22 | k8s_info: 23 | kind: Pod 24 | namespace: "{{ ansible_operator_meta.namespace }}" 25 | label_selectors: 26 | - "{{ postgres_label_selector }}" 27 | field_selectors: 28 | - status.phase=Running 29 | register: postgres_pod 30 | 31 | - name: Set the resource pod name as a variable. 32 | set_fact: 33 | postgres_pod_name: "{{ postgres_pod['resources'][0]['metadata']['name'] }}" 34 | 35 | - name: Scale down Deployment for migration 36 | include_tasks: idle_deployment.yml 37 | 38 | - name: Set pg_dump command 39 | set_fact: 40 | pgdump: >- 41 | pg_dump 42 | -h {{ eda_old_postgres_host }} 43 | -U {{ eda_old_postgres_user }} 44 | -d {{ eda_old_postgres_database }} 45 | -p {{ eda_old_postgres_port }} 46 | -F custom 47 | {{ pg_dump_suffix }} 48 | no_log: "{{ no_log }}" 49 | 50 | - name: Set pg_restore command 51 | set_fact: 52 | pg_restore: >- 53 | pg_restore --clean --if-exists 54 | -U {{ database_username }} 55 | -d {{ database_name }} 56 | no_log: "{{ no_log }}" 57 | 58 | - name: Stream backup from pg_dump to the new postgresql container 59 | k8s_exec: 60 | namespace: "{{ ansible_operator_meta.namespace }}" 61 | pod: "{{ postgres_pod_name }}" 62 | command: | 63 | bash -c " 64 | function end_keepalive { 65 | rc=$? 66 | rm -f \"$1\" 67 | kill $(cat /proc/$2/task/$2/children 2>/dev/null) 2>/dev/null || true 68 | wait $2 || true 69 | exit $rc 70 | } 71 | keepalive_file=\"$(mktemp)\" 72 | while [[ -f \"$keepalive_file\" ]]; do 73 | echo 'Migrating data from old database...' 74 | sleep 60 75 | done & 76 | keepalive_pid=$! 77 | trap 'end_keepalive \"$keepalive_file\" \"$keepalive_pid\"' EXIT SIGINT SIGTERM 78 | echo keepalive_pid: $keepalive_pid 79 | set -e -o pipefail 80 | psql -c 'GRANT postgres TO {{ eda_postgres_user }}' 81 | PGPASSWORD=\"$PGPASSWORD_OLD\" {{ pgdump }} | PGPASSWORD=\"$POSTGRES_PASSWORD\" {{ pg_restore }} 82 | psql -c 'REVOKE postgres FROM {{ eda_postgres_user }}' 83 | set +e +o pipefail 84 | echo 'Successful' 85 | " 86 | no_log: "{{ no_log }}" 87 | register: data_migration 88 | 89 | - name: Set flag signifying that this instance has been migrated 90 | set_fact: 91 | eda_migrated_from_secret: "{{ old_postgres_configuration_name }}" 92 | -------------------------------------------------------------------------------- /roles/restore/tasks/secrets.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Get secret definition from pvc 4 | k8s_exec: 5 | namespace: "{{ backup_pvc_namespace }}" 6 | pod: "{{ ansible_operator_meta.name }}-db-management" 7 | command: >- 8 | bash -c "cat '{{ backup_dir }}/secrets.yml'" 9 | register: _secrets 10 | no_log: "{{ no_log }}" 11 | 12 | - name: Create Temporary secrets file 13 | tempfile: 14 | state: file 15 | suffix: .json 16 | register: tmp_secrets 17 | 18 | - name: Write vars to file locally 19 | copy: 20 | dest: "{{ tmp_secrets.path }}" 21 | content: "{{ _secrets.stdout }}" 22 | mode: 0640 23 | no_log: "{{ no_log }}" 24 | 25 | - name: Include secret vars from backup 26 | include_vars: "{{ tmp_secrets.path }}" 27 | no_log: "{{ no_log }}" 28 | 29 | - name: If deployment is managed, set the new postgres_configuration_secret name 30 | block: 31 | - name: Set new postgres_configuration_secret name 32 | set_fact: 33 | _generated_pg_secret_name: "{{ deployment_name }}-postgres-configuration" 34 | 35 | - name: Set tmp database dict 36 | set_fact: 37 | _tmp_database: "{{ spec['database'] | combine({'database_secret': _generated_pg_secret_name}, recursive=True) }}" 38 | no_log: "{{ no_log }}" 39 | 40 | - name: Override database_secret 41 | set_fact: 42 | spec: 43 | "{{ spec | combine({'database': _tmp_database}, recursive=True) }}" 44 | when: secrets['databaseConfigurationSecret']['data']['type'] | b64decode == 'managed' 45 | 46 | - name: If deployment is managed, set the database_host in the pg config secret 47 | block: 48 | - name: Set new database host 49 | set_fact: 50 | database_host: "{{ deployment_name }}-postgres-{{ supported_pg_version }}" 51 | no_log: "{{ no_log }}" 52 | 53 | - name: Set tmp postgres secret dict 54 | set_fact: 55 | _pg_secret: "{{ secrets['databaseConfigurationSecret'] }}" 56 | no_log: "{{ no_log }}" 57 | 58 | - name: Change postgres host and name value 59 | set_fact: 60 | _pg_data: "{{ _pg_secret['data'] | combine({'host': database_host | b64encode }) }}" 61 | _pg_secret_name: "{{ deployment_name }}-postgres-configuration" 62 | no_log: "{{ no_log }}" 63 | 64 | - name: Override postgres secret name 65 | set_fact: 66 | _pg_secret: "{{ _pg_secret | combine({'name': _pg_secret_name}) }}" 67 | no_log: "{{ no_log }}" 68 | 69 | - name: Override postgres secret host with new Postgres service 70 | set_fact: 71 | _pg_secret: "{{ _pg_secret | combine({'data': _pg_data}) }}" 72 | no_log: "{{ no_log }}" 73 | 74 | - name: Create a new dict of secrets with the new postgres secret 75 | set_fact: 76 | secrets: "{{ secrets | combine({'databaseConfigurationSecret': _pg_secret}) }}" 77 | no_log: "{{ no_log }}" 78 | when: secrets['databaseConfigurationSecret']['data']['type'] | b64decode == 'managed' 79 | 80 | - name: Apply secret 81 | k8s: 82 | state: present 83 | namespace: "{{ ansible_operator_meta.namespace }}" 84 | apply: yes 85 | wait: yes 86 | definition: "{{ lookup('template', 'secrets.yml.j2') }}" 87 | no_log: "{{ no_log }}" 88 | 89 | - name: Remove ownerReference on restored secrets 90 | k8s: 91 | definition: 92 | apiVersion: v1 93 | kind: Secret 94 | metadata: 95 | name: "{{ item.value.name }}" 96 | namespace: '{{ ansible_operator_meta.namespace }}' 97 | ownerReferences: null 98 | loop: "{{ secrets | dict2items }}" 99 | no_log: "{{ no_log }}" 100 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: namespace 7 | app.kubernetes.io/instance: system 8 | app.kubernetes.io/component: eda-manager 9 | app.kubernetes.io/created-by: eda-server-operator 10 | app.kubernetes.io/part-of: eda-server-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: system 13 | --- 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | metadata: 17 | name: controller-manager 18 | namespace: system 19 | labels: 20 | control-plane: controller-manager 21 | app.kubernetes.io/name: deployment 22 | app.kubernetes.io/instance: controller-manager 23 | app.kubernetes.io/component: eda-manager 24 | app.kubernetes.io/created-by: eda-server-operator 25 | app.kubernetes.io/part-of: eda-server-operator 26 | app.kubernetes.io/managed-by: kustomize 27 | spec: 28 | selector: 29 | matchLabels: 30 | control-plane: controller-manager 31 | replicas: 1 32 | template: 33 | metadata: 34 | annotations: 35 | kubectl.kubernetes.io/default-container: eda-manager 36 | labels: 37 | control-plane: controller-manager 38 | spec: 39 | # TODO(user): Uncomment the following code to configure the nodeAffinity expression 40 | # according to the platforms which are supported by your solution. 41 | # It is considered best practice to support multiple architectures. You can 42 | # build your manager image using the makefile target docker-buildx. 43 | # affinity: 44 | # nodeAffinity: 45 | # requiredDuringSchedulingIgnoredDuringExecution: 46 | # nodeSelectorTerms: 47 | # - matchExpressions: 48 | # - key: kubernetes.io/arch 49 | # operator: In 50 | # values: 51 | # - amd64 52 | # - arm64 53 | # - ppc64le 54 | # - s390x 55 | # - key: kubernetes.io/os 56 | # operator: In 57 | # values: 58 | # - linux 59 | securityContext: 60 | runAsNonRoot: true 61 | containers: 62 | - args: 63 | - --leader-elect 64 | - --leader-election-id=eda-server-operator 65 | image: controller:latest 66 | imagePullPolicy: IfNotPresent 67 | name: eda-manager 68 | env: 69 | - name: ANSIBLE_GATHERING 70 | value: explicit 71 | - name: ANSIBLE_VERBOSITY 72 | value: "0" 73 | - name: ANSIBLE_DEBUG_LOGS 74 | value: 'false' 75 | - name: WATCH_NAMESPACE 76 | valueFrom: 77 | fieldRef: 78 | fieldPath: metadata.namespace 79 | securityContext: 80 | allowPrivilegeEscalation: false 81 | capabilities: 82 | drop: 83 | - "ALL" 84 | livenessProbe: 85 | httpGet: 86 | path: /healthz 87 | port: 6789 88 | initialDelaySeconds: 15 89 | periodSeconds: 20 90 | readinessProbe: 91 | httpGet: 92 | path: /readyz 93 | port: 6789 94 | initialDelaySeconds: 5 95 | periodSeconds: 10 96 | # TODO(user): Configure the resources accordingly based on the project requirements. 97 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 98 | resources: 99 | limits: 100 | cpu: 2000m 101 | memory: 4000Mi 102 | requests: 103 | cpu: 10m 104 | memory: 256Mi 105 | serviceAccountName: controller-manager 106 | imagePullSecrets: 107 | - name: redhat-operators-pull-secret 108 | terminationGracePeriodSeconds: 10 109 | -------------------------------------------------------------------------------- /.github/workflows/stage.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Stage Release 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Version to stage' 8 | required: true 9 | default_eda_version: 10 | description: 'Will be injected as the DEFAULT_EDA_VERSION build arg.' 11 | required: true 12 | default_eda_ui_version: 13 | description: 'Will be injected as the DEFAULT_EDA_UI_VERSION build arg.' 14 | required: true 15 | confirm: 16 | description: 'Are you sure? Set this to yes.' 17 | required: true 18 | default: 'no' 19 | 20 | jobs: 21 | stage: 22 | runs-on: ubuntu-latest 23 | permissions: 24 | packages: write 25 | contents: write 26 | steps: 27 | - name: Verify inputs 28 | run: | 29 | set -e 30 | 31 | if [[ ${{ github.event.inputs.confirm }} != "yes" ]]; then 32 | >&2 echo "Confirm must be 'yes'" 33 | exit 1 34 | fi 35 | 36 | if [[ ${{ github.event.inputs.version }} == "" ]]; then 37 | >&2 echo "Set version to continue." 38 | exit 1 39 | fi 40 | 41 | exit 0 42 | 43 | - name: Checkout eda-server-operator 44 | uses: actions/checkout@v3 45 | with: 46 | repository: ${{ github.repository_owner }}/eda-server-operator 47 | path: eda-server-operator 48 | 49 | - name: Install playbook dependencies 50 | run: | 51 | python3 -m pip install docker 52 | 53 | - name: Log into registry ghcr.io 54 | uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 55 | with: 56 | registry: ghcr.io 57 | username: ${{ github.actor }} 58 | password: ${{ secrets.GITHUB_TOKEN }} 59 | 60 | - name: Build Operator Image 61 | run: | 62 | BUILD_ARGS="--build-arg DEFAULT_EDA_VERSION=${{ github.event.inputs.default_eda_version }} \ 63 | --build-arg DEFAULT_EDA_UI_VERSION=${{ github.event.inputs.default_eda_ui_version }} \ 64 | --build-arg OPERATOR_VERSION=${{ github.event.inputs.version }}" \ 65 | IMG=ghcr.io/${{ github.repository }}:${{ github.event.inputs.version }} \ 66 | make docker-buildx 67 | working-directory: eda-server-operator 68 | 69 | # Stub task for later PR to add EDA CI run 70 | # - name: Run test deployment 71 | # working-directory: eda-server-operator 72 | # run: | 73 | # python3 -m pip install -r molecule/requirements.txt 74 | # ansible-galaxy collection install -r molecule/requirements.yml 75 | # sudo rm -f $(which kustomize) 76 | # make kustomize 77 | # KUSTOMIZE_PATH=$(readlink -f bin/kustomize) molecule test -s kind 78 | # env: 79 | # EDA_TEST_VERSION: ${{ github.event.inputs.default_eda_version }} 80 | 81 | - name: Generate operator.yaml 82 | run: make generate-operator-yaml VERSION=${{ github.event.inputs.version }} 83 | working-directory: eda-server-operator 84 | 85 | 86 | - name: Create Draft Release 87 | id: create_release 88 | uses: actions/create-release@v1 89 | env: 90 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 91 | with: 92 | tag_name: ${{ github.event.inputs.version }} 93 | release_name: Release ${{ github.event.inputs.version }} 94 | draft: true 95 | 96 | - name: Upload Release Artifact 97 | uses: actions/upload-release-asset@v1 98 | env: 99 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 100 | with: 101 | upload_url: ${{ steps.create_release.outputs.upload_url }} 102 | asset_path: ./eda-server-operator/operator.yaml 103 | asset_name: operator.yaml 104 | asset_content_type: application/yaml 105 | --------------------------------------------------------------------------------