├── .ansible-lint ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── issue_labeler.yml └── workflows │ ├── ci.yaml │ ├── devel.yaml │ ├── feature.yml │ ├── label_issue.yml │ ├── label_pr.yml │ ├── pr_body_check.yml │ ├── promote.yaml │ ├── publish-operator-hub.yaml │ ├── reusable-nox.yml │ └── stage.yml ├── .gitignore ├── .readthedocs.yml ├── .yamllint ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── SECURITY.md ├── ansible └── instantiate-awx-deployment.yml ├── awx-demo.yml ├── awxmeshingress-demo.yml ├── config ├── crd │ ├── bases │ │ ├── awx.ansible.com_awxbackups.yaml │ │ ├── awx.ansible.com_awxmeshingresses.yaml │ │ ├── awx.ansible.com_awxrestores.yaml │ │ └── awx.ansible.com_awxs.yaml │ └── kustomization.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ └── manager_config_patch.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── manifests │ ├── bases │ │ └── awx-operator.clusterserviceversion.yaml │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── awx_editor_role.yaml │ ├── awx_viewer_role.yaml │ ├── awxbackup_editor_role.yaml │ ├── awxbackup_viewer_role.yaml │ ├── awxmeshingress_editor_role.yaml │ ├── awxmeshingress_viewer_role.yaml │ ├── awxrestore_editor_role.yaml │ ├── awxrestore_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml ├── samples │ ├── awx_v1alpha1_awxmeshingress.yaml │ ├── awx_v1beta1_awx.yaml │ ├── awx_v1beta1_awx_resource_limits.yaml │ ├── awx_v1beta1_awxbackup.yaml │ ├── awx_v1beta1_awxrestore.yaml │ └── kustomization.yaml ├── scorecard │ ├── bases │ │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ │ ├── basic.config.yaml │ │ └── olm.config.yaml └── testing │ ├── debug_logs_patch.yaml │ ├── kustomization.yaml │ ├── manager_image.yaml │ └── pull_policy │ ├── Always.yaml │ ├── IfNotPresent.yaml │ └── Never.yaml ├── dev ├── awx-cr │ ├── awx-cr-settings.yml │ ├── awx-k8s-ingress.yml │ └── awx-openshift-cr.yml └── secrets │ ├── admin-password-secret.yml │ ├── custom-secret-key.yml │ └── external-pg-secret.yml ├── docs ├── README.md ├── contributors-guide │ ├── author.md │ ├── code-of-conduct.md │ ├── contributing.md │ ├── get-involved.md │ └── release-process.md ├── development.md ├── index.md ├── installation │ ├── basic-install.md │ ├── creating-a-minikube-cluster-for-testing.md │ └── kind-install.md ├── migration │ └── migration.md ├── requirements.in ├── requirements.txt ├── troubleshooting │ └── debugging.md ├── uninstall │ └── uninstall.md ├── upgrade │ └── upgrading.md └── user-guide │ ├── admin-user-account-configuration.md │ ├── advanced-configuration │ ├── assigning-awx-pods-to-specific-nodes.md │ ├── auto-upgrade.md │ ├── container-probes.md │ ├── containers-resource-requirements.md │ ├── csrf-cookie-secure-setting.md │ ├── custom-receptor-certs.md │ ├── custom-volume-and-volume-mount-options.md │ ├── deploying-a-specific-version-of-awx.md │ ├── disable-ipv6.md │ ├── enabling-ldap-integration-at-awx-bootstrap.md │ ├── exporting-environment-variables-to-containers.md │ ├── extra-settings.md │ ├── horizontal-pod-autoscaler.md │ ├── host-aliases.md │ ├── images │ │ ├── mesh-ingress-instance-listener-address-on-awx-ui.png │ │ ├── mesh-ingress-instance-on-awx-ui.png │ │ └── peering-to-mesh-ingress-on-awx-ui.png │ ├── labeling-operator-managed-objects.md │ ├── mesh-ingress.md │ ├── no-log.md │ ├── persisting-projects-directory.md │ ├── pods-termination-grace-period.md │ ├── priority-classes.md │ ├── privileged-tasks.md │ ├── redis-container-capabilities.md │ ├── scaling-the-web-and-task-pods-independently.md │ ├── security-context.md │ ├── service-account.md │ ├── session-cookie-secure-setting.md │ └── trusting-a-custom-certificate-authority.md │ ├── database-configuration.md │ └── network-and-tls-configuration.md ├── down.sh ├── hack └── publish-to-operator-hub.sh ├── mkdocs.yml ├── molecule ├── default │ ├── converge.yml │ ├── create.yml │ ├── destroy.yml │ ├── kustomize.yml │ ├── molecule.yml │ ├── prepare.yml │ ├── tasks │ │ ├── _test_case_replicas.yml │ │ ├── apply_awx_spec.yml │ │ ├── awx_replicas_test.yml │ │ ├── awx_test.yml │ │ ├── awxbackup_test.yml │ │ ├── awxmeshingress_test.yml │ │ └── awxrestore_test.yml │ ├── templates │ │ └── awx_cr_molecule.yml.j2 │ ├── utils │ │ ├── output_all_container_logs_for_pod.yml │ │ └── output_k8s_resources.yml │ └── verify.yml ├── kind │ ├── converge.yml │ ├── create.yml │ ├── destroy.yml │ ├── molecule.yml │ └── prepare.yml ├── requirements.txt └── requirements.yml ├── noxfile.py ├── playbooks ├── .gitkeep └── awx.yml ├── projects └── .gitkeep ├── requirements.yml ├── roles ├── backup │ ├── README.md │ ├── defaults │ │ └── main.yml │ ├── meta │ │ └── main.yml │ ├── tasks │ │ ├── awx-cro.yml │ │ ├── cleanup.yml │ │ ├── creation.yml │ │ ├── delete_backup.yml │ │ ├── dump_generated_secret.yml │ │ ├── dump_ingress_tls_secrets.yml │ │ ├── dump_receptor_secrets.yml │ │ ├── dump_secret.yml │ │ ├── error_handling.yml │ │ ├── finalizer.yml │ │ ├── init.yml │ │ ├── main.yml │ │ ├── postgres.yml │ │ ├── secrets.yml │ │ └── update_status.yml │ ├── templates │ │ ├── backup_pvc.yml.j2 │ │ ├── event.yml.j2 │ │ └── management-pod.yml.j2 │ └── vars │ │ └── main.yml ├── common │ ├── defaults │ │ └── main.yml │ ├── meta │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ └── templates │ │ └── labels │ │ ├── additional_labels.yaml.j2 │ │ ├── common.yaml.j2 │ │ └── version.yaml.j2 ├── installer │ ├── README.md │ ├── defaults │ │ └── main.yml │ ├── files │ │ └── pre-stop │ │ │ ├── termination-env │ │ │ ├── termination-master │ │ │ └── termination-waiter │ ├── filter_plugins │ │ └── cpu_string_to_decimal.py │ ├── meta │ │ └── main.yml │ ├── tasks │ │ ├── admin_password_configuration.yml │ │ ├── broadcast_websocket_configuration.yml │ │ ├── check_existing.yml │ │ ├── cleanup.yml │ │ ├── database.yml │ │ ├── database_configuration.yml │ │ ├── enable_metrics_utility.yml │ │ ├── enable_metrics_utility_console.yml │ │ ├── idle_deployment.yml │ │ ├── initialize_django.yml │ │ ├── install.yml │ │ ├── load_bundle_cacert_secret.yml │ │ ├── load_ldap_cacert_secret.yml │ │ ├── load_ldap_password_secret.yml │ │ ├── load_route_tls_secret.yml │ │ ├── main.yml │ │ ├── migrate_data.yml │ │ ├── migrate_schema.yml │ │ ├── resources_configuration.yml │ │ ├── scale_down_deployment.yml │ │ ├── secret_key_configuration.yml │ │ ├── set_images.yml │ │ ├── update_status.yml │ │ └── upgrade_postgres.yml │ ├── templates │ │ ├── common │ │ │ ├── volume_mounts │ │ │ │ └── extra_settings_files.yaml.j2 │ │ │ └── volumes │ │ │ │ └── extra_settings_files.yaml.j2 │ │ ├── configmaps │ │ │ ├── config.yaml.j2 │ │ │ ├── pre_stop_scripts.yaml.j2 │ │ │ └── redirect-page.configmap.html.j2 │ │ ├── cronjobs │ │ │ ├── metrics-utility-gather.yaml.j2 │ │ │ └── metrics-utility-report.yaml.j2 │ │ ├── deployments │ │ │ ├── task.yaml.j2 │ │ │ └── web.yaml.j2 │ │ ├── jobs │ │ │ └── migration.yaml.j2 │ │ ├── networking │ │ │ ├── ingress.yaml.j2 │ │ │ └── service.yaml.j2 │ │ ├── rbac │ │ │ └── service_account.yaml.j2 │ │ ├── secrets │ │ │ ├── admin_password_secret.yaml.j2 │ │ │ ├── app_credentials.yaml.j2 │ │ │ ├── broadcast_websocket_secret.yaml.j2 │ │ │ ├── postgres_secret.yaml.j2 │ │ │ ├── postgres_upgrade_secret.yaml.j2 │ │ │ ├── receptor_ca_secret.yaml.j2 │ │ │ ├── receptor_work_signing_secret.yaml.j2 │ │ │ └── secret_key.yaml.j2 │ │ ├── settings │ │ │ ├── credentials.py.j2 │ │ │ ├── execution_environments.py.j2 │ │ │ └── ldap.py.j2 │ │ ├── statefulsets │ │ │ └── postgres.yaml.j2 │ │ └── storage │ │ │ ├── metrics-utility.yaml.j2 │ │ │ └── persistent.yaml.j2 │ └── vars │ │ └── main.yml ├── mesh_ingress │ ├── defaults │ │ └── main.yml │ ├── tasks │ │ ├── creation.yml │ │ ├── finalizer.yml │ │ └── main.yml │ └── templates │ │ ├── deployment.yml.j2 │ │ ├── ingress.yml.j2 │ │ ├── receptor_conf.configmap.yml.j2 │ │ ├── service.yml.j2 │ │ └── service_account.yml.j2 └── restore │ ├── README.md │ ├── defaults │ └── main.yml │ ├── meta │ └── main.yml │ ├── tasks │ ├── cleanup.yml │ ├── deploy_awx.yml │ ├── error_handling.yml │ ├── import_vars.yml │ ├── init.yml │ ├── main.yml │ ├── postgres.yml │ ├── secrets.yml │ └── update_status.yml │ ├── templates │ ├── awx_object.yml.j2 │ ├── event.yml.j2 │ ├── management-pod.yml.j2 │ └── secrets.yml.j2 │ └── vars │ └── main.yml ├── up.sh ├── vendor └── galaxy.ansible.com │ ├── kubernetes │ └── core │ │ └── kubernetes-core-2.4.2.tar.gz │ └── operator_sdk │ └── util │ └── operator_sdk-util-0.4.0.tar.gz └── watches.yaml /.ansible-lint: -------------------------------------------------------------------------------- 1 | --- 2 | skip_list: 3 | - '306' 4 | - '602' 5 | - '503' 6 | 7 | exclude_paths: 8 | - deploy/ 9 | - .cache/ 10 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Code of Conduct 2 | 3 | Please see the official [Ansible Community Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html). 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | blank_issues_enabled: false 3 | contact_links: 4 | - name: For debugging help or technical support 5 | url: https://github.com/ansible/awx-operator#get-involved 6 | about: For general debugging or technical support please see the Get Involved section of our readme. 7 | - name: 📝 Ansible Code of Conduct 8 | url: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_template_chooser 9 | about: AWX uses the Ansible Code of Conduct; ❤ Be nice to other members of the community. ☮ Behave. 10 | - name: 💼 For Enterprise 11 | url: https://www.ansible.com/products/engine?utm_medium=github&utm_source=issue_template_chooser 12 | about: Red Hat offers support for the Ansible Automation Platform 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ✨ Feature request 3 | description: Suggest an idea for this project 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Feature Request issues are for **feature requests** only. 9 | For debugging help or technical support, please see the [Get Involved section of our README](https://github.com/ansible/awx-operator#get-involved) 10 | 11 | - type: checkboxes 12 | id: terms 13 | attributes: 14 | label: Please confirm the following 15 | options: 16 | - label: I agree to follow this project's [code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html). 17 | required: true 18 | - label: I have checked the [current issues](https://github.com/ansible/awx-operator/issues) for duplicates. 19 | required: true 20 | - label: I understand that AWX Operator is open source software provided for free and that I might not receive a timely response. 21 | required: true 22 | 23 | - type: textarea 24 | id: summary 25 | attributes: 26 | label: Feature Summary 27 | description: Briefly describe the desired enhancement. 28 | validations: 29 | required: true 30 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ##### SUMMARY 2 | 3 | 4 | 9 | 10 | ##### ISSUE TYPE 11 | 12 | - Breaking Change 13 | - New or Enhanced Feature 14 | - Bug, Docs Fix or other nominal change 15 | 16 | ##### ADDITIONAL INFORMATION 17 | 22 | 23 | 24 | ``` 25 | 26 | ``` 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/docs" 5 | groups: 6 | dependencies: 7 | patterns: 8 | - "*" 9 | schedule: 10 | interval: "weekly" 11 | labels: 12 | - "component:docs" 13 | - "dependencies" 14 | - package-ecosystem: "github-actions" 15 | directory: "/" 16 | groups: 17 | dependencies: 18 | patterns: 19 | - "*" 20 | schedule: 21 | interval: "weekly" 22 | labels: 23 | - "dependencies" 24 | -------------------------------------------------------------------------------- /.github/issue_labeler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | needs_triage: 3 | - '.*' 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: CI 4 | 5 | on: 6 | pull_request: 7 | push: 8 | 9 | jobs: 10 | molecule: 11 | runs-on: ubuntu-latest 12 | name: molecule 13 | strategy: 14 | matrix: 15 | ansible_args: 16 | - --skip-tags=replicas 17 | - -t replicas 18 | env: 19 | DOCKER_API_VERSION: "1.41" 20 | DEBUG_OUTPUT_DIR: /tmp/awx_operator_molecule_test 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - uses: actions/setup-python@v5 25 | with: 26 | python-version: "3.8" 27 | 28 | - name: Install Dependencies 29 | run: | 30 | pip install -r molecule/requirements.txt 31 | 32 | - name: Install Collections 33 | run: | 34 | ansible-galaxy collection install -r molecule/requirements.yml 35 | 36 | - name: Run Molecule 37 | env: 38 | MOLECULE_VERBOSITY: 3 39 | PY_COLORS: '1' 40 | ANSIBLE_FORCE_COLOR: '1' 41 | STORE_DEBUG_OUTPUT: true 42 | run: | 43 | sudo rm -f $(which kustomize) 44 | make kustomize 45 | KUSTOMIZE_PATH=$(readlink -f bin/kustomize) molecule test -s kind -- ${{ matrix.ansible_args }} 46 | 47 | - name: Upload artifacts for failed tests if Run Molecule fails 48 | if: failure() 49 | uses: actions/upload-artifact@v4 50 | with: 51 | name: awx_operator_molecule_test 52 | path: ${{ env.DEBUG_OUTPUT_DIR }} 53 | no-log: 54 | runs-on: ubuntu-latest 55 | steps: 56 | - name: Checkout sources 57 | uses: actions/checkout@v4 58 | 59 | - name: Check no_log statements 60 | run: | 61 | set +e 62 | no_log=$(grep -nr ' no_log:' roles | grep -v '"{{ no_log }}"') 63 | if [ -n "${no_log}" ]; then 64 | echo 'Please update the following no_log statement(s) with the "{{ no_log }}" value' 65 | echo "${no_log}" 66 | exit 1 67 | fi 68 | nox-sessions: 69 | uses: ./.github/workflows/reusable-nox.yml 70 | -------------------------------------------------------------------------------- /.github/workflows/devel.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Devel 4 | 5 | on: 6 | push: 7 | branches: [devel] 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | name: Push devel image 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Fail if QUAY_REGISTRY not set 17 | run: | 18 | if [[ -z "${{ vars.QUAY_REGISTRY }}" ]]; then 19 | echo "QUAY_REGISTRY not set. Please set QUAY_REGISTRY in variable GitHub Actions variables." 20 | exit 1 21 | fi 22 | 23 | - name: Log into registry ghcr.io 24 | uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 25 | with: 26 | registry: ghcr.io 27 | username: ${{ github.actor }} 28 | password: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | 31 | - name: Log into registry quay.io 32 | uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 33 | with: 34 | registry: ${{ vars.QUAY_REGISTRY }} 35 | username: ${{ secrets.QUAY_USER }} 36 | password: ${{ secrets.QUAY_TOKEN }} 37 | 38 | 39 | - name: Build and Store Image @ghcr 40 | run: | 41 | IMG=ghcr.io/${{ github.repository }}:${{ github.sha }} make docker-buildx 42 | 43 | 44 | - name: Publish Image to quay.io 45 | run: | 46 | docker buildx imagetools create \ 47 | ghcr.io/${{ github.repository }}:${{ github.sha }} \ 48 | --tag ${{ vars.QUAY_REGISTRY }}/awx-operator:devel 49 | -------------------------------------------------------------------------------- /.github/workflows/feature.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Feature Branch Image Build and Push 4 | 5 | on: 6 | push: 7 | branches: [feature_*] 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | name: Push devel image 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 # needed so that git describe --tag works 17 | 18 | - name: Set VERSION 19 | run: | 20 | echo "VERSION=$(git describe --tags)" >>${GITHUB_ENV} 21 | 22 | - name: Set lower case owner name 23 | run: | 24 | echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} 25 | env: 26 | OWNER: '${{ github.repository_owner }}' 27 | 28 | - name: Set IMAGE_TAG_BASE 29 | run: | 30 | echo "IMAGE_TAG_BASE=ghcr.io/${OWNER_LC}/awx-operator" >>${GITHUB_ENV} 31 | 32 | - name: Log in to registry 33 | run: | 34 | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin 35 | 36 | - name: Build and Push awx-operator Image 37 | run: | 38 | make docker-build docker-push 39 | docker tag ${IMAGE_TAG_BASE}:${VERSION} ${IMAGE_TAG_BASE}:${GITHUB_REF##*/} 40 | docker push ${IMAGE_TAG_BASE}:${GITHUB_REF##*/} 41 | 42 | - name: Build bundle manifests 43 | run: | 44 | make bundle 45 | 46 | - name: Build and Push awx-operator Bundle 47 | run: | 48 | make bundle-build bundle-push 49 | docker tag ${IMAGE_TAG_BASE}-bundle:v${VERSION} ${IMAGE_TAG_BASE}-bundle:${GITHUB_REF##*/} 50 | docker push ${IMAGE_TAG_BASE}-bundle:${GITHUB_REF##*/} 51 | 52 | - name: Build and Push awx-operator Catalog 53 | run: | 54 | make catalog-build catalog-push 55 | docker tag ${IMAGE_TAG_BASE}-catalog:v${VERSION} ${IMAGE_TAG_BASE}-catalog:${GITHUB_REF##*/} 56 | docker push ${IMAGE_TAG_BASE}-catalog:${GITHUB_REF##*/} 57 | -------------------------------------------------------------------------------- /.github/workflows/label_issue.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Label Issues 3 | 4 | on: 5 | issues: 6 | types: 7 | - opened 8 | - reopened 9 | 10 | jobs: 11 | triage: 12 | runs-on: ubuntu-latest 13 | name: Label 14 | 15 | steps: 16 | - name: Label Issue - Needs Triage 17 | uses: github/issue-labeler@v3.4 18 | with: 19 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 20 | not-before: 2021-12-07T07:00:00Z 21 | configuration-path: .github/issue_labeler.yml 22 | enable-versioned-regex: 0 23 | if: github.event_name == 'issues' 24 | 25 | community: 26 | runs-on: ubuntu-latest 27 | name: Label Issue - Community 28 | steps: 29 | - uses: actions/checkout@v4 30 | - uses: actions/setup-python@v5 31 | - name: Install python requests 32 | run: pip install requests 33 | - name: Check if user is a member of Ansible org 34 | uses: jannekem/run-python-script-action@v1 35 | id: check_user 36 | with: 37 | script: | 38 | import requests 39 | headers = {'Accept': 'application/vnd.github+json', 'Authorization': 'token ${{ secrets.GITHUB_TOKEN }}'} 40 | response = requests.get('${{ fromJson(toJson(github.event.issue.user.url)) }}/orgs?per_page=100', headers=headers) 41 | is_member = False 42 | for org in response.json(): 43 | if org['login'] == 'ansible': 44 | is_member = True 45 | if is_member: 46 | print("User is member") 47 | else: 48 | print("User is community") 49 | - name: Add community label if not a member 50 | if: contains(steps.check_user.outputs.stdout, 'community') 51 | uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 52 | with: 53 | add-labels: "community" 54 | repo-token: ${{ secrets.GITHUB_TOKEN }} 55 | -------------------------------------------------------------------------------- /.github/workflows/label_pr.yml: -------------------------------------------------------------------------------- 1 | name: Label PR 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - reopened 8 | - synchronize 9 | 10 | jobs: 11 | community: 12 | runs-on: ubuntu-latest 13 | name: Label PR - Community 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - uses: actions/setup-python@v5 18 | 19 | - name: Create a virtual environment 20 | run: python3 -m venv venv 21 | 22 | - name: Activate virtual environment and install dependencies 23 | run: | 24 | source venv/bin/activate 25 | pip3 install requests 26 | 27 | - name: Check if user is a member of Ansible org 28 | uses: jannekem/run-python-script-action@v1 29 | id: check_user 30 | with: 31 | script: | 32 | import requests 33 | headers = {'Accept': 'application/vnd.github+json', 'Authorization': 'token ${{ secrets.GITHUB_TOKEN }}'} 34 | response = requests.get('${{ fromJson(toJson(github.event.pull_request.user.url)) }}/orgs?per_page=100', headers=headers) 35 | is_member = False 36 | for org in response.json(): 37 | if org['login'] == 'ansible': 38 | is_member = True 39 | if is_member: 40 | print("User is member") 41 | else: 42 | print("User is community") 43 | 44 | - name: Add community label if not a member 45 | if: contains(steps.check_user.outputs.stdout, 'community') 46 | uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 47 | with: 48 | add-labels: "community" 49 | repo-token: ${{ secrets.GITHUB_TOKEN }} 50 | -------------------------------------------------------------------------------- /.github/workflows/pr_body_check.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: PR Check 3 | env: 4 | BRANCH: ${{ github.base_ref || 'devel' }} 5 | on: 6 | pull_request: 7 | types: [opened, edited, reopened, synchronize] 8 | jobs: 9 | pr-check: 10 | name: Scan PR description for semantic versioning keywords 11 | runs-on: ubuntu-latest 12 | permissions: 13 | packages: write 14 | contents: read 15 | steps: 16 | - name: Check for each of the lines 17 | env: 18 | PR_BODY: ${{ github.event.pull_request.body }} 19 | run: | 20 | echo "$PR_BODY" | grep "Bug, Docs Fix or other nominal change" > Z 21 | echo "$PR_BODY" | grep "New or Enhanced Feature" > Y 22 | echo "$PR_BODY" | grep "Breaking Change" > X 23 | exit 0 24 | # We exit 0 and set the shell to prevent the returns from the greps from failing this step 25 | # See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference 26 | shell: bash {0} 27 | 28 | - name: Check for exactly one item 29 | run: | 30 | if [ $(cat X Y Z | wc -l) != 1 ] ; then 31 | echo "The PR body must contain exactly one of [ 'Bug, Docs Fix or other nominal change', 'New or Enhanced Feature', 'Breaking Change' ]" 32 | echo "We counted $(cat X Y Z | wc -l)" 33 | echo "See the default PR body for examples" 34 | exit 255; 35 | else 36 | exit 0; 37 | fi 38 | -------------------------------------------------------------------------------- /.github/workflows/promote.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Promote AWX Operator image 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | inputs: 8 | tag_name: 9 | description: 'Name for the tag of the release.' 10 | required: true 11 | quay_registry: 12 | description: 'Quay registry to push to.' 13 | default: 'quay.io/ansible' 14 | 15 | env: 16 | QUAY_REGISTRY: ${{ vars.QUAY_REGISTRY }} 17 | 18 | jobs: 19 | promote: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Set GitHub Env vars for workflow_dispatch event 23 | if: ${{ github.event_name == 'workflow_dispatch' }} 24 | run: | 25 | echo "TAG_NAME=${{ github.event.inputs.tag_name }}" >> $GITHUB_ENV 26 | echo "QUAY_REGISTRY=${{ github.event.inputs.quay_registry }}" >> $GITHUB_ENV 27 | 28 | - name: Set GitHub Env vars if release event 29 | if: ${{ github.event_name == 'release' }} 30 | run: | 31 | echo "TAG_NAME=${{ github.event.release.tag_name }}" >> $GITHUB_ENV 32 | 33 | - name: Fail if QUAY_REGISTRY not set 34 | run: | 35 | if [[ -z "${{ env.QUAY_REGISTRY }}" ]]; then 36 | echo "QUAY_REGISTRY not set. Please set QUAY_REGISTRY in variable GitHub Actions variables." 37 | exit 1 38 | fi 39 | 40 | - uses: actions/checkout@v4 41 | with: 42 | depth: 0 43 | 44 | 45 | - name: Log into registry ghcr.io 46 | uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 47 | with: 48 | registry: ghcr.io 49 | username: ${{ github.actor }} 50 | password: ${{ secrets.GITHUB_TOKEN }} 51 | 52 | 53 | - name: Log into registry quay.io 54 | uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 55 | with: 56 | registry: ${{ env.QUAY_REGISTRY }} 57 | username: ${{ secrets.QUAY_USER }} 58 | password: ${{ secrets.QUAY_TOKEN }} 59 | 60 | 61 | - name: Pull Tagged Staged Image and Publish to quay.io 62 | run: | 63 | docker buildx imagetools create \ 64 | ghcr.io/${{ github.repository }}:${{ env.TAG_NAME }} \ 65 | --tag ${{ env.QUAY_REGISTRY }}/awx-operator:${{ env.TAG_NAME }} 66 | 67 | 68 | - name: Pull Staged Image and Publish to quay.io/${{ github.repository }}:latest 69 | run: | 70 | docker buildx imagetools create \ 71 | ghcr.io/${{ github.repository }}:${{ env.TAG_NAME }} \ 72 | --tag ${{ env.QUAY_REGISTRY }}/awx-operator:latest 73 | -------------------------------------------------------------------------------- /.github/workflows/reusable-nox.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: nox 3 | 4 | "on": 5 | workflow_call: 6 | 7 | jobs: 8 | nox: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | include: 14 | - session: build 15 | python-versions: "3.11" 16 | name: "Run nox ${{ matrix.session }} session" 17 | steps: 18 | - name: Check out repo 19 | uses: actions/checkout@v4 20 | - name: Setup nox 21 | uses: wntrblm/nox@2024.04.15 22 | with: 23 | python-versions: "${{ matrix.python-versions }}" 24 | - name: "Run nox -s ${{ matrix.session }}" 25 | run: | 26 | nox -s "${{ matrix.session }}" 27 | -------------------------------------------------------------------------------- /.github/workflows/stage.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Stage Release 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Version to stage' 8 | required: true 9 | default_awx_version: 10 | description: 'Will be injected as the DEFAULT_AWX_VERSION build arg.' 11 | required: true 12 | confirm: 13 | description: 'Are you sure? Set this to yes.' 14 | required: true 15 | default: 'no' 16 | 17 | jobs: 18 | stage: 19 | runs-on: ubuntu-latest 20 | permissions: 21 | packages: write 22 | contents: write 23 | steps: 24 | - name: Verify inputs 25 | run: | 26 | set -e 27 | 28 | if [[ ${{ github.event.inputs.confirm }} != "yes" ]]; then 29 | >&2 echo "Confirm must be 'yes'" 30 | exit 1 31 | fi 32 | 33 | if [[ ${{ github.event.inputs.version }} == "" ]]; then 34 | >&2 echo "Set version to continue." 35 | exit 1 36 | fi 37 | 38 | exit 0 39 | 40 | - name: Checkout awx-operator 41 | uses: actions/checkout@v4 42 | with: 43 | repository: ${{ github.repository_owner }}/awx-operator 44 | path: awx-operator 45 | 46 | - name: Install playbook dependencies 47 | run: | 48 | python3 -m pip install docker 49 | 50 | - name: Log into registry ghcr.io 51 | uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 52 | with: 53 | registry: ghcr.io 54 | username: ${{ github.actor }} 55 | password: ${{ secrets.GITHUB_TOKEN }} 56 | 57 | - name: Stage awx-operator 58 | working-directory: awx-operator 59 | run: | 60 | BUILD_ARGS="--build-arg DEFAULT_AWX_VERSION=${{ github.event.inputs.default_awx_version }} \ 61 | --build-arg OPERATOR_VERSION=${{ github.event.inputs.version }}" \ 62 | IMG=ghcr.io/${{ github.repository }}:${{ github.event.inputs.version }} \ 63 | make docker-buildx 64 | 65 | - name: Run test deployment 66 | working-directory: awx-operator 67 | run: | 68 | python3 -m pip install -r molecule/requirements.txt 69 | ansible-galaxy collection install -r molecule/requirements.yml 70 | sudo rm -f $(which kustomize) 71 | make kustomize 72 | KUSTOMIZE_PATH=$(readlink -f bin/kustomize) molecule test -s kind 73 | env: 74 | AWX_TEST_VERSION: ${{ github.event.inputs.default_awx_version }} 75 | 76 | - name: Create Draft Release 77 | id: create_release 78 | uses: actions/create-release@v1 79 | env: 80 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 81 | with: 82 | tag_name: ${{ github.event.inputs.version }} 83 | release_name: Release ${{ github.event.inputs.version }} 84 | draft: true 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | gh-pages/ 3 | .cache/ 4 | /bin 5 | /bundle 6 | /bundle_tmp* 7 | /bundle.Dockerfile 8 | /charts 9 | /.cr-release-packages 10 | .vscode/ 11 | __pycache__ 12 | /site 13 | venv/* 14 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # RTD API version 5 | version: 2 6 | 7 | build: 8 | os: ubuntu-22.04 9 | tools: 10 | python: "3.11" 11 | 12 | mkdocs: 13 | configuration: mkdocs.yml 14 | 15 | python: 16 | install: 17 | - requirements: ./docs/requirements.txt 18 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | ignore: | 5 | .cache/ 6 | kustomization.yaml 7 | awx-operator.clusterserviceversion.yaml 8 | bundle 9 | hacking/ 10 | 11 | rules: 12 | truthy: disable 13 | line-length: 14 | max: 170 15 | document-start: disable 16 | comments-indentation: disable 17 | indentation: 18 | level: warning 19 | indent-sequences: consistent 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/operator-framework/ansible-operator:v1.34.2 2 | 3 | USER root 4 | RUN dnf update --security --bugfix -y && \ 5 | dnf install -y openssl 6 | 7 | USER 1001 8 | 9 | ARG DEFAULT_AWX_VERSION 10 | ARG OPERATOR_VERSION 11 | ENV DEFAULT_AWX_VERSION=${DEFAULT_AWX_VERSION} 12 | ENV OPERATOR_VERSION=${OPERATOR_VERSION} 13 | 14 | COPY requirements.yml ${HOME}/requirements.yml 15 | RUN ansible-galaxy collection install -r ${HOME}/requirements.yml \ 16 | && chmod -R ug+rwx ${HOME}/.ansible 17 | 18 | COPY watches.yaml ${HOME}/watches.yaml 19 | COPY roles/ ${HOME}/roles/ 20 | COPY playbooks/ ${HOME}/playbooks/ 21 | 22 | ENTRYPOINT ["/tini", "--", "/usr/local/bin/ansible-operator", "run", \ 23 | "--watches-file=./watches.yaml", \ 24 | "--reconcile-period=0s" \ 25 | ] 26 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | # Code generated by tool. DO NOT EDIT. 2 | # This file is used to track the info used to scaffold your project 3 | # and allow the plugins properly work. 4 | # More info: https://book.kubebuilder.io/reference/project-config.html 5 | domain: ansible.com 6 | layout: 7 | - ansible.sdk.operatorframework.io/v1 8 | plugins: 9 | manifests.sdk.operatorframework.io/v2: {} 10 | scorecard.sdk.operatorframework.io/v2: {} 11 | projectName: awx-operator 12 | resources: 13 | - api: 14 | crdVersion: v1 15 | namespaced: true 16 | domain: ansible.com 17 | group: awx 18 | kind: AWX 19 | version: v1beta1 20 | - api: 21 | crdVersion: v1 22 | namespaced: true 23 | domain: ansible.com 24 | group: awx 25 | kind: AWXBackup 26 | version: v1beta1 27 | - api: 28 | crdVersion: v1 29 | namespaced: true 30 | domain: ansible.com 31 | group: awx 32 | kind: AWXRestore 33 | version: v1beta1 34 | - api: 35 | crdVersion: v1 36 | namespaced: true 37 | domain: ansible.com 38 | group: awx 39 | kind: AWXMeshIngress 40 | version: v1alpha1 41 | version: "3" 42 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | For all security related bugs, email security@ansible.com instead of using this issue tracker and you will receive a prompt response. 2 | 3 | For more information on the Ansible community's practices regarding responsible disclosure, see https://www.ansible.com/security 4 | -------------------------------------------------------------------------------- /ansible/instantiate-awx-deployment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Deploy an instance of AWX using the awx-operator 3 | hosts: localhost 4 | 5 | collections: 6 | - kubernetes.core 7 | 8 | tasks: 9 | - name: Deploy AWX 10 | k8s: 11 | state: "{{ state | default('present') }}" 12 | namespace: "{{ namespace | default('default') }}" 13 | apply: yes 14 | wait: yes 15 | definition: 16 | apiVersion: awx.ansible.com/v1beta1 17 | kind: AWX 18 | metadata: 19 | name: awx 20 | spec: 21 | admin_user: admin 22 | admin_email: admin@localhost 23 | service_type: "{{ service_type | default(omit) }}" # Either clusterIP, Loadbalancer or NodePort 24 | ingress_type: "{{ ingress_type | default(omit) }}" # Either none, Ingress, Route 25 | image: "{{ image | default(omit) }}" 26 | image_version: "{{ image_version | default(omit) }}" 27 | development_mode: "{{ development_mode | default(omit) | bool }}" 28 | image_pull_policy: "{{ image_pull_policy | default(omit) }}" 29 | nodeport_port: "{{ nodeport_port | default(omit) }}" 30 | # ee_images: 31 | # - name: test-ee 32 | # image: quay.io//awx-ee 33 | -------------------------------------------------------------------------------- /awx-demo.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: awx.ansible.com/v1beta1 3 | kind: AWX 4 | metadata: 5 | name: awx-demo 6 | spec: 7 | service_type: nodeport 8 | -------------------------------------------------------------------------------- /awxmeshingress-demo.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: awx.ansible.com/v1alpha1 3 | kind: AWXMeshIngress 4 | metadata: 5 | name: awx-mesh-ingress-demo 6 | spec: 7 | deployment_name: awx-demo 8 | -------------------------------------------------------------------------------- /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/awx.ansible.com_awxs.yaml 6 | - bases/awx.ansible.com_awxbackups.yaml 7 | - bases/awx.ansible.com_awxrestores.yaml 8 | - bases/awx.ansible.com_awxmeshingresses.yaml 9 | #+kubebuilder:scaffold:crdkustomizeresource 10 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: awx 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: awx-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 | -------------------------------------------------------------------------------- /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 | containers: 12 | - name: kube-rbac-proxy 13 | securityContext: 14 | allowPrivilegeEscalation: false 15 | capabilities: 16 | drop: 17 | - "ALL" 18 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.15.0 19 | args: 20 | - "--secure-listen-address=0.0.0.0:8443" 21 | - "--upstream=http://127.0.0.1:8080/" 22 | - "--logtostderr=true" 23 | - "--v=0" 24 | ports: 25 | - containerPort: 8443 26 | protocol: TCP 27 | name: https 28 | resources: 29 | limits: 30 | cpu: 500m 31 | memory: 128Mi 32 | requests: 33 | cpu: 5m 34 | memory: 64Mi 35 | - name: awx-manager 36 | args: 37 | - "--health-probe-bind-address=:6789" 38 | - "--metrics-bind-address=127.0.0.1:8080" 39 | - "--leader-elect" 40 | - "--leader-election-id=awx-operator" 41 | -------------------------------------------------------------------------------- /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: awx-manager 11 | args: 12 | - "--config=controller_manager_config.yaml" 13 | volumeMounts: 14 | - name: awx-manager-config 15 | mountPath: /controller_manager_config.yaml 16 | subPath: controller_manager_config.yaml 17 | volumes: 18 | - name: awx-manager-config 19 | configMap: 20 | name: awx-manager-config 21 | -------------------------------------------------------------------------------- /config/manager/controller_manager_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 2 | kind: ControllerManagerConfig 3 | health: 4 | healthProbeBindAddress: :6789 5 | metrics: 6 | bindAddress: 127.0.0.1:8080 7 | 8 | leaderElection: 9 | leaderElect: true 10 | resourceName: 811c9dc5.ansible.com 11 | # leaderElectionReleaseOnCancel defines if the leader should step down volume 12 | # when the Manager ends. This requires the binary to immediately end when the 13 | # Manager is stopped, otherwise, this setting is unsafe. Setting this significantly 14 | # speeds up voluntary leader transitions as the new leader don't have to wait 15 | # LeaseDuration time first. 16 | # In the default scaffold provided, the program ends immediately after 17 | # the manager stops, so would be fine to enable this option. However, 18 | # if you are doing or is intended to do any operation such as perform cleanups 19 | # after the manager stops then its usage might be unsafe. 20 | # leaderElectionReleaseOnCancel: true 21 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | 4 | generatorOptions: 5 | disableNameSuffixHash: true 6 | 7 | configMapGenerator: 8 | - files: 9 | - controller_manager_config.yaml 10 | name: awx-manager-config 11 | 12 | apiVersion: kustomize.config.k8s.io/v1beta1 13 | kind: Kustomization 14 | images: 15 | - name: controller 16 | newName: quay.io/ansible/awx-operator 17 | newTag: latest 18 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | labels: 6 | control-plane: controller-manager 7 | name: system 8 | --- 9 | apiVersion: apps/v1 10 | kind: Deployment 11 | metadata: 12 | name: controller-manager 13 | namespace: system 14 | labels: 15 | control-plane: controller-manager 16 | spec: 17 | selector: 18 | matchLabels: 19 | control-plane: controller-manager 20 | replicas: 1 21 | template: 22 | metadata: 23 | annotations: 24 | kubectl.kubernetes.io/default-container: awx-manager 25 | labels: 26 | control-plane: controller-manager 27 | spec: 28 | securityContext: 29 | runAsNonRoot: true 30 | # For common cases that do not require escalating privileges 31 | # it is recommended to ensure that all your Pods/Containers are restrictive. 32 | # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted 33 | # Please uncomment the following code if your project does NOT have to work on old Kubernetes 34 | # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). 35 | # seccompProfile: 36 | # type: RuntimeDefault 37 | containers: 38 | - args: 39 | - --leader-elect 40 | - --leader-election-id=awx-operator 41 | image: controller:latest 42 | imagePullPolicy: IfNotPresent 43 | name: awx-manager 44 | env: 45 | - name: ANSIBLE_GATHERING 46 | value: explicit 47 | - name: ANSIBLE_DEBUG_LOGS 48 | value: 'false' 49 | - name: WATCH_NAMESPACE 50 | valueFrom: 51 | fieldRef: 52 | fieldPath: metadata.namespace 53 | securityContext: 54 | allowPrivilegeEscalation: false 55 | capabilities: 56 | drop: 57 | - "ALL" 58 | livenessProbe: 59 | httpGet: 60 | path: /healthz 61 | port: 6789 62 | initialDelaySeconds: 15 63 | periodSeconds: 20 64 | readinessProbe: 65 | httpGet: 66 | path: /readyz 67 | port: 6789 68 | initialDelaySeconds: 5 69 | periodSeconds: 10 70 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 71 | resources: 72 | requests: 73 | memory: "32Mi" 74 | cpu: "50m" 75 | limits: 76 | memory: "4000Mi" 77 | cpu: "2000m" 78 | serviceAccountName: controller-manager 79 | imagePullSecrets: 80 | - name: redhat-operators-pull-secret 81 | terminationGracePeriodSeconds: 10 82 | -------------------------------------------------------------------------------- /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/awx-operator.clusterserviceversion.yaml 5 | - ../default 6 | - ../samples 7 | - ../scorecard 8 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | # Prometheus Monitor Service (Metrics) 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | labels: 6 | control-plane: controller-manager 7 | name: controller-manager-metrics-monitor 8 | namespace: system 9 | spec: 10 | endpoints: 11 | - path: /metrics 12 | port: https 13 | scheme: https 14 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 15 | tlsConfig: 16 | insecureSkipVerify: true 17 | selector: 18 | matchLabels: 19 | control-plane: controller-manager 20 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: 7 | - "/metrics" 8 | verbs: 9 | - get 10 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: 7 | - authentication.k8s.io 8 | resources: 9 | - tokenreviews 10 | verbs: 11 | - create 12 | - apiGroups: 13 | - authorization.k8s.io 14 | resources: 15 | - subjectaccessreviews 16 | verbs: 17 | - create 18 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | protocol: TCP 13 | targetPort: https 14 | selector: 15 | control-plane: controller-manager 16 | -------------------------------------------------------------------------------- /config/rbac/awx_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit awxs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: awx-editor-role 6 | rules: 7 | - apiGroups: 8 | - awx.ansible.com 9 | resources: 10 | - awxs 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - awx.ansible.com 21 | resources: 22 | - awxs/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/awx_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view awxs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: awx-viewer-role 6 | rules: 7 | - apiGroups: 8 | - awx.ansible.com 9 | resources: 10 | - awxs 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - awx.ansible.com 17 | resources: 18 | - awxs/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/awxbackup_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit awxbackups. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: awxbackup-editor-role 6 | rules: 7 | - apiGroups: 8 | - awx.ansible.com 9 | resources: 10 | - awxbackups 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - awx.ansible.com 21 | resources: 22 | - awxbackups/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/awxbackup_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view awxbackups. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: awxbackup-viewer-role 6 | rules: 7 | - apiGroups: 8 | - awx.ansible.com 9 | resources: 10 | - awxbackups 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - awx.ansible.com 17 | resources: 18 | - awxbackups/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/awxmeshingress_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit awxmeshingresses. 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: awxmeshingress-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: awx-operator 10 | app.kubernetes.io/part-of: awx-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: awxmeshingress-editor-role 13 | rules: 14 | - apiGroups: 15 | - awx.ansible.com 16 | resources: 17 | - awxmeshingresses 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - awx.ansible.com 28 | resources: 29 | - awxmeshingresses/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/awxmeshingress_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view awxmeshingresses. 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: awxmeshingress-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: awx-operator 10 | app.kubernetes.io/part-of: awx-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: awxmeshingress-viewer-role 13 | rules: 14 | - apiGroups: 15 | - awx.ansible.com 16 | resources: 17 | - awxmeshingresses 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - awx.ansible.com 24 | resources: 25 | - awxmeshingresses/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/awxrestore_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit awxrestores. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: awxrestore-editor-role 6 | rules: 7 | - apiGroups: 8 | - awx.ansible.com 9 | resources: 10 | - awxrestores 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - awx.ansible.com 21 | resources: 22 | - awxrestores/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/awxrestore_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view awxrestores. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: awxrestore-viewer-role 6 | rules: 7 | - apiGroups: 8 | - awx.ansible.com 9 | resources: 10 | - awxrestores 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - awx.ansible.com 17 | resources: 18 | - awxrestores/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch 38 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | creationTimestamp: null 6 | name: awx-manager-role 7 | rules: 8 | - apiGroups: 9 | - route.openshift.io 10 | resources: 11 | - routes 12 | - routes/custom-host 13 | verbs: 14 | - get 15 | - list 16 | - create 17 | - delete 18 | - patch 19 | - update 20 | - watch 21 | - apiGroups: 22 | - "" 23 | resources: 24 | - pods 25 | - services 26 | - services/finalizers 27 | - serviceaccounts 28 | - endpoints 29 | - persistentvolumeclaims 30 | - events 31 | - configmaps 32 | - secrets 33 | verbs: 34 | - get 35 | - list 36 | - create 37 | - delete 38 | - patch 39 | - update 40 | - watch 41 | - apiGroups: 42 | - "rbac.authorization.k8s.io" 43 | resources: 44 | - roles 45 | - rolebindings 46 | verbs: 47 | - get 48 | - list 49 | - create 50 | - delete 51 | - patch 52 | - update 53 | - watch 54 | - apiGroups: 55 | - apps 56 | resources: 57 | - deployments 58 | - daemonsets 59 | - replicasets 60 | - statefulsets 61 | verbs: 62 | - get 63 | - list 64 | - create 65 | - delete 66 | - patch 67 | - update 68 | - watch 69 | - apiGroups: 70 | - networking.k8s.io 71 | resources: 72 | - ingresses 73 | verbs: 74 | - get 75 | - list 76 | - create 77 | - delete 78 | - patch 79 | - update 80 | - watch 81 | - apiGroups: 82 | - batch 83 | resources: 84 | - cronjobs 85 | - jobs 86 | verbs: 87 | - get 88 | - list 89 | - create 90 | - patch 91 | - update 92 | - watch 93 | - apiGroups: 94 | - monitoring.coreos.com 95 | resources: 96 | - servicemonitors 97 | verbs: 98 | - get 99 | - create 100 | - apiGroups: 101 | - apps 102 | resourceNames: 103 | - awx-operator 104 | resources: 105 | - deployments/finalizers 106 | verbs: 107 | - update 108 | - apiGroups: 109 | - apps 110 | resources: 111 | - deployments/scale 112 | - statefulsets/scale 113 | verbs: 114 | - patch 115 | - apiGroups: 116 | - "" 117 | resources: 118 | - pods/exec 119 | - pods/attach 120 | - pods/log # log & attach rules needed to be able to grant them to AWX service account 121 | verbs: 122 | - create 123 | - get 124 | - apiGroups: 125 | - apps 126 | resources: 127 | - replicasets 128 | verbs: 129 | - get 130 | - create 131 | - apiGroups: 132 | - awx.ansible.com 133 | resources: 134 | - '*' 135 | - awxbackups 136 | - awxrestores 137 | verbs: 138 | - '*' 139 | - apiGroups: 140 | - traefik.containo.us 141 | - traefik.io 142 | resources: 143 | - ingressroutetcps 144 | verbs: 145 | - get 146 | - list 147 | - create 148 | - delete 149 | - patch 150 | - update 151 | - watch 152 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: RoleBinding 4 | metadata: 5 | name: awx-manager-rolebinding 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: Role 9 | name: awx-manager-role 10 | subjects: 11 | - kind: ServiceAccount 12 | name: controller-manager 13 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/samples/awx_v1alpha1_awxmeshingress.yaml: -------------------------------------------------------------------------------- 1 | # Placeholder to pass CI and allow bundle generation 2 | --- 3 | apiVersion: awx.ansible.com/v1alpha1 4 | kind: AWXMeshIngress 5 | metadata: 6 | name: example-awx-mesh-ingress 7 | spec: 8 | deployment_name: example-awx 9 | -------------------------------------------------------------------------------- /config/samples/awx_v1beta1_awx.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: awx.ansible.com/v1beta1 3 | kind: AWX 4 | metadata: 5 | name: example-awx 6 | spec: 7 | web_resource_requirements: 8 | requests: 9 | cpu: 50m 10 | memory: 128M 11 | task_resource_requirements: 12 | requests: 13 | cpu: 50m 14 | memory: 128M 15 | ee_resource_requirements: 16 | requests: 17 | cpu: 50m 18 | memory: 64M 19 | -------------------------------------------------------------------------------- /config/samples/awx_v1beta1_awx_resource_limits.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: awx.ansible.com/v1beta1 3 | kind: AWX 4 | metadata: 5 | name: awx-with-limits 6 | spec: 7 | task_resource_requirements: 8 | requests: 9 | cpu: 100m 10 | memory: 128Mi 11 | limits: 12 | cpu: 2000m 13 | memory: 4Gi 14 | web_resource_requirements: 15 | requests: 16 | cpu: 100m 17 | memory: 128Mi 18 | limits: 19 | cpu: 1000m 20 | memory: 4Gi 21 | ee_resource_requirements: 22 | requests: 23 | cpu: 100m 24 | memory: 64Mi 25 | limits: 26 | cpu: 1000m 27 | memory: 4Gi 28 | redis_resource_requirements: 29 | requests: 30 | cpu: 50m 31 | memory: 64Mi 32 | limits: 33 | cpu: 1000m 34 | memory: 4Gi 35 | rsyslog_resource_requirements: 36 | requests: 37 | cpu: 100m 38 | memory: 128Mi 39 | limits: 40 | cpu: 1000m 41 | memory: 2Gi 42 | init_container_resource_requirements: 43 | requests: 44 | cpu: 100m 45 | memory: 128Mi 46 | limits: 47 | cpu: 1000m 48 | memory: 2Gi 49 | -------------------------------------------------------------------------------- /config/samples/awx_v1beta1_awxbackup.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: awx.ansible.com/v1beta1 2 | kind: AWXBackup 3 | metadata: 4 | name: example-awx-backup 5 | spec: 6 | deployment_name: example-awx 7 | backup_resource_requirements: 8 | limits: 9 | cpu: "1000m" 10 | memory: "4096Mi" 11 | requests: 12 | cpu: "25m" 13 | memory: "32Mi" 14 | -------------------------------------------------------------------------------- /config/samples/awx_v1beta1_awxrestore.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: awx.ansible.com/v1beta1 2 | kind: AWXRestore 3 | metadata: 4 | name: awxrestore-sample 5 | spec: 6 | deployment_name: example-awx-2 7 | backup_name: example-awx-backup 8 | restore_resource_requirements: 9 | limits: 10 | cpu: "1000m" 11 | memory: "4096Mi" 12 | requests: 13 | cpu: "25m" 14 | memory: "32Mi" 15 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - awx_v1beta1_awx.yaml 4 | - awx_v1beta1_awxbackup.yaml 5 | - awx_v1beta1_awxrestore.yaml 6 | - awx_v1alpha1_awxmeshingress.yaml 7 | #+kubebuilder:scaffold:manifestskustomizesamples 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.26.0 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /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.26.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.26.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.26.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.26.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.26.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /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: awx-manager 12 | env: 13 | - name: ANSIBLE_DEBUG_LOGS 14 | value: "TRUE" 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 | -------------------------------------------------------------------------------- /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: awx-manager 12 | image: testing 13 | -------------------------------------------------------------------------------- /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: awx-manager 12 | imagePullPolicy: Always 13 | -------------------------------------------------------------------------------- /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: awx-manager 12 | imagePullPolicy: IfNotPresent 13 | -------------------------------------------------------------------------------- /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: awx-manager 12 | imagePullPolicy: Never 13 | -------------------------------------------------------------------------------- /dev/awx-cr/awx-cr-settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: awx.ansible.com/v1beta1 3 | kind: AWX 4 | metadata: 5 | name: awx 6 | spec: 7 | service_type: clusterip 8 | ingress_type: route 9 | no_log: false 10 | 11 | # Secrets 12 | admin_password_secret: custom-admin-password 13 | postgres_configuration_secret: custom-pg-configuration 14 | secret_key_secret: custom-secret-key 15 | 16 | # Resource Requirements 17 | postgres_storage_requirements: 18 | requests: 19 | storage: 10Gi 20 | 21 | # Extra Settings 22 | extra_settings: 23 | - setting: MAX_PAGE_SIZE 24 | value: "500" 25 | -------------------------------------------------------------------------------- /dev/awx-cr/awx-k8s-ingress.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: awx.ansible.com/v1beta1 3 | kind: AWX 4 | metadata: 5 | name: awx 6 | spec: 7 | service_type: nodeport 8 | ingress_type: ingress 9 | 10 | # Secrets 11 | admin_password_secret: custom-admin-password 12 | postgres_configuration_secret: custom-pg-configuration 13 | secret_key_secret: custom-secret-key 14 | -------------------------------------------------------------------------------- /dev/awx-cr/awx-openshift-cr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: awx.ansible.com/v1beta1 3 | kind: AWX 4 | metadata: 5 | name: awx 6 | spec: 7 | service_type: clusterip 8 | ingress_type: Route 9 | 10 | # Secrets 11 | admin_password_secret: custom-admin-password 12 | postgres_configuration_secret: custom-pg-configuration 13 | secret_key_secret: custom-secret-key 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /dev/secrets/custom-secret-key.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: custom-secret-key 6 | stringData: 7 | secret_key: 'awxsecret' 8 | -------------------------------------------------------------------------------- /dev/secrets/external-pg-secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: external-pg-secret 6 | stringData: 7 | database: 'awx' 8 | host: 'awx-postgres' 9 | password: 'test' 10 | port: '5432' 11 | type: 'managed' 12 | username: 'awx' 13 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Building the Ansible AWX Operator Docs 2 | 3 | To build the AWX Operator docs locally: 4 | 5 | 1. Clone the AWX operator repository. 6 | 1. Preferrably, create a virtual environment for installing the dependencies. 7 | a. `python3 -m venv venv` 8 | b. `source venv/bin/activate` 9 | 1. From the root directory: 10 | a. `pip install -r docs/requirements.txt` 11 | b. `mkdocs build` 12 | 1. View the docs in your browser: 13 | a. `mkdocs serve` 14 | b. Open your browser and navigate to `http://127.0.0.1:8000/` 15 | 16 | This will create a new directory called `site/` in the root of your clone containing the index.html and static files. 17 | -------------------------------------------------------------------------------- /docs/contributors-guide/author.md: -------------------------------------------------------------------------------- 1 | # Author 2 | 3 | This operator was originally built in 2019 by [Jeff Geerling](https://www.jeffgeerling.com) and is now maintained by the Ansible Team 4 | -------------------------------------------------------------------------------- /docs/contributors-guide/code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | We ask all of our community members and contributors to adhere to the [Ansible code of conduct](http://docs.ansible.com/ansible/latest/community/code_of_conduct.html). If you have questions or need assistance, please reach out to our community team at [codeofconduct@ansible.com](mailto:codeofconduct@ansible.com) 4 | -------------------------------------------------------------------------------- /docs/contributors-guide/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please visit [our contributing guidelines](https://github.com/ansible/awx-operator/blob/devel/CONTRIBUTING.md). 4 | 5 | For docs changes, create PRs on the appropriate files in the `/docs` folder. 6 | -------------------------------------------------------------------------------- /docs/contributors-guide/get-involved.md: -------------------------------------------------------------------------------- 1 | # Get Involved 2 | 3 | We welcome your feedback, questions and ideas. Here's how to reach the community. 4 | 5 | ## Forum 6 | 7 | Join the [Ansible Forum](https://forum.ansible.com) as a single starting point and our default communication platform for questions and help, development discussions, events, and much more. [Register](https://forum.ansible.com/signup?) to join the community. Search by categories and tags to find interesting topics or start a new one; subscribe only to topics you need! 8 | 9 | * [Get Help](https://forum.ansible.com/c/help/6): get help or help others. Please add appropriate tags if you start new discussions, for example `awx-operator` and `documentation`. 10 | * [Posts tagged with 'awx-operator'](https://forum.ansible.com/tag/awx-operator): subscribe to participate in project-related conversations. 11 | * [Bullhorn newsletter](https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn) used to announce releases and important changes. 12 | * [Social Spaces](https://forum.ansible.com/c/chat/4): gather and interact with fellow enthusiasts. 13 | * [News & Announcements](https://forum.ansible.com/c/news/5): track project-wide announcements including social events. 14 | 15 | For more information on the forum navigation, see [Navigating the Ansible forum](https://forum.ansible.com/t/navigating-the-ansible-forum-tags-categories-and-concepts/39) post. 16 | 17 | ## Matrix 18 | 19 | For real-time interactions, conversations in the community happen over the Matrix protocol in the following channels: 20 | 21 | * [#awx:ansible.com](https://matrix.to/#/#awx:ansible.com): AWX and AWX-Operator project-related discussions. 22 | * [#docs:ansible.im](https://matrix.to/#/#docs:ansible.im): Ansible, AWX and AWX-Operator documentation-related discussions. 23 | 24 | For more information, see the community-hosted [Matrix FAQ](https://hackmd.io/@ansible-community/community-matrix-faq). 25 | -------------------------------------------------------------------------------- /docs/contributors-guide/release-process.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | The first step is to create a draft release. Typically this will happen in the [Stage Release](https://github.com/ansible/awx/blob/devel/.github/workflows/stage.yml) workflow for AWX and you don't need to do it as a separate step. 4 | 5 | If you need to do an independent release of the operator, you can run the [Stage Release](https://github.com/ansible/awx-operator/blob/devel/.github/workflows/stage.yml) in the awx-operator repo. Both of these workflows will run smoke tests, so there is no need to do this manually. 6 | 7 | After the draft release is created, publish it and the [Promote AWX Operator image](https://github.com/ansible/awx-operator/blob/devel/.github/workflows/promote.yaml) will run, which will: 8 | 9 | - Publish image to Quay 10 | - Release Helm chart 11 | 12 | After the GHA is complete, the final step is to run the [publish-to-operator-hub.sh](https://github.com/ansible/awx-operator/blob/devel/hack/publish-to-operator-hub.sh) script, which will create a PR in the following repos to add the new awx-operator bundle version to OperatorHub: 13 | 14 | - (community operator index) 15 | - (operator index shipped with Openshift) 16 | 17 | !!! note 18 | The usage is documented in the script itself, but here is an example of how you would use the script to publish the 2.5.3 awx-opeator bundle to OperatorHub. 19 | Note that you need to specify the version being released, as well as the previous version. This is because the bundle has a pointer to the previous version that is it being upgrade from. This is used by OLM to create a dependency graph. 20 | 21 | ```bash 22 | VERSION=2.5.3 PREV_VERSION=2.5.2 ./hack/publish-to-operator-hub.sh 23 | ``` 24 | 25 | There are some quirks with running this on OS X that still need to be fixed, but the script runs smoothly on linux. 26 | 27 | As soon as CI completes successfully, the PR's will be auto-merged. Please remember to monitor those PR's to make sure that CI passes, sometimes it needs a retry. 28 | -------------------------------------------------------------------------------- /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 awx-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=awx 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 AWX 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 | More tests coming soon... 59 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /docs/installation/kind-install.md: -------------------------------------------------------------------------------- 1 | # AWX Operator on Kind 2 | 3 | ## Kind Install 4 | 5 | Install Kind by running the following. Refer to the [official Kind documentation](https://kind.sigs.k8s.io/docs/user/quick-start/) for more information. 6 | 7 | ```sh 8 | # For Intel Macs 9 | [ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-darwin-amd64 10 | # For M1 / ARM Macs 11 | [ $(uname -m) = arm64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-darwin-arm64 12 | chmod +x ./kind 13 | mv ./kind /some-dir-in-your-PATH/kind 14 | ``` 15 | 16 | ### Create the Kind cluster 17 | 18 | Create a file called `kind.config` 19 | 20 | ```yaml 21 | apiVersion: kind.x-k8s.io/v1alpha4 22 | kind: Cluster 23 | nodes: 24 | - role: control-plane 25 | extraPortMappings: 26 | - containerPort: 32000 27 | hostPort: 32000 28 | listenAddress: "0.0.0.0" # Optional, defaults to "0.0.0.0" 29 | protocol: tcp # Optional, defaults to tcp 30 | - role: worker 31 | ``` 32 | 33 | Then create a cluster using that config 34 | 35 | ```sh 36 | kind create cluster --config=kind.config 37 | ``` 38 | 39 | Set cluster context for kubectl 40 | 41 | ```sh 42 | kubectl cluster-info --context kind-kind 43 | ``` 44 | 45 | Install NGINX Ingress Controller 46 | 47 | ```sh 48 | kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml 49 | ``` 50 | 51 | ## AWX 52 | 53 | Set the namespace context 54 | 55 | ```sh 56 | kubectl config set-context --current --namespace=awx 57 | ``` 58 | 59 | Checkout the tag you want to install from 60 | 61 | ```sh 62 | git checkout 2.7.2 63 | ``` 64 | 65 | Create a file named `kustomization.yaml` in the root of your local awx-operator clone. Include the following: 66 | 67 | ```sh 68 | apiVersion: kustomize.config.k8s.io/v1beta1 69 | kind: Kustomization 70 | resources: 71 | # Find the latest tag here: https://github.com/ansible/awx-operator/releases 72 | - github.com/ansible/awx-operator/config/default?ref=2.7.2 73 | 74 | # Set the image tags to match the git version from above 75 | images: 76 | - name: quay.io/ansible/awx-operator 77 | newTag: 2.7.2 78 | 79 | # Specify a custom namespace in which to install AWX 80 | namespace: awx 81 | ``` 82 | 83 | Run the following to apply the yaml 84 | 85 | ```sh 86 | kubectl apply -k . 87 | ``` 88 | 89 | Create a file called `awx-cr.yaml` with the following contents and any configuration changes you may wish to add. 90 | 91 | ```yaml 92 | --- 93 | apiVersion: awx.ansible.com/v1beta1 94 | kind: AWX 95 | metadata: 96 | name: awx-demo 97 | spec: 98 | service_type: nodeport 99 | nodeport_port: 32000 100 | ``` 101 | 102 | Create your AWX CR 103 | 104 | ```sh 105 | kubectl create -f awx-cr.yaml 106 | ``` 107 | 108 | Your AWX instance should now be reachable at 109 | 110 | !!! note 111 | If you configured a custom `nodeport_port`, you can find it by running `kubectl -n awx get svc awx-demo-service` 112 | 113 | ## Cleanup 114 | 115 | When you are done, you can delete all of this by running 116 | 117 | ```sh 118 | kind delete cluster 119 | ``` 120 | -------------------------------------------------------------------------------- /docs/requirements.in: -------------------------------------------------------------------------------- 1 | # This requirements file is used for AWX Operator latest doc builds. 2 | 3 | mkdocs-ansible 4 | -------------------------------------------------------------------------------- /docs/uninstall/uninstall.md: -------------------------------------------------------------------------------- 1 | # Uninstall 2 | 3 | To uninstall an AWX deployment instance, you basically need to remove the AWX kind related to that instance. For example, to delete an AWX instance named awx-demo, you would do: 4 | 5 | ```sh 6 | $ kubectl delete awx awx-demo 7 | awx.awx.ansible.com "awx-demo" deleted 8 | ``` 9 | 10 | Deleting an AWX instance will remove all related deployments and statefulsets, however, persistent volumes and secrets will remain. To enforce secrets also getting removed, you can use `garbage_collect_secrets: true`. 11 | 12 | !!! note 13 | If you ever intend to recover an AWX from an existing database you will need a copy of the secrets in order to perform a successful recovery. 14 | -------------------------------------------------------------------------------- /docs/user-guide/admin-user-account-configuration.md: -------------------------------------------------------------------------------- 1 | # Admin user account configuration 2 | 3 | There are three variables that are customizable for the admin user account creation. 4 | 5 | | Name | Description | Default | 6 | | --------------------- | -------------------------------------------- | ------------------ | 7 | | admin_user | Name of the admin user | `admin` | 8 | | admin_email | Email of the admin user | `test@example.com` | 9 | | admin_password_secret | Secret that contains the admin user password | Empty string | 10 | 11 | !!! warning 12 | `admin_password_secret` must be a Kubernetes secret and not your text clear password. 13 | 14 | If `admin_password_secret` is not provided, the operator will look for a secret named `-admin-password` for the admin password. If it is not present, the operator will generate a password and create a Secret from it named `-admin-password`. 15 | 16 | To retrieve the admin password, run `kubectl get secret -admin-password -o jsonpath="{.data.password}" | base64 --decode ; echo` 17 | 18 | The secret that is expected to be passed should be formatted as follow: 19 | 20 | ```yaml 21 | --- 22 | apiVersion: v1 23 | kind: Secret 24 | metadata: 25 | name: -admin-password 26 | namespace: 27 | stringData: 28 | password: mysuperlongpassword 29 | ``` 30 | 31 | ## Secret Key Configuration 32 | 33 | This key is used to encrypt sensitive data in the database. 34 | 35 | | Name | Description | Default | 36 | | ----------------- | ----------------------------------------------------- | ---------------- | 37 | | secret_key_secret | Secret that contains the symmetric key for encryption | Generated | 38 | 39 | !!! warning 40 | `secret_key_secret` must be a Kubernetes secret and not your text clear secret value. 41 | 42 | If `secret_key_secret` is not provided, the operator will look for a secret named `-secret-key` for the secret key. If it is not present, the operator will generate a password and create a Secret from it named `-secret-key`. It is important to not delete this secret as it will be needed for upgrades and if the pods get scaled down at any point. If you are using a GitOps flow, you will want to pass a secret key secret. 43 | 44 | The secret should be formatted as follow: 45 | 46 | ```yaml 47 | --- 48 | apiVersion: v1 49 | kind: Secret 50 | metadata: 51 | name: custom-awx-secret-key 52 | namespace: 53 | stringData: 54 | secret_key: supersecuresecretkey 55 | ``` 56 | 57 | Then specify the secret name on the AWX spec: 58 | 59 | ```yaml 60 | --- 61 | spec: 62 | ... 63 | secret_key_secret: custom-awx-secret-key 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/auto-upgrade.md: -------------------------------------------------------------------------------- 1 | # Auto upgrade 2 | 3 | With this parameter you can influence the behavior during an operator upgrade. 4 | If set to `true`, the operator will upgrade the specific instance directly. 5 | When the value is set to `false`, and we have a running deployment, the operator will not update the AWX instance. 6 | This can be useful when you have multiple AWX instances which you want to upgrade step by step instead of all at once. 7 | 8 | | Name | Description | Default | 9 | | -------------| ---------------------------------- | ------- | 10 | | auto_upgrade | Automatic upgrade of AWX instances | true | 11 | 12 | Example configuration of `auto_upgrade` parameter 13 | 14 | ```yaml 15 | spec: 16 | auto_upgrade: true 17 | ``` 18 | 19 | ## Upgrade of instances without auto upgrade 20 | 21 | There are two ways to upgrade instances which are marked with the 'auto_upgrade: false' flag. 22 | 23 | Changing flags: 24 | 25 | - change the auto_upgrade flag on your AWX object to true 26 | - wait until the upgrade process of that instance is finished 27 | - change the auto_upgrade flag on your AWX object back to false 28 | 29 | Delete the deployment: 30 | 31 | - delete the deployment object of your AWX instance 32 | 33 | ```sh 34 | kubectl -n awx delete deployment 35 | ``` 36 | 37 | - wait until the instance gets redeployed 38 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/container-probes.md: -------------------------------------------------------------------------------- 1 | # Container Probes 2 | 3 | These parameters control the usage of liveness and readiness container probes for 4 | the web and task containers. 5 | 6 | !!! tip 7 | All of probes are disabled by default for now, to enable it, set the `*_period` parameters. For example: 8 | 9 | ```yaml 10 | spec: 11 | web_liveness_period: 15 12 | web_readiness_period: 15 13 | task_liveness_period: 15 14 | task_readiness_period: 15 15 | ``` 16 | 17 | ## Web / Task Container Liveness Check 18 | 19 | The liveness probe queries the status of the supervisor daemon of the container. The probe will fail if it 20 | detects one of the services in a state other than "RUNNING". 21 | 22 | | Name | Description | Default | 23 | | -------------| -----------------------------------|---------| 24 | | web_liveness_period | Time period in seconds between each probe check. The value of 0 disables the probe. | 0 | 25 | | web_liveness_initial_delay | Initial delay before starting probes in seconds | 5 | 26 | | web_liveness_failure_threshold| Number of consecutive failure events to identify failure of container | 3 | 27 | | web_liveness_timeout | Number of seconds to wait for a probe response from container | 1 | 28 | | task_liveness_period | Time period in seconds between each probe check. The value of 0 disables the probe. | 0 | 29 | | task_liveness_initial_delay | Initial delay before starting probes in seconds | 5 | 30 | | task_liveness_failure_threshold| Number of consecutive failure events to identify failure of container | 3 | 31 | | task_liveness_timeout | Number of seconds to wait for a probe response from container | 1 | 32 | 33 | ## Web Container Readiness Check 34 | 35 | This is an HTTP check against the status endpoint to confirm the system is still able to respond to web requests. 36 | 37 | | Name | Description | Default | 38 | | -------------| ---------------------------------- | ------- | 39 | | web_readiness_period | Time period in seconds between each probe check. The value of 0 disables the probe. | 0 | 40 | | web_readiness_initial_delay | Initial delay before starting probes in seconds | 5 | 41 | | web_readiness_failure_threshold| Number of consecutive failure events to identify failure of container | 3 | 42 | | web_readiness_timeout | Number of seconds to wait for a probe response from container | 1 | 43 | 44 | ## Task Container Readiness Check 45 | 46 | This is a command probe using the builtin check command of the awx-manage utility. 47 | 48 | | Name | Description | Default | 49 | | -------------| ---------------------------------- | ------- | 50 | | task_readiness_period | Time period in seconds between each probe check. The value of 0 disables the probe. | 0 | 51 | | task_readiness_initial_delay | Initial delay before starting probes in seconds | 5 | 52 | | task_readiness_failure_threshold| Number of consecutive failure events to identify failure of container | 3 | 53 | | task_readiness_timeout | Number of seconds to wait for a probe response from container | 1 | 54 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/containers-resource-requirements.md: -------------------------------------------------------------------------------- 1 | # Containers Resource Requirements 2 | 3 | The resource requirements for both, the task and the web containers are configurable - both the lower end (requests) and the upper end (limits). 4 | 5 | | Name | Description | Default | 6 | | ------------------------------------ | ------------------------------------------------------------ | ------------------------------------ | 7 | | web_resource_requirements | Web container resource requirements | requests: {cpu: 100m, memory: 128Mi} | 8 | | task_resource_requirements | Task container resource requirements | requests: {cpu: 100m, memory: 128Mi} | 9 | | ee_resource_requirements | EE control plane container resource requirements | requests: {cpu: 50m, memory: 64Mi} | 10 | | redis_resource_requirements | Redis container resource requirements | requests: {cpu: 100m, memory: 128Mi} | 11 | | postgres_resource_requirements | Postgres container (and initContainer) resource requirements | requests: {cpu: 10m, memory: 64Mi} | 12 | | rsyslog_resource_requirements | Rsyslog container resource requirements | requests: {cpu: 100m, memory: 128Mi} | 13 | | init_container_resource_requirements | Init Container resource requirements | requests: {cpu: 100m, memory: 128Mi} | 14 | 15 | Example of customization could be: 16 | 17 | ```yaml 18 | --- 19 | spec: 20 | ... 21 | 22 | task_resource_requirements: 23 | requests: 24 | cpu: 100m 25 | memory: 128Mi 26 | ephemeral-storage: 100M 27 | limits: 28 | cpu: 2000m 29 | memory: 4Gi 30 | ephemeral-storage: 500M 31 | web_resource_requirements: 32 | requests: 33 | cpu: 100m 34 | memory: 128Mi 35 | limits: 36 | cpu: 1000m 37 | memory: 4Gi 38 | ee_resource_requirements: 39 | requests: 40 | cpu: 100m 41 | memory: 64Mi 42 | limits: 43 | cpu: 1000m 44 | memory: 4Gi 45 | redis_resource_requirements: 46 | requests: 47 | cpu: 50m 48 | memory: 64Mi 49 | limits: 50 | cpu: 1000m 51 | memory: 4Gi 52 | rsyslog_resource_requirements: 53 | requests: 54 | cpu: 100m 55 | memory: 128Mi 56 | limits: 57 | cpu: 1000m 58 | memory: 2Gi 59 | init_container_resource_requirements: 60 | requests: 61 | cpu: 100m 62 | memory: 128Mi 63 | limits: 64 | cpu: 1000m 65 | memory: 2Gi 66 | ``` 67 | 68 | ## Limits and ResourceQuotas 69 | 70 | If the cluster you are deploying in has a ResoruceQuota, you will need to configure resource limits for all of the pods deployed in that cluster. This can be done for AWX pods on the AWX spec in the manner shown above. 71 | 72 | There is an example you can use in [`awx_v1beta1_awx_resource_limits.yaml`](https://raw.githubusercontent.com/ansible/awx-operator/devel/config/samples/awx_v1beta1_awx_resource_limits.yaml). 73 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/csrf-cookie-secure-setting.md: -------------------------------------------------------------------------------- 1 | # CSRF Cookie Secure Setting 2 | 3 | With `csrf_cookie_secure`, you can pass the value for `CSRF_COOKIE_SECURE` to `/etc/tower/settings.py` 4 | 5 | | Name | Description | Default | 6 | | ------------------ | ------------------ | ------- | 7 | | csrf_cookie_secure | CSRF Cookie Secure | '' | 8 | 9 | Example configuration of the `csrf_cookie_secure` setting: 10 | 11 | ```yaml 12 | spec: 13 | csrf_cookie_secure: 'False' 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/custom-receptor-certs.md: -------------------------------------------------------------------------------- 1 | # Custom Receptor CA 2 | 3 | The control nodes on the K8S cluster will communicate with execution nodes via mutual TLS TCP connections, running via Receptor. 4 | Execution nodes will verify incoming connections by ensuring the x509 certificate was issued by a trusted Certificate Authority (CA). 5 | 6 | A user may wish to provide their own CA for this validation. If no CA is provided, AWX Operator will automatically generate one using OpenSSL. 7 | 8 | Given custom `ca.crt` and `ca.key` stored locally, run the following, 9 | 10 | ```bash 11 | kubectl create secret tls awx-demo-receptor-ca \ 12 | --cert=/path/to/ca.crt --key=/path/to/ca.key 13 | ``` 14 | 15 | The secret should be named `{AWX Custom Resource name}-receptor-ca`. In the above the AWX CR name is "awx-demo". Please replace "awx-demo" with your AWX Custom Resource name. 16 | 17 | If this secret is created after AWX is deployed, run the following to restart the deployment, 18 | 19 | ```bash 20 | kubectl rollout restart deployment awx-demo 21 | ``` 22 | 23 | !!! warning 24 | Changing the receptor CA will break connections to any existing execution nodes. These nodes will enter an `unavailable` state, and jobs will not be able to run on them. Users will need to download and re-run the install bundle for each execution node. This will replace the TLS certificate files with those signed by the new CA. The execution nodes should then appear in a `ready` state after a few minutes. 25 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/disable-ipv6.md: -------------------------------------------------------------------------------- 1 | # Disable IPv6 2 | 3 | Starting with AWX Operator release 0.24.0, [IPv6 was enabled in ngnix configuration](https://github.com/ansible/awx-operator/pull/950) which causes 4 | upgrades and installs to fail in environments where IPv6 is not allowed. Starting in 1.1.1 release, you can set the `ipv6_disabled` flag on the AWX 5 | spec. If you need to use an AWX operator version between 0.24.0 and 1.1.1 in an IPv6 disabled environment, it is suggested to enabled ipv6 on worker 6 | nodes. 7 | 8 | In order to disable ipv6 on ngnix configuration (awx-web container), add following to the AWX spec. 9 | 10 | The following variables are customizable: 11 | 12 | | Name | Description | Default | 13 | | ------------- | ---------------------- | ------- | 14 | | ipv6_disabled | Flag to disable ipv6 | false | 15 | 16 | ```yaml 17 | spec: 18 | ipv6_disabled: true 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/exporting-environment-variables-to-containers.md: -------------------------------------------------------------------------------- 1 | # Exporting Environment Variables to Containers 2 | 3 | If you need to export custom environment variables to your containers. 4 | 5 | | Name | Description | Default | 6 | | ----------------- | ------------------------------------------------------ | ------- | 7 | | task_extra_env | Environment variables to be added to Task container | '' | 8 | | web_extra_env | Environment variables to be added to Web container | '' | 9 | | rsyslog_extra_env | Environment variables to be added to Rsyslog container | '' | 10 | | ee_extra_env | Environment variables to be added to EE container | '' | 11 | 12 | !!! warning 13 | The `ee_extra_env` will only take effect to the globally available Execution Environments. For custom `ee`, please [customize the Pod spec](https://docs.ansible.com/ansible-tower/latest/html/administration/external_execution_envs.html#customize-the-pod-spec). 14 | 15 | Example configuration of environment variables 16 | 17 | ```yaml 18 | spec: 19 | task_extra_env: | 20 | - name: MYCUSTOMVAR 21 | value: foo 22 | web_extra_env: | 23 | - name: MYCUSTOMVAR 24 | value: foo 25 | rsyslog_extra_env: | 26 | - name: MYCUSTOMVAR 27 | value: foo 28 | ee_extra_env: | 29 | - name: MYCUSTOMVAR 30 | value: foo 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/horizontal-pod-autoscaler.md: -------------------------------------------------------------------------------- 1 | # Horizontal Pod Autoscaler (HPA) 2 | 3 | Horizontal Pod Autoscaler allows Kubernetes to scale the number of replicas of 4 | deployments in response to configured metrics. 5 | 6 | This feature conflicts with the operators ability to manage the number of static 7 | replicas to create for each deployment. 8 | 9 | The use of the settings below will tell the operator to not manage the replicas 10 | field on the identified deployments even if a replicas count has been set for those 11 | properties in the operator resource. 12 | 13 | | Name | Description | Default | 14 | | ---------------------- | ----------------------------------------------------------------------------- | ------- | 15 | | web_manage_replicas | Indicates operator should control the replicas count for the web deployment. | true | 16 | | task_manage_replicas | Indicates operator should control the replicas count for the task deployment. | true | 17 | 18 | ## Recommended Settings for HPA 19 | 20 | Please see the Kubernetes documentation on how to configure the horizontal pod 21 | autoscaler. 22 | 23 | The values for optimal HPA are cluster and need specific so general guidelines 24 | are not available at this time. 25 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/host-aliases.md: -------------------------------------------------------------------------------- 1 | # HostAliases 2 | 3 | Sometimes you might need to use [HostAliases](https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/) in web/task containers. 4 | 5 | | Name | Description | Default | 6 | | ------------ | --------------------- | ------- | 7 | | host_aliases | A list of HostAliases | None | 8 | 9 | Example of customization could be: 10 | 11 | ```yaml 12 | --- 13 | spec: 14 | ... 15 | host_aliases: 16 | - ip: 17 | hostnames: 18 | - 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/images/mesh-ingress-instance-listener-address-on-awx-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/awx-operator/e8f0306ec2bc8cdf4c27f55b8e860453d4618f83/docs/user-guide/advanced-configuration/images/mesh-ingress-instance-listener-address-on-awx-ui.png -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/images/mesh-ingress-instance-on-awx-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/awx-operator/e8f0306ec2bc8cdf4c27f55b8e860453d4618f83/docs/user-guide/advanced-configuration/images/mesh-ingress-instance-on-awx-ui.png -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/images/peering-to-mesh-ingress-on-awx-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/awx-operator/e8f0306ec2bc8cdf4c27f55b8e860453d4618f83/docs/user-guide/advanced-configuration/images/peering-to-mesh-ingress-on-awx-ui.png -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/labeling-operator-managed-objects.md: -------------------------------------------------------------------------------- 1 | # Labeling operator managed objects 2 | 3 | In certain situations labeling of Kubernetes objects managed by the operator might be desired (e.g. for owner identification purposes). 4 | For that `additional_labels` parameter could be used: 5 | 6 | | Name | Description | Default | 7 | | --------------------------- | ---------------------------------------------------------------------------------------- | ------- | 8 | | additional_labels | Additional labels defined on the resource, which should be propagated to child resources | [] | 9 | 10 | Example configuration where only `my/team` and `my/service` labels will be propagated to child objects (`Deployment`, `Secret`s, `ServiceAccount`, etc): 11 | 12 | ```yaml 13 | apiVersion: awx.ansible.com/v1beta1 14 | kind: AWX 15 | metadata: 16 | name: awx-demo 17 | labels: 18 | my/team: "foo" 19 | my/service: "bar" 20 | my/do-not-inherit: "yes" 21 | spec: 22 | additional_labels: 23 | - my/team 24 | - my/service 25 | ... 26 | ``` 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/persisting-projects-directory.md: -------------------------------------------------------------------------------- 1 | # Persisting Projects Directory 2 | 3 | In cases which you want to persist the `/var/lib/projects` directory, there are few variables that are customizable for the `awx-operator`. 4 | 5 | | Name | Description | Default | 6 | | ---------------------------- | ---------------------------------------------------------------------------------------------- | ------------- | 7 | | projects_persistence | Whether or not the /var/lib/projects directory will be persistent | false | 8 | | projects_storage_class | Define the PersistentVolume storage class | '' | 9 | | projects_storage_size | Define the PersistentVolume size | 8Gi | 10 | | projects_storage_access_mode | Define the PersistentVolume access mode | ReadWriteMany | 11 | | projects_existing_claim | Define an existing PersistentVolumeClaim to use (cannot be combined with `projects_storage_*`) | '' | 12 | 13 | Example of customization when the `awx-operator` automatically handles the persistent volume could be: 14 | 15 | ```yaml 16 | --- 17 | spec: 18 | ... 19 | projects_persistence: true 20 | projects_storage_class: rook-ceph 21 | projects_storage_size: 20Gi 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/pods-termination-grace-period.md: -------------------------------------------------------------------------------- 1 | # Pods termination grace period 2 | 3 | During deployment restarts or new rollouts, when old ReplicaSet Pods are being terminated, the corresponding jobs which are managed (executed or controlled) by old AWX Pods may end up in `Error` state as there is no mechanism to transfer them to the newly spawned AWX Pods. 4 | To work around the problem one could set `termination_grace_period_seconds` in AWX spec, which does the following: 5 | 6 | - It sets the corresponding [`terminationGracePeriodSeconds`](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination) Pod spec of the AWX Deployment to the value provided 7 | - The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal 8 | 9 | - It adds a [`PreStop`](https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#hook-handler-execution) hook script, which will keep AWX Pods in terminating state until it finished, up to `terminationGracePeriodSeconds`. 10 | - This grace period applies to the total time it takes for both the PreStop hook to execute and for the Container to stop normally 11 | - While the hook script just waits until the corresponding AWX Pod (instance) no longer has any managed jobs, in which case it finishes with success and hands over the overall Pod termination process to normal AWX processes. 12 | 13 | One may want to set this value to the maximum duration they accept to wait for the affected Jobs to finish. 14 | Keeping in mind that such finishing jobs may increase Pods termination time in such situations as `kubectl rollout restart`, AWX upgrade by the operator, or Kubernetes [API-initiatedevictions](https://kubernetes.io/docs/concepts/scheduling-eviction/api-eviction/). 15 | 16 | | Name | Description | Default | 17 | | -------------------------------- | --------------------------------------------------------------- | ------- | 18 | | termination_grace_period_seconds | Optional duration in seconds pods needs to terminate gracefully | not set | 19 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/priority-classes.md: -------------------------------------------------------------------------------- 1 | # Priority Classes 2 | 3 | The AWX and Postgres pods can be assigned a custom PriorityClass to rank their importance compared to other pods in your cluster, which determines which pods get evicted first if resources are running low. 4 | First, [create your PriorityClass](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/#priorityclass) if needed. 5 | Then set the name of your priority class to the control plane and postgres pods as shown below. 6 | 7 | ```yaml 8 | spec: 9 | ... 10 | control_plane_priority_class: awx-demo-high-priority 11 | postgres_priority_class: awx-demo-medium-priority 12 | ``` 13 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/privileged-tasks.md: -------------------------------------------------------------------------------- 1 | # Privileged Tasks 2 | 3 | Depending on the type of tasks that you'll be running, you may find that you need the task pod to run as `privileged`. This can open yourself up to a variety of security concerns, so you should be aware (and verify that you have the privileges) to do this if necessary. In order to toggle this feature, you can add the following to your custom resource: 4 | 5 | ```yaml 6 | --- 7 | spec: 8 | ... 9 | task_privileged: true 10 | ``` 11 | 12 | If you are attempting to do this on an OpenShift cluster, you will need to grant the `awx` ServiceAccount the `privileged` SCC, which can be done with: 13 | 14 | ```sh 15 | oc adm policy add-scc-to-user privileged -z awx 16 | ``` 17 | 18 | Again, this is the most relaxed SCC that is provided by OpenShift, so be sure to familiarize yourself with the security concerns that accompany this action. 19 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/redis-container-capabilities.md: -------------------------------------------------------------------------------- 1 | # Redis container capabilities 2 | 3 | Depending on your kubernetes cluster and settings you might need to grant some capabilities to the redis container so it can start. Set the `redis_capabilities` option so the capabilities are added in the deployment. 4 | 5 | ```yaml 6 | --- 7 | spec: 8 | ... 9 | redis_capabilities: 10 | - CHOWN 11 | - SETUID 12 | - SETGID 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/scaling-the-web-and-task-pods-independently.md: -------------------------------------------------------------------------------- 1 | # Scaling the Web and Task Pods independently 2 | 3 | You can scale replicas up or down for each deployment by using the `web_replicas` or `task_replicas` respectively. You can scale all pods across both deployments by using `replicas` as well. The logic behind these CRD keys acts as such: 4 | 5 | - If you specify the `replicas` field, the key passed will scale both the `web` and `task` replicas to the same number. 6 | - If `web_replicas` or `task_replicas` is ever passed, it will override the existing `replicas` field on the specific deployment with the new key value. 7 | 8 | These new replicas can be constrained in a similar manner to previous single deployments by appending the particular deployment name in front of the constraint used. More about those new constraints can be found in the [Assigning AWX pods to specific nodes](./assigning-awx-pods-to-specific-nodes.md) page. 9 | 10 | ## Horizontal Pod Autoscaling 11 | 12 | The operator is capable of working with Kubernetes' HPA capabilities. See [Horizontal Pod Autoscaler](./horizontal-pod-autoscaler.md) 13 | documentation for more information. 14 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/security-context.md: -------------------------------------------------------------------------------- 1 | # Security Context 2 | 3 | It is possible to modify some `SecurityContext` proprieties of the various deployments and stateful sets if needed. 4 | 5 | | Name | Description | Default | 6 | | ---------------------------------- | -------------------------------------------- | ------- | 7 | | security_context_settings | SecurityContext for Task and Web deployments | {} | 8 | | postgres_security_context_settings | SecurityContext for PostgreSQL container | {} | 9 | 10 | Example configuration securityContext for the Task and Web deployments: 11 | 12 | ```yaml 13 | spec: 14 | security_context_settings: 15 | allowPrivilegeEscalation: false 16 | capabilities: 17 | drop: 18 | - ALL 19 | postgres_security_context_settings: 20 | runAsNonRoot: true 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/service-account.md: -------------------------------------------------------------------------------- 1 | # Service Account 2 | 3 | If you need to modify some `ServiceAccount` proprieties 4 | 5 | | Name | Description | Default | 6 | | --------------------------- | --------------------------------- | ------- | 7 | | service_account_annotations | Annotations to the ServiceAccount | '' | 8 | 9 | Example configuration of environment variables 10 | 11 | ```yaml 12 | spec: 13 | service_account_annotations: | 14 | eks.amazonaws.com/role-arn: arn:aws:iam:::role/ 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-configuration/session-cookie-secure-setting.md: -------------------------------------------------------------------------------- 1 | # Session Cookie Secure Setting 2 | 3 | With `session_cookie_secure`, you can pass the value for `SESSION_COOKIE_SECURE` to `/etc/tower/settings.py` 4 | 5 | | Name | Description | Default | 6 | | --------------------- | --------------------- | ------- | 7 | | session_cookie_secure | Session Cookie Secure | '' | 8 | 9 | Example configuration of the `session_cookie_secure` setting: 10 | 11 | ```yaml 12 | spec: 13 | session_cookie_secure: 'False' 14 | ``` 15 | -------------------------------------------------------------------------------- /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 `awx-operator`. 4 | 5 | Trusting a custom Certificate Authority allows the AWX to access network services configured with SSL certificates issued locally, such as cloning a project from from an internal Git server via HTTPS. It is common for these scenarios, experiencing the error [unable to verify the first certificate](https://github.com/ansible/awx-operator/issues/376). 6 | 7 | | Name | Description | Default | 8 | |-------------------------------------| ---------------------------------------- |---------| 9 | | ldap_cacert_secret _(deprecated)_ | LDAP Certificate Authority secret name | '' | 10 | | ldap_password_secret _(deprecated)_ | LDAP BIND DN Password secret name | '' | 11 | | bundle_cacert_secret | Certificate Authority secret name | '' | 12 | 13 | Please note the `awx-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. 14 | 15 | Example of customization could be: 16 | 17 | ```yaml 18 | --- 19 | spec: 20 | ... 21 | ldap_cacert_secret: -custom-certs 22 | ldap_password_secret: -ldap-password 23 | bundle_cacert_secret: -custom-certs 24 | ``` 25 | 26 | Create the secret with `kustomization.yaml` file: 27 | 28 | ```yaml 29 | ... 30 | secretGenerator: 31 | - name: -custom-certs 32 | files: 33 | - bundle-ca.crt= 34 | options: 35 | disableNameSuffixHash: true 36 | ... 37 | ``` 38 | 39 | Create the secret with CLI: 40 | 41 | * Certificate Authority secret 42 | 43 | ```sh 44 | kubectl create secret generic -custom-certs \ 45 | --from-file=ldap-ca.crt= \ 46 | --from-file=bundle-ca.crt= 47 | ``` 48 | 49 | * LDAP BIND DN Password secret 50 | 51 | ```sh 52 | kubectl create secret generic -ldap-password \ 53 | --from-literal=ldap-password= 54 | ``` 55 | -------------------------------------------------------------------------------- /down.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # AWX Operator down.sh 3 | # Purpose: 4 | # Cleanup and delete the namespace you deployed in 5 | 6 | # -- Usage 7 | # NAMESPACE=awx ./down.sh 8 | 9 | # -- Variables 10 | TAG=${TAG:-dev} 11 | AWX_CR=${AWX_CR:-awx} 12 | CLEAN_DB=${CLEAN_DB:-false} 13 | 14 | 15 | # -- Check for required variables 16 | # Set the following environment variables 17 | # export NAMESPACE=awx 18 | 19 | if [ -z "$NAMESPACE" ]; then 20 | echo "Error: NAMESPACE env variable is not set. Run the following with your namespace:" 21 | echo " export NAMESPACE=developer" 22 | exit 1 23 | fi 24 | 25 | # -- Delete Backups 26 | kubectl delete awxbackup --all 27 | 28 | # -- Delete Restores 29 | kubectl delete awxrestore --all 30 | 31 | # Deploy Operator 32 | make undeploy NAMESPACE=$NAMESPACE 33 | 34 | # Remove PVCs 35 | kubectl delete pvc postgres-15-$AWX_CR-postgres-15-0 36 | 37 | -------------------------------------------------------------------------------- /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 | tags: 16 | - always 17 | 18 | - import_tasks: kustomize.yml 19 | vars: 20 | state: present 21 | tags: 22 | - always 23 | -------------------------------------------------------------------------------- /molecule/default/create.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create 3 | hosts: localhost 4 | connection: local 5 | gather_facts: false 6 | tasks: [] 7 | -------------------------------------------------------------------------------- /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 | tags: 14 | - always 15 | 16 | - name: Destroy Namespace 17 | k8s: 18 | api_version: v1 19 | kind: Namespace 20 | name: '{{ namespace }}' 21 | state: absent 22 | tags: 23 | - always 24 | 25 | - name: Unset pull policy 26 | command: '{{ kustomize }} edit remove patch --path pull_policy/{{ operator_pull_policy }}.yaml' 27 | args: 28 | chdir: '{{ config_dir }}/testing' 29 | tags: 30 | - always 31 | -------------------------------------------------------------------------------- /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 | tags: 10 | - always 11 | 12 | - name: Set resources to {{ state }} 13 | k8s: 14 | definition: '{{ item }}' 15 | state: '{{ state }}' 16 | wait: yes 17 | loop: '{{ resources.stdout | from_yaml_all | list }}' 18 | tags: 19 | - always 20 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: delegated 6 | lint: | 7 | set -e 8 | yamllint . 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 | awx_ee_image: ${AWX_EE_TEST_IMAGE:-""} 25 | awx_image: ${AWX_TEST_IMAGE:-""} 26 | awx_version: ${AWX_TEST_VERSION:-""} 27 | default_awx_version: "{{ lookup('url', 'https://api.github.com/repos/ansible/awx/releases/latest') | from_json | json_query('tag_name') }}" 28 | ansible_python_interpreter: '{{ ansible_playbook_python }}' 29 | config_dir: ${MOLECULE_PROJECT_DIRECTORY}/config 30 | samples_dir: ${MOLECULE_PROJECT_DIRECTORY}/config/samples 31 | operator_image: ${OPERATOR_IMAGE:-""} 32 | operator_pull_policy: ${OPERATOR_PULL_POLICY:-"Always"} 33 | kustomize: ${KUSTOMIZE_PATH:-kustomize} 34 | store_debug_output: ${STORE_DEBUG_OUTPUT:-false} 35 | debug_output_dir: ${DEBUG_OUTPUT_DIR:-"/tmp/awx_operator_molecule_test"} 36 | env: 37 | K8S_AUTH_KUBECONFIG: ${KUBECONFIG:-"~/.kube/config"} 38 | verifier: 39 | name: ansible 40 | lint: | 41 | set -e 42 | ansible-lint 43 | -------------------------------------------------------------------------------- /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 | tags: 15 | - always 16 | 17 | - name: Set testing image 18 | command: '{{ kustomize }} edit set image testing={{ operator_image }}' 19 | args: 20 | chdir: '{{ config_dir }}/testing' 21 | tags: 22 | - always 23 | 24 | - name: Set pull policy 25 | command: '{{ kustomize }} edit add patch --path pull_policy/{{ operator_pull_policy }}.yaml' 26 | args: 27 | chdir: '{{ config_dir }}/testing' 28 | tags: 29 | - always 30 | 31 | - name: Set testing namespace 32 | command: '{{ kustomize }} edit set namespace {{ namespace }}' 33 | args: 34 | chdir: '{{ config_dir }}/testing' 35 | tags: 36 | - always 37 | -------------------------------------------------------------------------------- /molecule/default/tasks/_test_case_replicas.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get web pod details 3 | k8s_info: 4 | namespace: '{{ namespace }}' 5 | kind: Pod 6 | label_selectors: 7 | - app.kubernetes.io/name = example-awx-web 8 | register: awx_web_pod 9 | # This can take a while to actually make it to the cluster 10 | retries: 30 11 | delay: 5 12 | until: awx_web_pod.resources | length == expected_web_replicas 13 | ignore_errors: true 14 | 15 | - name: Get task pod details 16 | k8s_info: 17 | namespace: '{{ namespace }}' 18 | kind: Pod 19 | label_selectors: 20 | - app.kubernetes.io/name = example-awx-task 21 | register: awx_task_pod 22 | # This can take a while to actually make it to the cluster 23 | retries: 30 24 | delay: 5 25 | until: awx_task_pod.resources | length == expected_task_replicas 26 | ignore_errors: true 27 | 28 | - name: Ensure that the correct number of web and task pods exist 29 | assert: 30 | that: 31 | - awx_web_pod.resources | length == expected_web_replicas 32 | - awx_task_pod.resources | length == expected_task_replicas 33 | fail_msg: >- 34 | Web pods: Expected {{ expected_web_replicas }}, got {{ awx_web_pod.resources | length }}. 35 | Task pods: Expected {{ expected_task_replicas }}, got {{ awx_task_pod.resources | length }}. 36 | -------------------------------------------------------------------------------- /molecule/default/tasks/apply_awx_spec.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create or update the awx.ansible.com/v1beta1.AWX 3 | k8s: 4 | state: present 5 | namespace: '{{ namespace }}' 6 | definition: "{{ lookup('template', 'awx_cr_molecule.yml.j2') | from_yaml }}" 7 | apply: true 8 | wait: yes 9 | wait_timeout: 900 10 | wait_condition: 11 | type: Running 12 | reason: Successful 13 | status: "True" 14 | -------------------------------------------------------------------------------- /molecule/default/tasks/awx_replicas_test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - block: 3 | - debug: 4 | msg: test - web_replicas and task_replicas should override replicas 5 | 6 | - include_tasks: apply_awx_spec.yml 7 | vars: 8 | additional_fields: 9 | replicas: 2 10 | web_replicas: 0 11 | task_replicas: 0 12 | 13 | - include_tasks: _test_case_replicas.yml 14 | vars: 15 | expected_web_replicas: 0 16 | expected_task_replicas: 0 17 | 18 | #### 19 | 20 | - debug: 21 | msg: test - replicas should act as a default 22 | 23 | - include_tasks: apply_awx_spec.yml 24 | vars: 25 | additional_fields: 26 | replicas: 2 27 | web_replicas: 1 28 | 29 | - include_tasks: _test_case_replicas.yml 30 | vars: 31 | expected_web_replicas: 1 32 | expected_task_replicas: 2 33 | 34 | #### 35 | 36 | - debug: 37 | msg: test - replicas=0 should kill all pods 38 | 39 | - include_tasks: apply_awx_spec.yml 40 | vars: 41 | additional_fields: 42 | replicas: 0 43 | 44 | - include_tasks: _test_case_replicas.yml 45 | vars: 46 | expected_web_replicas: 0 47 | expected_task_replicas: 0 48 | 49 | #### 50 | 51 | - debug: 52 | msg: test - replicas=2 should give 2 of each 53 | 54 | - include_tasks: apply_awx_spec.yml 55 | vars: 56 | additional_fields: 57 | replicas: 2 58 | 59 | - include_tasks: _test_case_replicas.yml 60 | vars: 61 | expected_web_replicas: 2 62 | expected_task_replicas: 2 63 | tags: 64 | - replicas 65 | -------------------------------------------------------------------------------- /molecule/default/tasks/awxbackup_test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # - name: Create the awx.ansible.com/v1beta1.AWXBackup 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: 'awx_v1beta1_awxbackup.yaml' 14 | # 15 | # - name: Add assertions here 16 | # assert: 17 | # that: false 18 | # fail_msg: FIXME Add real assertions for your operator 19 | -------------------------------------------------------------------------------- /molecule/default/tasks/awxmeshingress_test.yml: -------------------------------------------------------------------------------- 1 | # TODO: Add tests for AWXMeshIngress 2 | # --- 3 | # - name: Create the awx.ansible.com/v1alpha1.AWXMeshIngress 4 | # k8s: 5 | # state: present 6 | # namespace: '{{ namespace }}' 7 | # definition: "{{ lookup('template', '/'.join([samples_dir, cr_file])) | from_yaml }}" 8 | # wait: yes 9 | # wait_timeout: 300 10 | # wait_condition: 11 | # type: Successful 12 | # status: "True" 13 | # vars: 14 | # cr_file: 'awx_v1alpha1_awxmeshingress.yaml' 15 | 16 | # - name: Add assertions here 17 | # assert: 18 | # that: false 19 | # fail_msg: FIXME Add real assertions for your operator 20 | -------------------------------------------------------------------------------- /molecule/default/tasks/awxrestore_test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # - name: Create the awx.ansible.com/v1beta1.AWXRestore 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: 'awx_v1beta1_awxrestore.yaml' 14 | # 15 | # - name: Add assertions here 16 | # assert: 17 | # that: false 18 | # fail_msg: FIXME Add real assertions for your operator 19 | -------------------------------------------------------------------------------- /molecule/default/templates/awx_cr_molecule.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: awx.ansible.com/v1beta1 3 | kind: AWX 4 | metadata: 5 | name: example-awx 6 | labels: 7 | my/team: "foo" 8 | my/service: "bar" 9 | my/do-not-inherit: "yes" 10 | spec: 11 | {% if awx_image %} 12 | image: {{ awx_image }} 13 | {% endif %} 14 | {% if awx_version %} 15 | image_version: {{ awx_version }} 16 | {% endif %} 17 | {% if awx_ee_image %} 18 | control_plane_ee_image: {{ awx_ee_image }} 19 | ee_images: 20 | - image: {{ awx_ee_image }} 21 | name: AWX EE 22 | {% endif %} 23 | ingress_type: ingress 24 | ingress_path: /awx 25 | ingress_annotations: | 26 | kubernetes.io/ingress.class: nginx 27 | web_resource_requirements: 28 | requests: 29 | cpu: 20m 30 | memory: 32M 31 | task_resource_requirements: 32 | requests: 33 | cpu: 20m 34 | memory: 32M 35 | ee_resource_requirements: 36 | requests: 37 | cpu: 20m 38 | memory: 16M 39 | no_log: false 40 | postgres_resource_requirements: {} 41 | redis_resource_requirements: {} 42 | additional_labels: 43 | - my/team 44 | - my/service 45 | extra_settings: 46 | - setting: LOG_AGGREGATOR_LEVEL 47 | value: "'DEBUG'" 48 | task_readiness_period: 15 49 | {% if additional_fields is defined %} 50 | {{ additional_fields | to_nice_yaml | indent(2) }} 51 | {% endif %} 52 | -------------------------------------------------------------------------------- /molecule/default/utils/output_all_container_logs_for_pod.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get all container log in pod 3 | kubernetes.core.k8s_log: 4 | namespace: '{{ namespace }}' 5 | name: '{{ item.metadata.name }}' 6 | all_containers: true 7 | register: all_container_logs 8 | 9 | - name: Store logs in file 10 | ansible.builtin.copy: 11 | content: "{{ all_container_logs.log_lines | join('\n') }}" 12 | dest: '{{ debug_output_dir }}/{{ item.metadata.name }}.log' 13 | 14 | # TODO: all_containser option dump all of the output in a single output make it hard to read we probably should iterate through each of the container to get specific logs 15 | # also we should probably investigate toolings to do OpenShift style sosreport/must-gather for kind cluster or switch to microshift where sosreport is supported 16 | -------------------------------------------------------------------------------- /molecule/default/utils/output_k8s_resources.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Retrieve relevant k8s resources 3 | kubernetes.core.k8s_info: 4 | api_version: '{{ item.api_version }}' 5 | kind: '{{ item.kind }}' 6 | namespace: '{{ namespace }}' 7 | loop: 8 | - api_version: v1 9 | kind: Pod 10 | - api_version: apps/v1 11 | kind: Deployment 12 | - api_version: v1 13 | kind: Secret 14 | - api_version: v1 15 | kind: ConfigMap 16 | - api_version: "awx.ansible.com/v1beta1" 17 | kind: AWX 18 | register: debug_resources 19 | 20 | - name: debug print item.kind and item.metadata.name 21 | debug: 22 | msg: '{{ item.kind }}-{{ item.metadata.name }}' 23 | loop: "{{ debug_resources.results | map(attribute='resources') | flatten }}" 24 | 25 | - name: Output gathered resource to files 26 | ansible.builtin.copy: 27 | content: '{{ item | to_nice_json }}' 28 | dest: '{{ debug_output_dir }}/{{ item.kind }}-{{ item.metadata.name }}.json' 29 | loop: "{{ debug_resources.results | map(attribute='resources') | flatten }}" 30 | -------------------------------------------------------------------------------- /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 | - name: Perform awx tests 14 | block: 15 | - name: Import all test files from tasks/ 16 | ansible.builtin.include_tasks: '{{ item }}' 17 | with_fileglob: 18 | - tasks/awx_test.yml 19 | - tasks/awx_replicas_test.yml 20 | tags: 21 | - always 22 | rescue: 23 | - name: Create debug output directory 24 | ansible.builtin.file: 25 | path: '{{ debug_output_dir }}' 26 | state: directory 27 | tags: 28 | - always 29 | 30 | - name: Gather and output K8s resources 31 | ansible.builtin.include_tasks: utils/output_k8s_resources.yml 32 | tags: 33 | - always 34 | 35 | - name: Get all pods 36 | kubernetes.core.k8s_info: 37 | api_version: v1 38 | kind: Pod 39 | namespace: '{{ namespace }}' 40 | register: all_pods 41 | tags: 42 | - always 43 | 44 | - name: Get all container logs for all pods 45 | ansible.builtin.include_tasks: utils/output_all_container_logs_for_pod.yml 46 | loop: '{{ all_pods.resources }}' 47 | ignore_errors: yes 48 | tags: 49 | - always 50 | 51 | - name: Re-emit failure 52 | vars: 53 | failed_task: 54 | result: '{{ ansible_failed_result }}' 55 | ansible.builtin.fail: 56 | msg: '{{ failed_task }}' 57 | tags: 58 | - always 59 | -------------------------------------------------------------------------------- /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 | community.docker.docker_image: 10 | build: 11 | path: '{{ project_dir }}' 12 | pull: no 13 | args: 14 | DEFAULT_AWX_VERSION: '{{ default_awx_version }}' 15 | name: '{{ operator_image }}' 16 | tag: latest 17 | push: no 18 | source: build 19 | force_source: yes 20 | tags: 21 | - always 22 | 23 | - name: Load operator image into kind cluster 24 | command: kind load docker-image --name osdk-test '{{ operator_image }}' 25 | register: result 26 | changed_when: '"not yet present" in result.stdout' 27 | tags: 28 | - always 29 | 30 | 31 | - name: Load awx image into kind cluster 32 | command: kind load docker-image --name osdk-test '{{ awx_image }}:{{ awx_version }}' 33 | register: result 34 | changed_when: '"not yet present" in result.stdout' 35 | when: 36 | - awx_image is defined 37 | - awx_image != '' 38 | tags: 39 | - always 40 | 41 | - import_playbook: ../default/converge.yml 42 | -------------------------------------------------------------------------------- /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 | shell: | 9 | cat <- 27 | {{ this_backup['resources'][0]['metadata']['labels'] 28 | | dict2items | selectattr('key', 'in', additional_labels) 29 | }} 30 | when: 31 | - additional_labels | length 32 | - this_backup['resources'][0]['metadata']['labels'] 33 | 34 | - block: 35 | - include_tasks: init.yml 36 | 37 | - include_tasks: postgres.yml 38 | 39 | - include_tasks: awx-cro.yml 40 | 41 | - include_tasks: secrets.yml 42 | 43 | - name: Set flag signifying this backup was successful 44 | set_fact: 45 | backup_complete: true 46 | 47 | - include_tasks: cleanup.yml 48 | 49 | when: 50 | - this_backup['resources'][0]['status']['backupDirectory'] is not defined 51 | 52 | - name: Update status variables 53 | include_tasks: update_status.yml 54 | -------------------------------------------------------------------------------- /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 | container: "{{ ansible_operator_meta.name }}-db-management" 7 | command: >- 8 | bash -c 'rm -rf {{ backup_dir }}' 9 | -------------------------------------------------------------------------------- /roles/backup/tasks/dump_generated_secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Get secret name 4 | set_fact: 5 | _name: "{{ this_awx['resources'][0]['status'][item] }}" 6 | 7 | - name: Fail if status is not set on AWX CR 8 | block: 9 | - name: Set error message 10 | set_fact: 11 | error_msg: "{{ item }} status is not set on AWX 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/tasks/dump_ingress_tls_secrets.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Get secret 4 | k8s_info: 5 | version: v1 6 | kind: Secret 7 | namespace: '{{ ansible_operator_meta.namespace }}' 8 | name: "{{ item }}" 9 | register: _secret 10 | no_log: "{{ no_log }}" 11 | 12 | - name: Backup secret if exists 13 | block: 14 | - name: Set secret key 15 | set_fact: 16 | _data: "{{ _secret['resources'][0]['data'] }}" 17 | _type: "{{ _secret['resources'][0]['type'] }}" 18 | no_log: "{{ no_log }}" 19 | 20 | - name: Create and Add secret names and data to dictionary 21 | set_fact: 22 | secret_dict: "{{ secret_dict | default({}) | combine({item: { 'name': item, 'data': _data, 'type': _type }}) }}" 23 | no_log: "{{ no_log }}" 24 | when: _secret | length 25 | -------------------------------------------------------------------------------- /roles/backup/tasks/dump_receptor_secrets.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Get secret 4 | k8s_info: 5 | version: v1 6 | kind: Secret 7 | namespace: '{{ ansible_operator_meta.namespace }}' 8 | name: "{{ item }}" 9 | register: _secret 10 | no_log: "{{ no_log }}" 11 | 12 | - name: Backup secret if exists 13 | block: 14 | - name: Set secret key 15 | set_fact: 16 | _data: "{{ _secret['resources'][0]['data'] }}" 17 | _type: "{{ _secret['resources'][0]['type'] }}" 18 | no_log: "{{ no_log }}" 19 | 20 | - name: Create and Add secret names and data to dictionary 21 | set_fact: 22 | secret_dict: "{{ secret_dict | default({}) | combine({item: { 'name': item, 'data': _data, 'type': _type }}) }}" 23 | no_log: "{{ no_log }}" 24 | when: _secret | length 25 | -------------------------------------------------------------------------------- /roles/backup/tasks/dump_secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Get Secret Name 4 | set_fact: 5 | _name: "{{ awx_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/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/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 | -------------------------------------------------------------------------------- /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/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 | - secretKeySecret 7 | - adminPasswordSecret 8 | - broadcastWebsocketSecret 9 | - postgresConfigurationSecret 10 | 11 | - name: Dump secret names from awx spec and data into file 12 | include_tasks: dump_secret.yml 13 | loop: 14 | - route_tls_secret 15 | # ingress_tls_secret is deprecated in favor of ingress_hosts.tls_secret 16 | - ingress_tls_secret 17 | # LDAP is deprecated 18 | - ldap_cacert_secret 19 | - bundle_cacert_secret 20 | - ee_pull_credentials_secret 21 | 22 | - name: Dump ingress tls secret names from awx spec and data into file 23 | include_tasks: dump_ingress_tls_secrets.yml 24 | with_items: "{{ awx_spec.spec['ingress_hosts'] | default([]) | selectattr('tls_secret', 'defined') | map(attribute='tls_secret') | list }}" 25 | 26 | - name: Dump receptor secret names and data into file 27 | include_tasks: dump_receptor_secrets.yml 28 | loop: 29 | - '{{ deployment_name }}-receptor-ca' 30 | - '{{ deployment_name }}-receptor-work-signing' 31 | 32 | # image_pull_secret is deprecated in favor of image_pull_secrets 33 | - name: Dump image_pull_secret into file 34 | include_tasks: dump_secret.yml 35 | with_items: 36 | - image_pull_secret 37 | when: image_pull_secret is defined 38 | 39 | - name: Dump image_pull_secrets into file 40 | include_tasks: dump_secret.yml 41 | with_items: 42 | - image_pull_secrets 43 | when: image_pull_secrets | default([]) | length 44 | 45 | - name: Nest secrets under a single variable 46 | set_fact: 47 | secrets: {"secrets": '{{ secret_dict }}'} 48 | no_log: "{{ no_log }}" 49 | 50 | - name: Write postgres configuration to pvc 51 | k8s_cp: 52 | namespace: "{{ backup_pvc_namespace }}" 53 | pod: "{{ ansible_operator_meta.name }}-db-management" 54 | container: "{{ ansible_operator_meta.name }}-db-management" 55 | remote_path: "{{ backup_dir }}/secrets.yml" 56 | content: "{{ secrets | to_yaml }}" 57 | no_log: "{{ no_log }}" 58 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | {% if backup_storage_class is defined %} 14 | storageClassName: {{ backup_storage_class }} 15 | {% endif %} 16 | resources: 17 | requests: 18 | storage: {{ backup_storage_requirements | default('5Gi', true) }} 19 | -------------------------------------------------------------------------------- /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: awx.ansible.com/v1beta1 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/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 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 9 | spec: 10 | containers: 11 | - name: {{ ansible_operator_meta.name }}-db-management 12 | image: "{{ _postgres_image }}" 13 | imagePullPolicy: "{{ image_pull_policy }}" 14 | command: ["sleep", "infinity"] 15 | volumeMounts: 16 | - name: {{ ansible_operator_meta.name }}-backup 17 | mountPath: /backups 18 | readOnly: false 19 | {% if backup_resource_requirements is defined %} 20 | resources: 21 | {{ backup_resource_requirements | to_nice_yaml(indent=2) | indent(width=6, first=False) }} 22 | {%- endif %} 23 | {% if db_management_pod_node_selector %} 24 | nodeSelector: 25 | {{ db_management_pod_node_selector | indent(width=8) }} 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/backup/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | deployment_type: "awx" 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 | image_pull_policy: IfNotPresent 9 | -------------------------------------------------------------------------------- /roles/common/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | deployment_type: awx 3 | kind: 'AWX' 4 | api_version: '{{ deployment_type }}.ansible.com/v1beta1' 5 | 6 | # Used to determine some cluster specific logic regarding projects_persistence pvc permissions 7 | is_k8s: false 8 | is_openshift: false 9 | -------------------------------------------------------------------------------- /roles/common/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: Ansible 4 | description: AWX role for AWX 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 | - tower 21 | - awx 22 | - ansible 23 | - automation 24 | - ci 25 | - cd 26 | - deployment 27 | 28 | dependencies: [] 29 | 30 | collections: 31 | - kubernetes.core 32 | - operator_sdk.util 33 | -------------------------------------------------------------------------------- /roles/common/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Get information about the cluster 4 | set_fact: 5 | api_groups: "{{ lookup('k8s', cluster_info='api_groups') }}" 6 | when: 7 | - not is_openshift | bool 8 | - not is_k8s | bool 9 | 10 | - name: Determine the cluster type 11 | set_fact: 12 | is_openshift: "{{ True if 'route.openshift.io' in api_groups else False }}" 13 | is_k8s: "{{ False if 'route.openshift.io' in api_groups else True }}" 14 | when: 15 | - not is_openshift | bool 16 | - not is_k8s | bool 17 | 18 | # Indicate what kind of cluster we are in (OpenShift or Kubernetes). 19 | - debug: 20 | msg: "CLUSTER TYPE: is_openshift={{ is_openshift }}; is_k8s={{ is_k8s }}" 21 | -------------------------------------------------------------------------------- /roles/common/templates/labels/additional_labels.yaml.j2: -------------------------------------------------------------------------------- 1 | {% for item in additional_labels_items | default([]) %} 2 | {{ item.key }}: '{{ item.value }}' 3 | {% endfor %} 4 | -------------------------------------------------------------------------------- /roles/common/templates/labels/common.yaml.j2: -------------------------------------------------------------------------------- 1 | # https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ 2 | app.kubernetes.io/part-of: '{{ ansible_operator_meta.name }}' 3 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 4 | app.kubernetes.io/component: '{{ deployment_type }}' 5 | app.kubernetes.io/operator-version: '{{ lookup("env", "OPERATOR_VERSION") }}' 6 | {{ lookup("template", "../common/templates/labels/additional_labels.yaml.j2") }} 7 | -------------------------------------------------------------------------------- /roles/common/templates/labels/version.yaml.j2: -------------------------------------------------------------------------------- 1 | app.kubernetes.io/version: '{{ _image.split(':')[-1] | truncate(63, True, '', 0) }}' 2 | -------------------------------------------------------------------------------- /roles/installer/README.md: -------------------------------------------------------------------------------- 1 | AWX 2 | ======= 3 | 4 | This role builds and maintains an Ansible Tower instance inside of Kubernetes. 5 | 6 | Requirements 7 | ------------ 8 | 9 | TODO. 10 | 11 | Role Variables 12 | -------------- 13 | 14 | See `defaults/main.yml` for all the role variables that you can override. 15 | 16 | TODO: add variable description table. 17 | 18 | Dependencies 19 | ------------ 20 | 21 | N/A 22 | 23 | Example Playbook 24 | ---------------- 25 | 26 | - hosts: localhost 27 | connection: local 28 | roles: 29 | - installer 30 | 31 | License 32 | ------- 33 | 34 | MIT / BSD 35 | -------------------------------------------------------------------------------- /roles/installer/files/pre-stop/termination-env: -------------------------------------------------------------------------------- 1 | # file, which when exists, indicates that `master` script has successfully 2 | # completed pre-stop script execution 3 | marker_file="${PRE_STOP_MARKER_FILE:-/var/lib/pre-stop/.termination_marker}" 4 | 5 | # file which the running `master` script continuously updates (mtime) to 6 | # indicate it's still running. this file is then read by `watcher`s to 7 | # understand if they still have to wait for `termination_marker` 8 | heartbeat_file="${PRE_STOP_HEARTBEAT_FILE:-/var/lib/pre-stop/.heartbeat}" 9 | 10 | # file which: 11 | # * `watcher`s create when they bail out because they didn't see the 12 | # `heartbeat_file` to be updated within `$heartbeat_failed_threshold`; 13 | # * `master` creates when its handler command fails; 14 | # when scripts see such file, they also give up 15 | bailout_file="${PRE_STOP_BAILOUT_FILE:-/var/lib/pre-stop/.bailout}" 16 | heartbeat_threshold="${PRE_STOP_HEARTBEAT_THRESHOLD:-60}" 17 | 18 | # where the scripts' stdout/stderr are streamed 19 | stdout="${PRE_STOP_STDOUT:-/proc/1/fd/1}" 20 | stderr="${PRE_STOP_STDERR:-/proc/1/fd/2}" 21 | 22 | # command the `master` script executes, which when successfully finishes, 23 | # causes the script to create the `marker_file` 24 | handler="${PRE_STOP_HANDLER:-bash -c \"PYTHONUNBUFFERED=x awx-manage disable_instance --wait --retry=inf\"}" 25 | 26 | log_prefix="${PRE_STOP_LOG_PREFIX:-preStop.exec}" 27 | [[ -n ${PRE_STOP_LOG_ROLE} ]] && log_prefix="${log_prefix}] [$PRE_STOP_LOG_ROLE" 28 | 29 | # interval at which `watcher`s check for `marker_file` presence 30 | recheck_sleep="${PRE_STOP_RECHECK_SLEEP:-1}" 31 | # interval at which `watcher`s report into $stdout that they are still watching 32 | report_every="${PRE_STOP_REPORT_EVERY:-30}" 33 | 34 | function log { 35 | printf "[%s] $1\n" "$log_prefix" "${@:2}" 36 | } 37 | 38 | function parameters_string { 39 | for param in "$@"; do 40 | printf "%s=\"%s\"\n" "$param" "${!param}" 41 | done | paste -s -d ' ' 42 | } 43 | 44 | function check_bailout { 45 | if [[ -f $bailout_file ]]; then 46 | log "\"%s\" file has been detected, accepting bail out signal and failing the hook script" \ 47 | "$bailout_file" 48 | exit 1 49 | fi 50 | } 51 | 52 | function check_heartbeat { 53 | if [[ -f $heartbeat_file ]]; then 54 | delta=$(( $(date +%s) - $(stat -c %Y "$heartbeat_file") )) 55 | else 56 | delta=$(( $(date +%s) - $1 )) 57 | fi 58 | 59 | if [[ $delta -gt $heartbeat_threshold ]]; then 60 | log "The heartbeat file hasn't been updated since %ss, which is above the threshold of %ds, assuming the master is not operating and failing the hook script" \ 61 | $delta 62 | $heartbeat_threshold 63 | touch "$bailout_file" 64 | exit 1 65 | fi 66 | } 67 | -------------------------------------------------------------------------------- /roles/installer/files/pre-stop/termination-master: -------------------------------------------------------------------------------- 1 | #/usr/bin/env bash 2 | 3 | PRE_STOP_LOG_ROLE="${PRE_STOP_LOG_ROLE:-master}" 4 | source $(dirname "$0")/termination-env 5 | 6 | { 7 | 8 | log "The hook has started: %s" \ 9 | "$(parameters_string \ 10 | "marker_file" \ 11 | "heartbeat_file" \ 12 | "bailout_file" \ 13 | "handler" \ 14 | )" 15 | 16 | touch "$heartbeat_file" 17 | 18 | set -o pipefail 19 | eval "$handler" 2>&1 | while IFS= read -r line; do 20 | # we check the files here and break early, but overall script termination 21 | # happens later - as we need to distinguish between files detection and 22 | # command failure, while bash doesn't offer a simple way to do this here 23 | # inside the loop (`exit` does not terminate the script) 24 | [[ -f $bailout_file ]] && break 25 | [[ -f $marker_file ]] && break 26 | 27 | log "[handler] %s" "$line" 28 | touch "$heartbeat_file" 29 | done 30 | ec=$? 31 | set +o pipefail 32 | 33 | # process various cases in specific order 34 | check_bailout 35 | 36 | if [[ -f $marker_file ]]; then 37 | log "Done! The marker file has been detected, assuming some other instance of the script has run to completion" 38 | exit 0 39 | elif [[ $ec -ne 0 ]]; then 40 | log "The handler has failed with \"%d\" exit code, failing the hook script too" \ 41 | $ec 42 | # signal others to bail out 43 | touch "$bailout_file" 44 | exit $ec 45 | else 46 | log "Done! Generating the marker file allowing to proceed to termination" 47 | touch "$marker_file" 48 | fi 49 | 50 | } > "$stdout" 2> "$stderr" 51 | -------------------------------------------------------------------------------- /roles/installer/files/pre-stop/termination-waiter: -------------------------------------------------------------------------------- 1 | #/usr/bin/env bash 2 | 3 | PRE_STOP_LOG_ROLE="${PRE_STOP_LOG_ROLE:-waiter}" 4 | source $(dirname "$0")/termination-env 5 | 6 | { 7 | 8 | log "The hook has started: %s" \ 9 | "$(parameters_string \ 10 | "marker_file" \ 11 | "heartbeat_file" \ 12 | "bailout_file" \ 13 | "recheck_sleep" \ 14 | "report_every" \ 15 | )" 16 | 17 | n=0 18 | checks_started=$(date +%s) 19 | 20 | while ! [[ -f $marker_file ]]; do 21 | check_bailout 22 | check_heartbeat $checks_started 23 | 24 | if [[ $(($n % $report_every)) -eq 0 ]]; then 25 | log "Waiting for the marker file to be accessible..." 26 | fi 27 | n=$(($n + 1)) 28 | sleep $recheck_sleep 29 | done 30 | 31 | log "The marker file found, exiting to proceed to termination" 32 | 33 | } > "$stdout" 2> "$stderr" 34 | -------------------------------------------------------------------------------- /roles/installer/filter_plugins/cpu_string_to_decimal.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Ansible Project 2 | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 3 | 4 | from __future__ import (absolute_import, division, print_function) 5 | __metaclass__ = type 6 | 7 | from ansible.errors import AnsibleFilterError 8 | 9 | __ERROR_MSG = "Not a valid cpu value. Cannot process value" 10 | 11 | class FilterModule(object): 12 | def filters(self): 13 | return { 14 | 'cpu_string_to_decimal': self.cpu_string_to_decimal 15 | } 16 | def cpu_string_to_decimal(self, cpu_string): 17 | 18 | # verify if task_output is a dict 19 | if not isinstance(cpu_string, str): 20 | raise AnsibleFilterError(__ERROR_MSG) 21 | 22 | if cpu_string[-1] == 'm': 23 | cpu = int(cpu_string[:-1])//1000 24 | else: 25 | cpu = int(cpu_string) 26 | 27 | return cpu 28 | -------------------------------------------------------------------------------- /roles/installer/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: Ansible 4 | description: AWX role for AWX 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 | - tower 21 | - awx 22 | - ansible 23 | - automation 24 | - ci 25 | - cd 26 | - deployment 27 | 28 | dependencies: 29 | - role: common 30 | 31 | collections: 32 | - kubernetes.core 33 | - operator_sdk.util 34 | -------------------------------------------------------------------------------- /roles/installer/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 | 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 | 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 | 39 | when: not _admin_password_secret['resources'] | default([]) | length 40 | 41 | - name: Set admin password secret 42 | set_fact: 43 | __admin_password_secret: '{{ _generated_admin_password["resources"] | default([]) | length | ternary(_generated_admin_password, _admin_password_secret) }}' 44 | no_log: "{{ no_log }}" 45 | 46 | - name: Store admin password 47 | set_fact: 48 | admin_password: "{{ __admin_password_secret['resources'][0]['data']['password'] | b64decode }}" 49 | no_log: "{{ no_log }}" 50 | -------------------------------------------------------------------------------- /roles/installer/tasks/broadcast_websocket_configuration.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check for specified broadcast websocket secret configuration 3 | k8s_info: 4 | kind: Secret 5 | namespace: '{{ ansible_operator_meta.namespace }}' 6 | name: '{{ broadcast_websocket_secret }}' 7 | register: _custom_broadcast_websocket 8 | no_log: "{{ no_log }}" 9 | when: broadcast_websocket_secret | length 10 | 11 | - name: Check for default broadcast websocket secret configuration 12 | k8s_info: 13 | kind: Secret 14 | namespace: '{{ ansible_operator_meta.namespace }}' 15 | name: '{{ ansible_operator_meta.name }}-broadcast-websocket' 16 | register: _default_broadcast_websocket 17 | no_log: "{{ no_log }}" 18 | 19 | - name: Set broadcast websocket secret 20 | set_fact: 21 | # yamllint disable-line rule:line-length 22 | _broadcast_websocket_secret: '{{ _custom_broadcast_websocket["resources"] | default([]) | length | ternary(_custom_broadcast_websocket, _default_broadcast_websocket) }}' # noqa 204 23 | no_log: "{{ no_log }}" 24 | 25 | - block: 26 | - name: Create broadcast websocket secret 27 | k8s: 28 | apply: true 29 | definition: "{{ lookup('template', 'secrets/broadcast_websocket_secret.yaml.j2') }}" 30 | no_log: "{{ no_log }}" 31 | 32 | - name: Read broadcast websocket secret 33 | k8s_info: 34 | kind: Secret 35 | namespace: '{{ ansible_operator_meta.namespace }}' 36 | name: '{{ ansible_operator_meta.name }}-broadcast-websocket' 37 | register: _generated_broadcast_websocket 38 | no_log: "{{ no_log }}" 39 | 40 | when: not _broadcast_websocket_secret['resources'] | default([]) | length 41 | 42 | - name: Set broadcast websocket secret 43 | set_fact: 44 | # yamllint disable-line rule:line-length 45 | __broadcast_websocket_secret: '{{ _generated_broadcast_websocket["resources"] | default([]) | length | ternary(_generated_broadcast_websocket, _broadcast_websocket_secret) }}' # noqa 204 46 | no_log: "{{ no_log }}" 47 | 48 | - name: Store broadcast websocket secret name 49 | set_fact: 50 | broadcast_websocket_secret_value: "{{ __broadcast_websocket_secret['resources'][0]['data']['secret'] | b64decode }}" 51 | no_log: "{{ no_log }}" 52 | -------------------------------------------------------------------------------- /roles/installer/tasks/check_existing.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Check for presence of Deployment 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/part-of={{ ansible_operator_meta.name }}' 10 | - 'app.kubernetes.io/managed-by={{ deployment_type }}-operator' 11 | - 'app.kubernetes.io/component={{ deployment_type }}' 12 | register: _deployments 13 | 14 | - name: Set previous_version if deployment exists 15 | when: _deployments.resources | length > 0 16 | block: 17 | - name: Check for existing deployment 18 | kubernetes.core.k8s_info: 19 | api_version: "{{ api_version }}" 20 | kind: "{{ kind }}" 21 | namespace: "{{ ansible_operator_meta.namespace }}" 22 | name: "{{ ansible_operator_meta.name }}" 23 | register: existing_cr 24 | 25 | - name: Set previous_version version based on AWX CR version status 26 | ansible.builtin.set_fact: 27 | previous_version: "{{ existing_cr.resources[0].status.version }}" 28 | when: existing_cr['resources'] | length 29 | 30 | - name: If previous_version is less than or equal to gating_version, set upgraded_from to previous_version 31 | ansible.builtin.set_fact: 32 | upgraded_from: "{{ previous_version }}" 33 | when: 34 | - previous_version is defined 35 | - previous_version is version_compare(gating_version, '<') 36 | -------------------------------------------------------------------------------- /roles/installer/tasks/cleanup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - block: 3 | - name: Define secrets name 4 | set_fact: 5 | _admin_password: '{{ admin_password_secret | length | ternary(admin_password_secret, ansible_operator_meta.name + "-admin-password") }}' 6 | _secret_key: '{{ secret_key_secret | length | ternary(secret_key_secret, ansible_operator_meta.name + "-secret-key") }}' 7 | # yamllint disable-line rule:line-length 8 | _broadcast_websocket_secret: '{{ broadcast_websocket_secret | length | ternary(broadcast_websocket_secret, ansible_operator_meta.name + "-broadcast-websocket") }}' # noqa 204 9 | # yamllint disable-line rule:line-length 10 | _postgres_configuration: '{{ postgres_configuration_secret | length | ternary(postgres_configuration_secret, ansible_operator_meta.name + "-postgres-configuration") }}' # noqa 204 11 | 12 | - name: Remove ownerReferences reference 13 | k8s: 14 | definition: 15 | apiVersion: v1 16 | kind: Secret 17 | metadata: 18 | name: '{{ item }}' 19 | namespace: '{{ ansible_operator_meta.namespace }}' 20 | ownerReferences: null 21 | loop: 22 | - '{{ _admin_password }}' 23 | - '{{ _secret_key }}' 24 | - '{{ _postgres_configuration }}' 25 | - '{{ _broadcast_websocket_secret }}' 26 | - '{{ ansible_operator_meta.name }}-receptor-ca' 27 | - '{{ ansible_operator_meta.name }}-receptor-work-signing' 28 | no_log: "{{ no_log }}" 29 | 30 | when: not garbage_collect_secrets | bool 31 | -------------------------------------------------------------------------------- /roles/installer/tasks/enable_metrics_utility.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Setup PVC if using directory ship target 3 | block: 4 | 5 | # Check to make sure provided pvc exists 6 | - name: Check provided PVC claim exists 7 | kubernetes.core.k8s_info: 8 | name: "{{ _metrics_utility_pvc_claim }}" 9 | kind: PersistentVolumeClaim 10 | namespace: "{{ ansible_operator_meta.namespace }}" 11 | when: 12 | - _metrics_utility_pvc_claim | length 13 | 14 | - name: Create PVC for metrics-utility 15 | kubernetes.core.k8s: 16 | kind: PersistentVolumeClaim 17 | definition: "{{ lookup('template', 'storage/metrics-utility.yaml.j2') }}" 18 | 19 | when: _metrics_utility_ship_target == "directory" 20 | 21 | - name: Create default metrics-utility Kubernetes CronJobs 22 | kubernetes.core.k8s: 23 | definition: "{{ lookup('template', item.template) }}" 24 | apply: true 25 | wait: true 26 | vars: 27 | cronjob_name: "{{ item.name }}" 28 | loop: 29 | - {name: 'metrics-utility-gather', template: 'cronjobs/metrics-utility-gather.yaml.j2'} 30 | - {name: 'metrics-utility-report', template: 'cronjobs/metrics-utility-report.yaml.j2'} 31 | -------------------------------------------------------------------------------- /roles/installer/tasks/enable_metrics_utility_console.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create metrics-utility Kubernetes CronJob for Red Hat Hybrid Cloud Console 3 | kubernetes.core.k8s: 4 | definition: "{{ lookup('template', item.template) }}" 5 | apply: true 6 | wait: true 7 | vars: 8 | cronjob_name: "{{ item.name }}" 9 | metrics_utility_ship_target: crc # TODO - Update to console when changed 10 | loop: 11 | - {name: 'metrics-utility-gather-console', template: 'cronjobs/metrics-utility-gather.yaml.j2'} 12 | -------------------------------------------------------------------------------- /roles/installer/tasks/idle_deployment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Scale down AWX 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 }}-task' 15 | - '{{ ansible_operator_meta.name }}-web' 16 | 17 | - name: Get database configuration 18 | include_tasks: database_configuration.yml 19 | 20 | - name: Scale down PostgreSQL Statefulset 21 | kubernetes.core.k8s: 22 | state: present 23 | definition: 24 | apiVersion: apps/v1 25 | kind: StatefulSet 26 | metadata: 27 | name: "{{ ansible_operator_meta.name }}-postgres-{{ supported_pg_version }}" 28 | namespace: "{{ ansible_operator_meta.namespace }}" 29 | spec: 30 | replicas: 0 31 | when: managed_database 32 | 33 | - name: End Playbook 34 | ansible.builtin.meta: end_play 35 | -------------------------------------------------------------------------------- /roles/installer/tasks/load_bundle_cacert_secret.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/installer/tasks/load_ldap_cacert_secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Log LDAP deprecated message if applicable 3 | debug: 4 | msg: | 5 | [DEPRECATION WARNING] LDAP is deprecated, but ldap_cacert_secret configuration is set. 6 | when: 7 | - ldap_cacert_secret != '' 8 | 9 | - name: Retrieve LDAP CA Certificate Secret 10 | k8s_info: 11 | kind: Secret 12 | namespace: '{{ ansible_operator_meta.namespace }}' 13 | name: '{{ ldap_cacert_secret }}' 14 | register: ldap_cacert 15 | no_log: "{{ no_log }}" 16 | 17 | - name: Load LDAP CA Certificate Secret content 18 | set_fact: 19 | ldap_cacert_ca_crt: '{{ ldap_cacert["resources"][0]["data"]["ldap-ca.crt"] | b64decode }}' 20 | no_log: "{{ no_log }}" 21 | when: '"ldap-ca.crt" in ldap_cacert["resources"][0]["data"]' 22 | -------------------------------------------------------------------------------- /roles/installer/tasks/load_ldap_password_secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Log LDAP deprecated message if applicable 3 | debug: 4 | msg: | 5 | [DEPRECATION WARNING] LDAP is deprecated, but ldap_password_secret configuration is set. 6 | when: 7 | - ldap_password_secret != '' 8 | 9 | - name: Retrieve LDAP bind password Secret 10 | k8s_info: 11 | kind: Secret 12 | namespace: '{{ ansible_operator_meta.namespace }}' 13 | name: '{{ ldap_password_secret }}' 14 | register: ldap_password 15 | no_log: "{{ no_log }}" 16 | 17 | - name: Load LDAP bind password Secret content 18 | set_fact: 19 | ldap_bind_password: '{{ ldap_password["resources"][0]["data"]["ldap-password"] | b64decode }}' 20 | no_log: "{{ no_log }}" 21 | when: '"ldap-password" in ldap_password["resources"][0]["data"]' 22 | -------------------------------------------------------------------------------- /roles/installer/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 | -------------------------------------------------------------------------------- /roles/installer/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Idle AWX 3 | include_tasks: idle_deployment.yml 4 | when: idle_deployment | bool 5 | 6 | - name: Check for presence of old awx Deployment 7 | k8s_info: 8 | api_version: apps/v1 9 | kind: Deployment 10 | name: "{{ ansible_operator_meta.name }}" 11 | namespace: "{{ ansible_operator_meta.namespace }}" 12 | register: awx_deployment 13 | 14 | - name: Check for presence of awx-task Deployment 15 | k8s_info: 16 | api_version: apps/v1 17 | kind: Deployment 18 | name: "{{ ansible_operator_meta.name }}-task" 19 | namespace: "{{ ansible_operator_meta.namespace }}" 20 | register: awx_task_deployment 21 | 22 | - name: Check for presence of awx-web Deployment 23 | k8s_info: 24 | api_version: apps/v1 25 | kind: Deployment 26 | name: "{{ ansible_operator_meta.name }}-web" 27 | namespace: "{{ ansible_operator_meta.namespace }}" 28 | register: awx_web_deployment 29 | 30 | - name: Check for existing deployment for previous version 31 | include_tasks: check_existing.yml 32 | when: gating_version | length 33 | 34 | - name: Start installation if auto_upgrade is true 35 | include_tasks: install.yml 36 | when: 37 | - auto_upgrade | bool 38 | 39 | - name: Start installation if auto_upgrade is false and deployment is missing 40 | include_tasks: install.yml 41 | when: 42 | - not (auto_upgrade | bool) 43 | - not (awx_deployment['resources'] | length > 0) 44 | - not (awx_web_deployment['resources'] | length > 0 and awx_task_deployment['resources'] | length > 0) 45 | -------------------------------------------------------------------------------- /roles/installer/tasks/migrate_schema.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Check for pending migrations 4 | k8s_exec: 5 | namespace: "{{ ansible_operator_meta.namespace }}" 6 | pod: "{{ awx_web_pod_name }}" 7 | container: "{{ ansible_operator_meta.name }}-web" 8 | command: >- 9 | bash -c "awx-manage showmigrations | grep -v '[X]' | grep '[ ]' | wc -l" 10 | changed_when: false 11 | when: awx_web_pod_name != '' 12 | register: database_check 13 | 14 | - block: 15 | - name: Get version of controller for tracking 16 | k8s_exec: 17 | namespace: "{{ ansible_operator_meta.namespace }}" 18 | pod: "{{ awx_web_pod_name }}" 19 | container: "{{ ansible_operator_meta.name }}-web" 20 | command: >- 21 | bash -c "awx-manage --version" 22 | changed_when: false 23 | register: version_check 24 | 25 | - name: Sanitize instance version 26 | set_fact: 27 | version: "{{ version_check.stdout | replace('+', '-') | trim }}" 28 | 29 | # It is possible to do a wait on this task to create the job and wait 30 | # until it completes. Unfortunately, if the job doesn't wait finish within 31 | # the timeout period that is considered an error. We only want this to 32 | # error if there is an issue with creating the job. 33 | - name: Create kubernetes job to perform the migration 34 | k8s: 35 | apply: yes 36 | definition: "{{ lookup('template', 'jobs/migration.yaml.j2') }}" 37 | register: migrate_result 38 | 39 | # This task is really only necessary for new installations. We need to 40 | # ensure the database has a schema loaded before continuing with the 41 | # initialization of admin user, etc. 42 | - name: Watch for the migration job to finish 43 | k8s_info: 44 | api_version: batch/v1 45 | kind: Job 46 | namespace: "{{ ansible_operator_meta.namespace }}" 47 | name: "{{ ansible_operator_meta.name }}-migration-{{ version }}" 48 | register: result 49 | until: 50 | - result.resources[0].status.succeeded is defined 51 | - result.resources[0].status.succeeded == 1 52 | retries: 180 53 | delay: 5 54 | ignore_errors: true 55 | 56 | when: 57 | - database_check is defined 58 | - (database_check.stdout|trim) != '0' 59 | -------------------------------------------------------------------------------- /roles/installer/tasks/scale_down_deployment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check for presence of Deployment 3 | k8s_info: 4 | api_version: apps/v1 5 | kind: Deployment 6 | namespace: "{{ ansible_operator_meta.namespace }}" 7 | label_selectors: 8 | - 'app.kubernetes.io/part-of={{ ansible_operator_meta.name }}' 9 | - 'app.kubernetes.io/managed-by={{ deployment_type }}-operator' 10 | - 'app.kubernetes.io/component={{ deployment_type }}' 11 | register: _deployments 12 | 13 | - name: Scale down Deployment for migration 14 | kubernetes.core.k8s_scale: 15 | api_version: apps/v1 16 | kind: Deployment 17 | name: "{{ item }}" 18 | namespace: "{{ ansible_operator_meta.namespace }}" 19 | replicas: 0 20 | wait: yes 21 | loop: "{{ _deployments.resources | map(attribute='metadata.name') | list }}" 22 | when: _deployments.resources | length 23 | -------------------------------------------------------------------------------- /roles/installer/tasks/secret_key_configuration.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check for specified secret key configuration 3 | k8s_info: 4 | kind: Secret 5 | namespace: '{{ ansible_operator_meta.namespace }}' 6 | name: '{{ secret_key_secret }}' 7 | register: _custom_secret_key 8 | no_log: "{{ no_log }}" 9 | when: secret_key_secret | length 10 | 11 | - name: Check for default secret key configuration 12 | k8s_info: 13 | kind: Secret 14 | namespace: '{{ ansible_operator_meta.namespace }}' 15 | name: '{{ ansible_operator_meta.name }}-secret-key' 16 | register: _default_secret_key 17 | no_log: "{{ no_log }}" 18 | 19 | - name: Set secret key secret 20 | set_fact: 21 | _secret_key_secret: '{{ _custom_secret_key["resources"] | default([]) | length | ternary(_custom_secret_key, _default_secret_key) }}' 22 | no_log: "{{ no_log }}" 23 | 24 | - block: 25 | - name: Create secret key secret 26 | k8s: 27 | apply: true 28 | definition: "{{ lookup('template', 'secrets/secret_key.yaml.j2') }}" 29 | no_log: "{{ no_log }}" 30 | 31 | - name: Read secret key secret 32 | k8s_info: 33 | kind: Secret 34 | namespace: '{{ ansible_operator_meta.namespace }}' 35 | name: '{{ ansible_operator_meta.name }}-secret-key' 36 | register: _generated_secret_key 37 | no_log: "{{ no_log }}" 38 | 39 | when: not _secret_key_secret['resources'] | default([]) | length 40 | 41 | - name: Set secret key secret 42 | set_fact: 43 | secret_key: '{{ _generated_secret_key["resources"] | default([]) | length | ternary(_generated_secret_key, _secret_key_secret) }}' 44 | no_log: "{{ no_log }}" 45 | 46 | - name: Store secret key secret name 47 | set_fact: 48 | secret_key_secret_name: "{{ secret_key['resources'][0]['metadata']['name'] }}" 49 | no_log: "{{ no_log }}" 50 | -------------------------------------------------------------------------------- /roles/installer/tasks/set_images.yml: -------------------------------------------------------------------------------- 1 | # For disconnected environments, images must be set based on the values of `RELATED_IMAGE_` variables 2 | --- 3 | - name: Set default awx init container image 4 | set_fact: 5 | _default_init_container_image: "{{ _init_container_image }}:{{ _init_container_image_version }}" 6 | 7 | - name: Set user provided awx init image 8 | set_fact: 9 | _custom_init_container_image: "{{ init_container_image }}:{{ init_container_image_version }}" 10 | when: 11 | - init_container_image | default('_undefined',true) != '_undefined' 12 | - init_container_image_version | default('_undefined',true) != '_undefined' 13 | 14 | - name: Set Init image URL 15 | set_fact: 16 | _init_container_image: >- 17 | {{ _custom_init_container_image | 18 | default(lookup('env', 'RELATED_IMAGE_AWX_INIT_CONTAINER')) | 19 | default(_default_init_container_image, true) }} 20 | 21 | - name: Set default awx init projects container image 22 | set_fact: 23 | _default_init_projects_container_image: "{{ _init_projects_container_image }}" 24 | 25 | - name: Set user provided awx init projects image 26 | set_fact: 27 | _custom_init_projects_container_image: "{{ init_projects_container_image }}" 28 | when: 29 | - init_projects_container_image | default([]) | length 30 | 31 | - name: Set Init projects image URL 32 | set_fact: 33 | _init_projects_container_image: >- 34 | {{ _custom_init_projects_container_image | 35 | default(lookup('env', 'RELATED_IMAGE_AWX_INIT_PROJECTS_CONTAINER')) | 36 | default(_default_init_projects_container_image, true) }} 37 | -------------------------------------------------------------------------------- /roles/installer/templates/common/volume_mounts/extra_settings_files.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if extra_settings_files.configmaps is defined and extra_settings_files.configmaps | length %} 2 | {% for configmap in extra_settings_files.configmaps %} 3 | - name: {{ ansible_operator_meta.name }}-{{ configmap.key | replace('_', '-') | replace('.', '-') | lower }}-configmap 4 | mountPath: "/etc/tower/conf.d/{{ configmap.key }}" 5 | subPath: {{ configmap.key }} 6 | readOnly: true 7 | {% endfor %} 8 | {% endif %} 9 | {% if extra_settings_files.secrets is defined and extra_settings_files.secrets | length %} 10 | {% for secret in extra_settings_files.secrets %} 11 | - name: {{ ansible_operator_meta.name }}-{{ secret.key | replace('_', '-') | replace('.', '-') | lower }}-secret 12 | mountPath: "/etc/tower/conf.d/{{ secret.key }}" 13 | subPath: {{ secret.key }} 14 | readOnly: true 15 | {% endfor %} 16 | {% endif %} 17 | -------------------------------------------------------------------------------- /roles/installer/templates/common/volumes/extra_settings_files.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if extra_settings_files.configmaps is defined and extra_settings_files.configmaps | length %} 2 | {% for configmap in extra_settings_files.configmaps %} 3 | - name: {{ ansible_operator_meta.name }}-{{ configmap.key | replace('_', '-') | replace('.', '-') | lower }}-configmap 4 | configMap: 5 | name: {{ configmap.name }} 6 | items: 7 | - key: {{ configmap.key }} 8 | path: {{ configmap.key }} 9 | {% endfor %} 10 | {% endif %} 11 | {% if extra_settings_files.secrets is defined and extra_settings_files.secrets | length %} 12 | {% for secret in extra_settings_files.secrets %} 13 | - name: {{ ansible_operator_meta.name }}-{{ secret.key | replace('_', '-') | replace('.', '-') | lower }}-secret 14 | secret: 15 | secretName: {{ secret.name }} 16 | items: 17 | - key: {{ secret.key }} 18 | path: {{ secret.key }} 19 | {% endfor %} 20 | {% endif %} 21 | -------------------------------------------------------------------------------- /roles/installer/templates/configmaps/pre_stop_scripts.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if termination_grace_period_seconds is defined %} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: '{{ ansible_operator_meta.name }}-{{ deployment_type }}-pre-stop-scripts' 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | labels: 8 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 9 | data: 10 | termination-master: | 11 | {{ lookup("file", "files/pre-stop/termination-master") | indent(width=4) | trim }} 12 | termination-waiter: | 13 | {{ lookup("file", "files/pre-stop/termination-waiter") | indent(width=4) | trim }} 14 | termination-env: | 15 | {{ lookup("file", "files/pre-stop/termination-env") | indent(width=4) | trim }} 16 | {% endif %} 17 | -------------------------------------------------------------------------------- /roles/installer/templates/networking/service.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: '{{ ansible_operator_meta.name }}-service' 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | labels: 8 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 9 | {{ service_labels | indent(width=4) }} 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: 8052 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: 8052 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: 8053 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: 8052 40 | name: https 41 | {% elif service_type | lower == 'loadbalancer' and loadbalancer_protocol | lower != 'https' %} 42 | - port: {{ loadbalancer_port }} 43 | protocol: TCP 44 | targetPort: 8052 45 | name: http 46 | {% endif %} 47 | selector: 48 | app.kubernetes.io/name: '{{ ansible_operator_meta.name }}-web' 49 | app.kubernetes.io/managed-by: '{{ deployment_type }}-operator' 50 | app.kubernetes.io/component: '{{ deployment_type }}' 51 | {% if service_type | lower == "nodeport" %} 52 | type: NodePort 53 | {% elif service_type | lower == "loadbalancer" %} 54 | type: LoadBalancer 55 | {% if loadbalancer_ip is defined and loadbalancer_ip | length %} 56 | loadbalancerip: '{{ loadbalancer_ip }}' 57 | {% endif %} 58 | {% if loadbalancer_class is defined and loadbalancer_class | length %} 59 | loadBalancerClass: {{ loadbalancer_class }} 60 | {% endif %} 61 | {% else %} 62 | type: ClusterIP 63 | {% endif %} 64 | -------------------------------------------------------------------------------- /roles/installer/templates/rbac/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"] 24 | verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] 25 | - apiGroups: [""] 26 | resources: ["pods/log"] 27 | verbs: ["get"] 28 | - apiGroups: [""] 29 | resources: ["pods/attach"] 30 | verbs: ["create"] 31 | - apiGroups: [""] 32 | resources: ["secrets"] 33 | verbs: ["get", "create", "delete"] 34 | 35 | --- 36 | kind: RoleBinding 37 | apiVersion: rbac.authorization.k8s.io/v1 38 | metadata: 39 | name: '{{ ansible_operator_meta.name }}' 40 | namespace: '{{ ansible_operator_meta.namespace }}' 41 | labels: 42 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 43 | subjects: 44 | - kind: ServiceAccount 45 | name: '{{ ansible_operator_meta.name }}' 46 | roleRef: 47 | apiGroup: rbac.authorization.k8s.io 48 | kind: Role 49 | name: '{{ ansible_operator_meta.name }}' 50 | -------------------------------------------------------------------------------- /roles/installer/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 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 9 | stringData: 10 | password: '{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}' 11 | -------------------------------------------------------------------------------- /roles/installer/templates/secrets/app_credentials.yaml.j2: -------------------------------------------------------------------------------- 1 | # AWX Secret Configurations 2 | --- 3 | apiVersion: v1 4 | kind: Secret 5 | metadata: 6 | name: '{{ ansible_operator_meta.name }}-app-credentials' 7 | namespace: '{{ ansible_operator_meta.namespace }}' 8 | labels: 9 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 10 | data: 11 | credentials.py: "{{ lookup('template', 'settings/credentials.py.j2') | b64encode }}" 12 | ldap.py: "{{ lookup('template', 'settings/ldap.py.j2') | b64encode }}" 13 | execution_environments.py: "{{ lookup('template', 'settings/execution_environments.py.j2') | b64encode }}" 14 | -------------------------------------------------------------------------------- /roles/installer/templates/secrets/broadcast_websocket_secret.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: '{{ ansible_operator_meta.name }}-broadcast-websocket' 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | labels: 8 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 9 | stringData: 10 | secret: '{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}' 11 | -------------------------------------------------------------------------------- /roles/installer/templates/secrets/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 | labels: 9 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 10 | stringData: 11 | password: '{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}' 12 | username: '{{ database_username }}' 13 | database: '{{ database_name }}' 14 | port: '5432' 15 | host: {{ ansible_operator_meta.name }}-postgres-{{ supported_pg_version }} 16 | type: 'managed' 17 | -------------------------------------------------------------------------------- /roles/installer/templates/secrets/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: '{{ awx_postgres_pass }}' 12 | username: '{{ awx_postgres_user }}' 13 | database: '{{ awx_postgres_database }}' 14 | port: '{{ awx_postgres_port }}' 15 | host: '{{ ansible_operator_meta.name }}-postgres-{{ supported_pg_version }}' 16 | type: 'managed' 17 | -------------------------------------------------------------------------------- /roles/installer/templates/secrets/receptor_ca_secret.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: '{{ ansible_operator_meta.name }}-receptor-ca' 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | labels: 8 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 9 | type: kubernetes.io/tls 10 | data: 11 | tls.crt: '{{ lookup('file', '{{ _receptor_ca_crt_file.path }}') | b64encode }}' 12 | tls.key: '{{ lookup('file', '{{ _receptor_ca_key_file.path }}') | b64encode }}' 13 | -------------------------------------------------------------------------------- /roles/installer/templates/secrets/receptor_work_signing_secret.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: '{{ ansible_operator_meta.name }}-receptor-work-signing' 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | labels: 8 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 9 | data: 10 | work-private-key.pem: '{{ lookup('file', '{{ _receptor_work_signing_private_key_file.path }}') | b64encode }}' 11 | work-public-key.pem: '{{ lookup('file', '{{ _receptor_work_signing_public_key_file.path }}') | b64encode }}' 12 | -------------------------------------------------------------------------------- /roles/installer/templates/secrets/secret_key.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: '{{ ansible_operator_meta.name }}-secret-key' 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | labels: 8 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 9 | stringData: 10 | secret_key: '{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}' 11 | -------------------------------------------------------------------------------- /roles/installer/templates/settings/credentials.py.j2: -------------------------------------------------------------------------------- 1 | DATABASES = { 2 | 'default': { 3 | 'ATOMIC_REQUESTS': True, 4 | 'ENGINE': 'awx.main.db.profiled_pg', 5 | 'NAME': "{{ awx_postgres_database }}", 6 | 'USER': "{{ awx_postgres_user }}", 7 | 'PASSWORD': "{{ awx_postgres_pass }}", 8 | 'HOST': '{{ awx_postgres_host }}', 9 | 'PORT': "{{ awx_postgres_port }}", 10 | 'OPTIONS': { 'sslmode': '{{ awx_postgres_sslmode }}', 11 | {% if awx_postgres_sslmode in ['verify-ca', 'verify-full'] %} 12 | 'sslrootcert': '{{ ca_trust_bundle }}', 13 | {% endif %} 14 | {% if awx_postgres_target_session_attrs %} 15 | 'target_session_attrs': '{{ awx_postgres_target_session_attrs }}', 16 | {% endif %} 17 | }, 18 | } 19 | } 20 | 21 | LISTENER_DATABASES = { 22 | 'default': { 23 | 'OPTIONS': { 24 | {% if postgres_keepalives %} 25 | 'keepalives': 1, 26 | 'keepalives_idle': {{ postgres_keepalives_idle }}, 27 | 'keepalives_interval': {{ postgres_keepalives_interval }}, 28 | 'keepalives_count': {{ postgres_keepalives_count }}, 29 | {% else %} 30 | 'keepalives': 0, 31 | {% endif %} 32 | {% if awx_postgres_target_session_attrs %} 33 | 'target_session_attrs': '{{ awx_postgres_target_session_attrs }}', 34 | {% endif %} 35 | }, 36 | } 37 | } 38 | 39 | BROADCAST_WEBSOCKET_SECRET = "{{ broadcast_websocket_secret_value }}" 40 | -------------------------------------------------------------------------------- /roles/installer/templates/settings/execution_environments.py.j2: -------------------------------------------------------------------------------- 1 | GLOBAL_JOB_EXECUTION_ENVIRONMENTS = [ 2 | {% for item in ee_images %} 3 | {'name': '{{ item.name }}' , 'image': '{{ item.image }}'}, 4 | {% endfor %} 5 | ] 6 | CONTROL_PLANE_EXECUTION_ENVIRONMENT = "{{ _custom_control_plane_ee_image | default(lookup('env', 'RELATED_IMAGE_CONTROL_PLANE_EE')) | default(_control_plane_ee_image, true) }}" 7 | -------------------------------------------------------------------------------- /roles/installer/templates/settings/ldap.py.j2: -------------------------------------------------------------------------------- 1 | {% if ldap_cacert_ca_crt %} 2 | import ldap 3 | 4 | AUTH_LDAP_GLOBAL_OPTIONS = { 5 | ldap.OPT_X_TLS_REQUIRE_CERT: True, 6 | ldap.OPT_X_TLS_CACERTFILE: "/etc/openldap/certs/ldap-ca.crt" 7 | } 8 | {% else %} 9 | AUTH_LDAP_GLOBAL_OPTIONS = {} 10 | {% endif %} 11 | 12 | # Load LDAP BIND password from Kubernetes secret if define 13 | {% if ldap_password_secret -%} 14 | AUTH_LDAP_BIND_PASSWORD = "{{ ldap_bind_password }}" 15 | {% endif %} 16 | -------------------------------------------------------------------------------- /roles/installer/templates/storage/metrics-utility.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: PersistentVolumeClaim 4 | metadata: 5 | name: {{ _metrics_utility_pvc_claim }} 6 | namespace: "{{ ansible_operator_meta.namespace }}" 7 | ownerReferences: null 8 | labels: 9 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | resources: 14 | requests: 15 | storage: {{ _metrics_utility_pvc_claim_size }} 16 | {% if metrics_utility_pvc_claim_storage_class is defined %} 17 | storageClassName: {{ metrics_utility_pvc_claim_storage_class }} 18 | {% endif %} -------------------------------------------------------------------------------- /roles/installer/templates/storage/persistent.yaml.j2: -------------------------------------------------------------------------------- 1 | {% if projects_persistence|bool and projects_existing_claim == '' %} 2 | kind: PersistentVolumeClaim 3 | apiVersion: v1 4 | metadata: 5 | name: '{{ ansible_operator_meta.name }}-projects-claim' 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | labels: 8 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 9 | spec: 10 | accessModes: 11 | - {{ projects_storage_access_mode }} 12 | resources: 13 | requests: 14 | storage: {{ projects_storage_size }} 15 | {% if projects_storage_class is defined %} 16 | storageClassName: {{ projects_storage_class }} 17 | {% endif %} 18 | {% endif %} 19 | -------------------------------------------------------------------------------- /roles/installer/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | postgres_initdb_args: '--auth-host=scram-sha-256' 3 | postgres_host_auth_method: 'scram-sha-256' 4 | # LDAP is deprecated 5 | ldap_cacert_ca_crt: '' 6 | bundle_ca_crt: '' 7 | projects_existing_claim: '' 8 | supported_pg_version: 15 9 | _previous_upgraded_pg_version: 0 10 | old_postgres_pod: [] 11 | _postgres_data_path: '/var/lib/pgsql/data/userdata' 12 | # metrics-utility (github.com/ansible/metrics-utility) 13 | _metrics_utility_enabled: "{{ metrics_utility_enabled | default(false) }}" 14 | _metrics_utility_configmap: "{{ metrics_utility_configmap | default(deployment_type + '-metrics-utility-configmap') }}" 15 | _metrics_utility_console_enabled: "{{ metrics_utility_console_enabled | default(false) }}" 16 | _metrics_utility_image: "{{ metrics_utility_image | default(_image) }}" 17 | _metrics_utility_image_version: "{{ metrics_utility_image_version | default(_image_version) }}" 18 | _metrics_utility_image_pull_policy: "{{ metrics_utility_image_pull_policy | default('IfNotPresent') }}" 19 | _metrics_utility_ship_target: "{{ metrics_utility_ship_target | default('directory') }}" 20 | _metrics_utility_pvc_claim: "{{ metrics_utility_pvc_claim | default(deployment_type + '-metrics-utility') }}" 21 | _metrics_utility_pvc_claim_size: "{{ metrics_utility_pvc_claim_size | default('5Gi') }}" 22 | _metrics_utility_cronjob_gather_schedule: "{{ metrics_utility_cronjob_gather_schedule | default('@hourly') }}" 23 | _metrics_utility_cronjob_report_schedule: "{{ metrics_utility_cronjob_report_schedule | default('@monthly') }}" 24 | 25 | # version check 26 | gating_version: '' 27 | -------------------------------------------------------------------------------- /roles/mesh_ingress/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | deployment_type: awx 3 | 4 | ingress_type: none 5 | ingress_api_version: 'networking.k8s.io/v1' 6 | ingress_annotations: '' 7 | ingress_class_name: '' 8 | ingress_controller: '' 9 | route_annotations: '' 10 | 11 | set_self_owneref: true 12 | 13 | _control_plane_ee_image: "quay.io/ansible/awx-ee:{{ lookup('env', 'DEFAULT_AWX_VERSION') or 'latest' }}" 14 | _image_pull_policy: Always 15 | image_pull_secrets: [] 16 | 17 | finalizer_run: false 18 | 19 | node_selector: '' 20 | topology_spread_constraints: '' 21 | tolerations: '' 22 | affinity: {} 23 | -------------------------------------------------------------------------------- /roles/mesh_ingress/tasks/finalizer.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get the current resource task pod information. 3 | k8s_info: 4 | api_version: v1 5 | kind: Pod 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | label_selectors: 8 | - "app.kubernetes.io/name={{ deployment_name }}-task" 9 | - "app.kubernetes.io/managed-by={{ deployment_type }}-operator" 10 | - "app.kubernetes.io/component={{ deployment_type }}" 11 | field_selectors: 12 | - status.phase=Running 13 | register: awx_task_pod 14 | 15 | - name: Set the resource pod as a variable. 16 | set_fact: 17 | awx_task_pod: >- 18 | {{ awx_task_pod['resources'] 19 | | rejectattr('metadata.deletionTimestamp', 'defined') 20 | | sort(attribute='metadata.creationTimestamp') 21 | | first | default({}) }} 22 | 23 | - name: Set the resource pod name as a variable. 24 | set_fact: 25 | awx_task_pod_name: "{{ awx_task_pod['metadata']['name'] | default('') }}" 26 | 27 | - name: Deprovision mesh ingress instance in AWX 28 | kubernetes.core.k8s_exec: 29 | namespace: "{{ ansible_operator_meta.namespace }}" 30 | pod: "{{ awx_task_pod_name }}" 31 | container: "{{ deployment_name }}-task" 32 | command: "awx-manage deprovision_instance --hostname {{ ansible_operator_meta.name }}" 33 | register: result 34 | -------------------------------------------------------------------------------- /roles/mesh_ingress/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Lowercase the ingress_type 3 | set_fact: 4 | ingress_type: "{{ ingress_type | lower }}" 5 | 6 | - name: Run creation tasks 7 | include_tasks: creation.yml 8 | when: not finalizer_run 9 | 10 | - name: Run finalizer tasks 11 | include_tasks: finalizer.yml 12 | when: finalizer_run 13 | -------------------------------------------------------------------------------- /roles/mesh_ingress/templates/ingress.yml.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 }} 9 | namespace: "{{ ansible_operator_meta.namespace }}" 10 | annotations: 11 | {% if ingress_annotations %} 12 | {{ ingress_annotations | indent(width=4) }} 13 | {% endif %} 14 | {% if ingress_controller|lower == "nginx" %} 15 | nginx.ingress.kubernetes.io/ssl-passthrough: "true" 16 | {% endif %} 17 | spec: 18 | {% if ingress_class_name %} 19 | ingressClassName: '{{ ingress_class_name }}' 20 | {% endif %} 21 | rules: 22 | - http: 23 | paths: 24 | - path: / 25 | pathType: Prefix 26 | backend: 27 | service: 28 | name: {{ ansible_operator_meta.name }} 29 | port: 30 | number: 27199 31 | {% if external_hostname is defined %} 32 | host: {{ external_hostname }} 33 | {% endif %} 34 | {% endif %} 35 | 36 | {% if ingress_type|lower == "ingressroutetcp" %} 37 | --- 38 | {% if ingress_api_version is defined %} 39 | apiVersion: '{{ ingress_api_version }}' 40 | {% endif %} 41 | kind: IngressRouteTCP 42 | metadata: 43 | name: {{ ansible_operator_meta.name }} 44 | namespace: "{{ ansible_operator_meta.namespace }}" 45 | annotations: 46 | {% if ingress_annotations %} 47 | {{ ingress_annotations | indent(width=4) }} 48 | {% endif %} 49 | spec: 50 | entryPoints: 51 | - websecure 52 | routes: 53 | - services: 54 | - name: {{ ansible_operator_meta.name }} 55 | port: 27199 56 | {% if external_hostname is defined %} 57 | match: HostSNI(`{{ external_hostname }}`) 58 | {% endif %} 59 | tls: 60 | passthrough: true 61 | {% endif %} 62 | 63 | {% if ingress_type|lower == "route" %} 64 | --- 65 | apiVersion: route.openshift.io/v1 66 | kind: Route 67 | metadata: 68 | annotations: 69 | openshift.io/host.generated: "true" 70 | {% if route_annotations %} 71 | {{ route_annotations | indent(width=4) }} 72 | {% endif %} 73 | name: {{ ansible_operator_meta.name }} 74 | namespace: "{{ ansible_operator_meta.namespace }}" 75 | spec: 76 | {% if external_hostname is defined %} 77 | host: {{ external_hostname }} 78 | {% endif %} 79 | port: 80 | targetPort: ws 81 | tls: 82 | insecureEdgeTerminationPolicy: None 83 | termination: passthrough 84 | to: 85 | kind: Service 86 | name: {{ ansible_operator_meta.name }} 87 | weight: 100 88 | wildcardPolicy: None 89 | {% endif %} 90 | -------------------------------------------------------------------------------- /roles/mesh_ingress/templates/receptor_conf.configmap.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ ansible_operator_meta.name }}-receptor-config 6 | namespace: "{{ ansible_operator_meta.namespace }}" 7 | data: 8 | receptor_conf: | 9 | --- 10 | - node: 11 | id: {{ ansible_operator_meta.name }} 12 | - log-level: debug 13 | - control-service: 14 | service: control 15 | - ws-listener: 16 | port: 27199 17 | tls: tlsserver 18 | - tls-server: 19 | cert: /etc/receptor/tls/receptor.crt 20 | key: /etc/receptor/tls/receptor.key 21 | name: tlsserver 22 | clientcas: /etc/receptor/tls/ca/mesh-CA.crt 23 | requireclientcert: true 24 | mintls13: false 25 | -------------------------------------------------------------------------------- /roles/mesh_ingress/templates/service.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ ansible_operator_meta.name }} 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | spec: 8 | type: ClusterIP 9 | ports: 10 | - name: ws 11 | port: 27199 12 | targetPort: 27199 13 | selector: 14 | app.kubernetes.io/name: {{ ansible_operator_meta.name }} 15 | -------------------------------------------------------------------------------- /roles/mesh_ingress/templates/service_account.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: '{{ ansible_operator_meta.name }}' 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | -------------------------------------------------------------------------------- /roles/restore/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Required: specify name of tower deployment to restore to 3 | deployment_name: '' 4 | kind: 'AWXRestore' 5 | api_version: '{{ deployment_type }}.ansible.com/v1beta1' 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 awxbackup 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 | # Add a nodeSelector for the Postgres pods to backup. 21 | # Specify as literal block. E.g.: 22 | # db_management_pod_node_selector: | 23 | # kubernetes.io/arch: amd64 24 | # kubernetes.io/os: linux 25 | db_management_pod_node_selector: '' 26 | 27 | 28 | # Default resource requirements 29 | restore_resource_requirements: 30 | limits: 31 | cpu: "1000m" 32 | memory: "4096Mi" 33 | requests: 34 | cpu: "25m" 35 | memory: "32Mi" 36 | 37 | # Labels defined on the resource, which should be propagated to child resources 38 | additional_labels: [] 39 | 40 | # Maintain some of the recommended `app.kubernetes.io/*` labels on the resource (self) 41 | set_self_labels: true 42 | 43 | spec_overrides: {} 44 | ... 45 | -------------------------------------------------------------------------------- /roles/restore/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: Ansible 4 | description: AWX role for AWX 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 | - tower 21 | - controller 22 | - awx 23 | - ansible 24 | - restore 25 | - automation 26 | 27 | dependencies: 28 | - role: common 29 | 30 | collections: 31 | - kubernetes.core 32 | - operator_sdk.util 33 | -------------------------------------------------------------------------------- /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: Remove ownerReferences from secrets to avoid garbage collection 12 | k8s: 13 | definition: 14 | apiVersion: v1 15 | kind: Secret 16 | metadata: 17 | name: '{{ item }}' 18 | namespace: '{{ ansible_operator_meta.namespace }}' 19 | ownerReferences: null 20 | loop: 21 | - '{{ secret_key_secret }}' 22 | - '{{ admin_password_secret }}' 23 | - '{{ broadcast_websocket_secret }}' 24 | - '{{ postgres_configuration_secret }}' 25 | no_log: "{{ no_log }}" 26 | 27 | - name: Cleanup temp spec file 28 | file: 29 | path: "{{ tmp_spec.path }}" 30 | state: absent 31 | when: tmp_spec.path is defined 32 | 33 | - name: Cleanup temp secret vars file 34 | file: 35 | path: "{{ secret_vars.path }}" 36 | state: absent 37 | when: secret_vars.path is defined 38 | -------------------------------------------------------------------------------- /roles/restore/tasks/deploy_awx.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Combine spec_overrides with spec 4 | set_fact: 5 | spec: "{{ spec | default({}) | combine(spec_overrides) }}" 6 | no_log: "{{ no_log }}" 7 | 8 | - name: Deploy AWX 9 | k8s: 10 | state: "{{ state | default('present') }}" 11 | namespace: "{{ ansible_operator_meta.namespace }}" 12 | apply: yes 13 | definition: "{{ lookup('template', 'awx_object.yml.j2') }}" 14 | wait: true 15 | wait_condition: 16 | type: "Running" 17 | status: "True" 18 | 19 | - name: Remove ownerReferences to prevent garbage collection of new AWX CRO 20 | k8s: 21 | definition: 22 | apiVersion: '{{ api_version }}' 23 | kind: AWX 24 | metadata: 25 | name: '{{ deployment_name }}' 26 | namespace: '{{ ansible_operator_meta.namespace }}' 27 | ownerReferences: null 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /roles/restore/tasks/import_vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Import awx_object variables 4 | block: 5 | 6 | - name: Create temp file for spec dict 7 | tempfile: 8 | state: file 9 | register: tmp_spec 10 | 11 | - name: Get AWX object definition from pvc 12 | k8s_cp: 13 | namespace: "{{ backup_pvc_namespace }}" 14 | pod: "{{ ansible_operator_meta.name }}-db-management" 15 | container: "{{ ansible_operator_meta.name }}-db-management" 16 | remote_path: "{{ backup_dir }}/awx_object" 17 | local_path: "{{ tmp_spec.path }}" 18 | state: from_pod 19 | 20 | - name: Include spec vars to save them as a dict 21 | include_vars: "{{ tmp_spec.path }}" 22 | -------------------------------------------------------------------------------- /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: '{{ lookup("template", "../common/templates/labels/common.yaml.j2") | from_yaml }}' 14 | when: set_self_labels | bool 15 | 16 | - name: Look up details for this restore object 17 | k8s_info: 18 | api_version: "{{ api_version }}" 19 | kind: "{{ kind }}" 20 | name: "{{ ansible_operator_meta.name }}" 21 | namespace: "{{ ansible_operator_meta.namespace }}" 22 | register: this_restore 23 | 24 | - name: Build `additional_labels_items` labels from `additional_labels` 25 | set_fact: 26 | additional_labels_items: >- 27 | {{ this_restore['resources'][0]['metadata']['labels'] 28 | | dict2items | selectattr('key', 'in', additional_labels) 29 | }} 30 | when: 31 | - additional_labels | length 32 | - this_restore['resources'][0]['metadata']['labels'] 33 | 34 | - block: 35 | - include_tasks: init.yml 36 | 37 | - include_tasks: import_vars.yml 38 | 39 | - include_tasks: secrets.yml 40 | 41 | - include_tasks: deploy_awx.yml 42 | 43 | - include_tasks: postgres.yml 44 | 45 | - name: Set flag signifying this restore was successful 46 | set_fact: 47 | tower_restore_complete: True 48 | 49 | - include_tasks: cleanup.yml 50 | 51 | when: 52 | - this_restore['resources'][0]['status']['restoreComplete'] is not defined 53 | 54 | - name: Update status variables 55 | include_tasks: update_status.yml 56 | -------------------------------------------------------------------------------- /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: tower_restore_complete is defined 12 | -------------------------------------------------------------------------------- /roles/restore/templates/awx_object.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: '{{ api_version }}' 3 | kind: AWX 4 | metadata: 5 | name: '{{ deployment_name }}' 6 | namespace: '{{ ansible_operator_meta.namespace }}' 7 | spec: 8 | {{ spec | to_yaml | indent(2) }} 9 | -------------------------------------------------------------------------------- /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: awx.ansible.com/v1beta1 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 | -------------------------------------------------------------------------------- /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 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 9 | spec: 10 | containers: 11 | - name: {{ ansible_operator_meta.name }}-db-management 12 | image: "{{ _postgres_image }}" 13 | imagePullPolicy: "{{ image_pull_policy }}" 14 | command: ["sleep", "infinity"] 15 | volumeMounts: 16 | - name: {{ ansible_operator_meta.name }}-backup 17 | mountPath: /backups 18 | readOnly: false 19 | {% if restore_resource_requirements is defined %} 20 | resources: 21 | {{ restore_resource_requirements | to_nice_yaml(indent=2) | indent(width=6, first=False) }} 22 | {%- endif %} 23 | {% if db_management_pod_node_selector %} 24 | nodeSelector: 25 | {{ db_management_pod_node_selector | indent(width=8) }} 26 | {% endif %} 27 | volumes: 28 | - name: {{ ansible_operator_meta.name }}-backup 29 | persistentVolumeClaim: 30 | claimName: {{ backup_pvc }} 31 | readOnly: false 32 | restartPolicy: Never 33 | -------------------------------------------------------------------------------- /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 | {{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }} 10 | type: '{{ secrets[secret]['type'] }}' 11 | stringData: 12 | {% for key, value in secrets[secret]['data'].items() %} 13 | {{ key }}: |- 14 | {{ value | b64decode | indent(4) }} 15 | {% endfor %} 16 | 17 | {% endfor %} 18 | -------------------------------------------------------------------------------- /roles/restore/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | deployment_type: "awx" 4 | _postgres_image: quay.io/sclorg/postgresql-15-c9s 5 | _postgres_image_version: latest 6 | 7 | backup_api_version: '{{ deployment_type }}.ansible.com/v1beta1' 8 | backup_kind: 'AWXBackup' 9 | 10 | # set default secret names to be used if a backup dir and claim are provided (not a backup_name) 11 | secret_key_secret: '{{ deployment_name }}-secret-key' 12 | admin_password_secret: '{{ deployment_name }}-admin-password' 13 | broadcast_websocket_secret: '{{ deployment_name }}-broadcast-websocket' 14 | postgres_configuration_secret: '{{ deployment_name }}-postgres-configuration' 15 | supported_pg_version: 15 16 | image_pull_policy: IfNotPresent 17 | 18 | # If set to true, the restore process will delete the existing database and create a new one 19 | force_drop_db: false 20 | pg_drop_create: '' 21 | -------------------------------------------------------------------------------- /vendor/galaxy.ansible.com/kubernetes/core/kubernetes-core-2.4.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/awx-operator/e8f0306ec2bc8cdf4c27f55b8e860453d4618f83/vendor/galaxy.ansible.com/kubernetes/core/kubernetes-core-2.4.2.tar.gz -------------------------------------------------------------------------------- /vendor/galaxy.ansible.com/operator_sdk/util/operator_sdk-util-0.4.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/awx-operator/e8f0306ec2bc8cdf4c27f55b8e860453d4618f83/vendor/galaxy.ansible.com/operator_sdk/util/operator_sdk-util-0.4.0.tar.gz -------------------------------------------------------------------------------- /watches.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Use the 'create api' subcommand to add watches to this file. 3 | - version: v1beta1 4 | group: awx.ansible.com 5 | kind: AWX 6 | playbook: playbooks/awx.yml 7 | snakeCaseParameters: False 8 | 9 | - version: v1beta1 10 | group: awx.ansible.com 11 | kind: AWXBackup 12 | role: backup 13 | snakeCaseParameters: False 14 | finalizer: 15 | name: awx.ansible.com/finalizer 16 | role: backup 17 | vars: 18 | finalizer_run: true 19 | 20 | - version: v1beta1 21 | group: awx.ansible.com 22 | kind: AWXRestore 23 | role: restore 24 | snakeCaseParameters: False 25 | 26 | - version: v1alpha1 27 | group: awx.ansible.com 28 | kind: AWXMeshIngress 29 | role: mesh_ingress 30 | snakeCaseParameters: False 31 | finalizer: 32 | name: awx.ansible.com/awx-mesh-ingress-finalizer 33 | role: mesh_ingress 34 | vars: 35 | finalizer_run: true 36 | # +kubebuilder:scaffold:watch 37 | --------------------------------------------------------------------------------