Compare commits
2 Commits
085654d017
...
update_202
| Author | SHA1 | Date | |
|---|---|---|---|
|
7df35b9dd4
|
|||
|
c164cb2598
|
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "ansible-dev-container-codespaces",
|
|
||||||
"image": "ghcr.io/ansible/community-ansible-dev-tools:latest",
|
|
||||||
"containerUser": "root",
|
|
||||||
"runArgs": [
|
|
||||||
"--security-opt",
|
|
||||||
"seccomp=unconfined",
|
|
||||||
"--security-opt",
|
|
||||||
"label=disable",
|
|
||||||
"--cap-add=SYS_ADMIN",
|
|
||||||
"--cap-add=SYS_RESOURCE",
|
|
||||||
"--device",
|
|
||||||
"/dev/fuse",
|
|
||||||
"--security-opt",
|
|
||||||
"apparmor=unconfined",
|
|
||||||
"--hostname=ansible-dev-container"
|
|
||||||
],
|
|
||||||
"updateRemoteUserUID": true,
|
|
||||||
"customizations": {
|
|
||||||
"vscode": {
|
|
||||||
"extensions": ["redhat.ansible","redhat.vscode-redhat-account"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "ansible-dev-container-docker",
|
|
||||||
"image": "ghcr.io/ansible/community-ansible-dev-tools:latest",
|
|
||||||
"containerUser": "root",
|
|
||||||
"runArgs": [
|
|
||||||
"--security-opt",
|
|
||||||
"seccomp=unconfined",
|
|
||||||
"--security-opt",
|
|
||||||
"label=disable",
|
|
||||||
"--cap-add=SYS_ADMIN",
|
|
||||||
"--cap-add=SYS_RESOURCE",
|
|
||||||
"--device",
|
|
||||||
"/dev/fuse",
|
|
||||||
"--security-opt",
|
|
||||||
"apparmor=unconfined",
|
|
||||||
"--hostname=ansible-dev-container"
|
|
||||||
],
|
|
||||||
"updateRemoteUserUID": true,
|
|
||||||
"customizations": {
|
|
||||||
"vscode": {
|
|
||||||
"extensions": ["redhat.ansible","redhat.vscode-redhat-account"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "ansible-dev-container-podman",
|
|
||||||
"image": "ghcr.io/ansible/community-ansible-dev-tools:latest",
|
|
||||||
"containerUser": "root",
|
|
||||||
"containerEnv": {
|
|
||||||
"REGISTRY_AUTH_FILE": "/container-auth.json"
|
|
||||||
},
|
|
||||||
"runArgs": [
|
|
||||||
"--cap-add=CAP_MKNOD",
|
|
||||||
"--cap-add=NET_ADMIN",
|
|
||||||
"--cap-add=SYS_ADMIN",
|
|
||||||
"--cap-add=SYS_RESOURCE",
|
|
||||||
"--device",
|
|
||||||
"/dev/fuse",
|
|
||||||
"--security-opt",
|
|
||||||
"seccomp=unconfined",
|
|
||||||
"--security-opt",
|
|
||||||
"label=disable",
|
|
||||||
"--security-opt",
|
|
||||||
"apparmor=unconfined",
|
|
||||||
"--security-opt",
|
|
||||||
"unmask=/sys/fs/cgroup",
|
|
||||||
"--userns=host",
|
|
||||||
"--hostname=ansible-dev-container",
|
|
||||||
"--env-file",
|
|
||||||
".env"
|
|
||||||
],
|
|
||||||
"customizations": {
|
|
||||||
"vscode": {
|
|
||||||
"extensions": ["redhat.ansible","redhat.vscode-redhat-account"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mounts": [
|
|
||||||
"source=${localEnv:XDG_RUNTIME_DIR}/containers/auth.json,target=/container-auth.json,type=bind,consistency=cached",
|
|
||||||
"source=${localEnv:HOME}/Dev/inventories/toallab-inventory,target=/workspaces/inventory,type=bind,consistency=cached",
|
|
||||||
"source=${localEnv:HOME}/Dev/ansible_collections/,target=/workspaces/collections/,type=bind,consistency=cached",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
- name: Set up IPA Client
|
- name: Set up IPA Client
|
||||||
hosts: tag_ipa_client
|
hosts: tag_ipa_client
|
||||||
become: yes
|
become: true
|
||||||
collections:
|
collections:
|
||||||
- freeipa.ansible_freeipa
|
- freeipa.ansible_freeipa
|
||||||
pre_tasks:
|
pre_tasks:
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
- name: Packages
|
- name: Packages
|
||||||
hosts: all
|
hosts: all
|
||||||
become: yes
|
become: true
|
||||||
tasks:
|
tasks:
|
||||||
|
|
||||||
- name: Host Packages
|
- name: Host Packages
|
||||||
|
|||||||
84
playbooks/deploy_automation_orchestrator.yml
Normal file
84
playbooks/deploy_automation_orchestrator.yml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
# Deploy Automation Orchestrator on OpenShift via its OLM operator, backed by a
|
||||||
|
# CloudNativePG-provisioned PostgreSQL cluster.
|
||||||
|
#
|
||||||
|
# WARNING: CloudNativePG here is a single-instance, operator-provisioned PostgreSQL
|
||||||
|
# cluster intended for demo/dev use — not production. For production, disable the
|
||||||
|
# cloudnativepg_operator role below and point automation_orchestrator_operator's
|
||||||
|
# postgres_* variables at a customer-managed PostgreSQL instance instead.
|
||||||
|
#
|
||||||
|
# Authenticates via the same aap-deployer ServiceAccount token used by deploy_aap.yml
|
||||||
|
# (same cluster). The token is stored in 1Password as vault_aap_deployer_token.
|
||||||
|
#
|
||||||
|
# Modeled on https://github.com/ansible/aapctl (aap-plus install path).
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# - OpenShift cluster deployed (deploy_openshift.yml)
|
||||||
|
# - aap-deployer ServiceAccount provisioned (re-run after RBAC changes):
|
||||||
|
# ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_service_accounts
|
||||||
|
# - CloudNativePG uses Manual install-plan approval; the role approves its
|
||||||
|
# InstallPlan. Automation Orchestrator installs from redhat-operators and
|
||||||
|
# adopts an existing operator install when its namespace is set in inventory.
|
||||||
|
# - Pre-release AO builds additionally need a custom CatalogSource and an
|
||||||
|
# ImageDigestMirrorSet (see automation_orchestrator_operator defaults).
|
||||||
|
#
|
||||||
|
# Keycloak OIDC (--tags ao_configure_oidc, run after the install):
|
||||||
|
# - Keycloak settings come from the openshift host (keycloak_url, keycloak_realm,
|
||||||
|
# keycloak_admin_user, vault_keycloak_admin_password).
|
||||||
|
# - automation_orchestrator_oidc_group_mappings in host_vars for the aap host.
|
||||||
|
# - The client secret is generated by Keycloak and read back each run (not vaulted).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ansible-navigator run playbooks/deploy_automation_orchestrator.yml --mode stdout
|
||||||
|
# ansible-navigator run playbooks/deploy_automation_orchestrator.yml --mode stdout --tags ao_configure_oidc
|
||||||
|
|
||||||
|
- name: Install Automation Orchestrator
|
||||||
|
hosts: aap
|
||||||
|
gather_facts: false
|
||||||
|
connection: local
|
||||||
|
|
||||||
|
environment:
|
||||||
|
K8S_AUTH_HOST: "{{ aap_k8s_api_url }}"
|
||||||
|
K8S_AUTH_API_KEY: "{{ vault_aap_deployer_token }}"
|
||||||
|
|
||||||
|
pre_tasks:
|
||||||
|
- name: Verify aap-deployer token is available
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- vault_aap_deployer_token is defined
|
||||||
|
- vault_aap_deployer_token | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
vault_aap_deployer_token is not set. Provision the ServiceAccount with:
|
||||||
|
ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_service_accounts
|
||||||
|
Then save the displayed token to 1Password as vault_aap_deployer_token.
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: cloudnativepg_operator
|
||||||
|
- role: automation_orchestrator_operator
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Configure Keycloak OIDC login (optional, run after the install)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- name: Configure Keycloak OIDC for Automation Orchestrator
|
||||||
|
hosts: aap
|
||||||
|
gather_facts: false
|
||||||
|
connection: local
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- never
|
||||||
|
- ao_configure_oidc
|
||||||
|
|
||||||
|
vars:
|
||||||
|
__ao_keycloak_host: "{{ hostvars[groups['openshift'][0]] }}"
|
||||||
|
automation_orchestrator_oidc_keycloak_url: "{{ __ao_keycloak_host.keycloak_url }}{{ __ao_keycloak_host.keycloak_context | default('') }}"
|
||||||
|
automation_orchestrator_oidc_keycloak_realm: "{{ __ao_keycloak_host.keycloak_realm }}"
|
||||||
|
automation_orchestrator_oidc_keycloak_admin_user: "{{ __ao_keycloak_host.keycloak_admin_user }}"
|
||||||
|
automation_orchestrator_oidc_keycloak_admin_password: "{{ __ao_keycloak_host.vault_keycloak_admin_password }}"
|
||||||
|
automation_orchestrator_oidc_keycloak_validate_certs: "{{ __ao_keycloak_host.keycloak_validate_certs | default(true) }}"
|
||||||
|
|
||||||
|
environment:
|
||||||
|
K8S_AUTH_HOST: "{{ aap_k8s_api_url }}"
|
||||||
|
K8S_AUTH_API_KEY: "{{ vault_aap_deployer_token }}"
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: automation_orchestrator_oidc
|
||||||
@@ -33,6 +33,9 @@
|
|||||||
# Play 7: sno_deploy_lvms — Install LVM Storage for persistent volumes
|
# Play 7: sno_deploy_lvms — Install LVM Storage for persistent volumes
|
||||||
# Play 8: sno_deploy_nfs — Deploy in-cluster NFS provisioner (RWX StorageClass)
|
# Play 8: sno_deploy_nfs — Deploy in-cluster NFS provisioner (RWX StorageClass)
|
||||||
# Play 9: sno_deploy_service_accounts — Provision ServiceAccounts for app deployers
|
# Play 9: sno_deploy_service_accounts — Provision ServiceAccounts for app deployers
|
||||||
|
# Play 10: sno_deploy_alert_tuning — Drop single-control-plane false-positive alerts
|
||||||
|
#
|
||||||
|
# Recurring maintenance (separate playbook, schedule in AAP): maintain_sno_etcd.yml
|
||||||
#
|
#
|
||||||
# AAP deployment is in a separate playbook: deploy_aap.yml
|
# AAP deployment is in a separate playbook: deploy_aap.yml
|
||||||
#
|
#
|
||||||
@@ -46,6 +49,7 @@
|
|||||||
# ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_lvms
|
# ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_lvms
|
||||||
# ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_nfs
|
# ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_nfs
|
||||||
# ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_service_accounts
|
# ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_service_accounts
|
||||||
|
# ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_alert_tuning
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Play 1: Create SNO VM in Proxmox
|
# Play 1: Create SNO VM in Proxmox
|
||||||
@@ -396,18 +400,40 @@
|
|||||||
- apiGroups: [""]
|
- apiGroups: [""]
|
||||||
resources: ["serviceaccounts"]
|
resources: ["serviceaccounts"]
|
||||||
verbs: ["get", "list", "watch"]
|
verbs: ["get", "list", "watch"]
|
||||||
|
# patch: Automation Orchestrator backend env (APP_OIDC_ALLOW_PRIVATE_NETWORKS)
|
||||||
- apiGroups: ["apps"]
|
- apiGroups: ["apps"]
|
||||||
resources: ["deployments"]
|
resources: ["deployments"]
|
||||||
|
verbs: ["get", "list", "watch", "patch"]
|
||||||
|
- apiGroups: ["route.openshift.io"]
|
||||||
|
resources: ["routes"]
|
||||||
|
verbs: ["get", "list"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["endpoints"]
|
||||||
verbs: ["get", "list", "watch"]
|
verbs: ["get", "list", "watch"]
|
||||||
- apiGroups: ["operators.coreos.com"]
|
- apiGroups: ["operators.coreos.com"]
|
||||||
resources: ["operatorgroups", "subscriptions", "clusterserviceversions"]
|
resources: ["operatorgroups", "subscriptions", "clusterserviceversions",
|
||||||
|
"catalogsources"]
|
||||||
verbs: ["get", "list", "create", "patch", "watch"]
|
verbs: ["get", "list", "create", "patch", "watch"]
|
||||||
|
- apiGroups: ["operators.coreos.com"]
|
||||||
|
resources: ["installplans"]
|
||||||
|
verbs: ["get", "list", "patch", "watch"]
|
||||||
- apiGroups: ["apiextensions.k8s.io"]
|
- apiGroups: ["apiextensions.k8s.io"]
|
||||||
resources: ["customresourcedefinitions"]
|
resources: ["customresourcedefinitions"]
|
||||||
verbs: ["get", "list", "watch"]
|
verbs: ["get", "list", "watch"]
|
||||||
- apiGroups: ["aap.ansible.com"]
|
- apiGroups: ["aap.ansible.com"]
|
||||||
resources: ["ansibleautomationplatforms"]
|
resources: ["ansibleautomationplatforms", "automationorchestrators"]
|
||||||
verbs: ["get", "list", "create", "patch", "watch"]
|
verbs: ["get", "list", "create", "patch", "watch"]
|
||||||
|
# CloudNativePG Cluster/Database CRs backing Automation Orchestrator
|
||||||
|
- apiGroups: ["postgresql.cnpg.io"]
|
||||||
|
resources: ["clusters", "databases"]
|
||||||
|
verbs: ["get", "list", "create", "patch", "watch"]
|
||||||
|
# Automation Orchestrator pre-release image mirror (IDMS) + MCP rollout wait
|
||||||
|
- apiGroups: ["config.openshift.io"]
|
||||||
|
resources: ["imagedigestmirrorsets"]
|
||||||
|
verbs: ["get", "list", "create", "patch"]
|
||||||
|
- apiGroups: ["machineconfiguration.openshift.io"]
|
||||||
|
resources: ["machineconfigpools"]
|
||||||
|
verbs: ["get", "list", "watch"]
|
||||||
|
|
||||||
- name: Store aap-deployer token in Vault
|
- name: Store aap-deployer token in Vault
|
||||||
community.hashi_vault.vault_kv2_write:
|
community.hashi_vault.vault_kv2_write:
|
||||||
@@ -416,3 +442,20 @@
|
|||||||
data:
|
data:
|
||||||
token: "{{ __ocp_service_account_token }}"
|
token: "{{ __ocp_service_account_token }}"
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Play 10: Drop platform alerts that are false positives on a single
|
||||||
|
# control-plane cluster (AlertRelabelConfig in openshift-monitoring)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- name: Tune platform alerts for single-node OpenShift
|
||||||
|
hosts: sno.openshift.toal.ca
|
||||||
|
gather_facts: false
|
||||||
|
connection: local
|
||||||
|
tags: sno_deploy_alert_tuning
|
||||||
|
|
||||||
|
environment:
|
||||||
|
KUBECONFIG: "{{ ocp_kubeconfig | default('~/.kube/config') }}"
|
||||||
|
K8S_AUTH_VERIFY_SSL: "false"
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: sno_alert_tuning
|
||||||
|
|||||||
23
playbooks/maintain_sno_etcd.yml
Normal file
23
playbooks/maintain_sno_etcd.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
# Defragment etcd on the single-node OpenShift cluster.
|
||||||
|
#
|
||||||
|
# OpenShift disables automatic etcd defragmentation on single-control-plane
|
||||||
|
# clusters, so run this on a schedule (e.g. weekly AAP schedule, off-hours).
|
||||||
|
# It only defragments when fragmentation exceeds the thresholds of the
|
||||||
|
# etcdDatabaseHighFragmentationRatio alert; otherwise it reports and exits.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ansible-navigator run playbooks/maintain_sno_etcd.yml --mode stdout
|
||||||
|
# ansible-navigator run playbooks/maintain_sno_etcd.yml --mode stdout -e sno_etcd_defrag_force=true
|
||||||
|
|
||||||
|
- name: Defragment etcd on single-node OpenShift
|
||||||
|
hosts: sno.openshift.toal.ca
|
||||||
|
gather_facts: false
|
||||||
|
connection: local
|
||||||
|
|
||||||
|
environment:
|
||||||
|
KUBECONFIG: "{{ ocp_kubeconfig | default('~/.kube/config') }}"
|
||||||
|
K8S_AUTH_VERIFY_SSL: "false"
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: sno_etcd_defrag
|
||||||
38
roles/automation_orchestrator_oidc/defaults/main.yml
Normal file
38
roles/automation_orchestrator_oidc/defaults/main.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
# --- Automation Orchestrator instance ---
|
||||||
|
# Defaults track the automation_orchestrator_operator role's variables when set.
|
||||||
|
automation_orchestrator_oidc_namespace: "{{ automation_orchestrator_operator_app_namespace | default('automation-orchestrator') }}"
|
||||||
|
automation_orchestrator_oidc_instance_name: "{{ automation_orchestrator_operator_instance_name | default('automation-orchestrator') }}"
|
||||||
|
# Public base URL of the AO UI/API; "" derives it from the instance's Route.
|
||||||
|
automation_orchestrator_oidc_url: ""
|
||||||
|
automation_orchestrator_oidc_admin_user: admin
|
||||||
|
automation_orchestrator_oidc_validate_certs: true
|
||||||
|
|
||||||
|
# --- Keycloak ---
|
||||||
|
automation_orchestrator_oidc_keycloak_url: ""
|
||||||
|
automation_orchestrator_oidc_keycloak_realm: ""
|
||||||
|
automation_orchestrator_oidc_keycloak_admin_user: ""
|
||||||
|
automation_orchestrator_oidc_keycloak_admin_password: ""
|
||||||
|
automation_orchestrator_oidc_keycloak_validate_certs: true
|
||||||
|
automation_orchestrator_oidc_client_id: automation-orchestrator
|
||||||
|
# Create the Keycloak groups named in group_mappings (membership is managed in Keycloak).
|
||||||
|
automation_orchestrator_oidc_create_keycloak_groups: true
|
||||||
|
|
||||||
|
# --- AO identity provider ---
|
||||||
|
automation_orchestrator_oidc_provider_name: Keycloak
|
||||||
|
# Keycloak group (from the "groups" claim) -> AO built-in group (admins, auditors, users).
|
||||||
|
# Users in no mapped group are refused login.
|
||||||
|
automation_orchestrator_oidc_group_mappings: []
|
||||||
|
# - idp_group: ao-admins
|
||||||
|
# group: admins
|
||||||
|
automation_orchestrator_oidc_rp_initiated_logout: true
|
||||||
|
# PATCH the provider even when its config is unchanged (e.g. to push a rotated client secret).
|
||||||
|
automation_orchestrator_oidc_force_update: false
|
||||||
|
|
||||||
|
# The backend rejects OIDC issuers that resolve to private IPs unless this is set, and
|
||||||
|
# the AutomationOrchestrator CR has no field for it — so the role sets the env var on the
|
||||||
|
# backend Deployment directly. Re-run the role if the operator ever reverts it.
|
||||||
|
automation_orchestrator_oidc_allow_private_networks: true
|
||||||
|
|
||||||
|
# --- Debugging ---
|
||||||
|
automation_orchestrator_oidc_no_log: true
|
||||||
99
roles/automation_orchestrator_oidc/meta/argument_specs.yml
Normal file
99
roles/automation_orchestrator_oidc/meta/argument_specs.yml
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
argument_specs:
|
||||||
|
main:
|
||||||
|
short_description: Configure Keycloak OIDC login for Automation Orchestrator
|
||||||
|
description:
|
||||||
|
- Creates a confidential OIDC client (with a "groups" claim mapper) and the mapped groups
|
||||||
|
in Keycloak, then registers Keycloak as an identity provider in Automation Orchestrator
|
||||||
|
via its REST API, mapping Keycloak groups to AO built-in groups.
|
||||||
|
- The client secret is read back from Keycloak on each run, so it is not stored elsewhere.
|
||||||
|
- >-
|
||||||
|
Sets APP_OIDC_ALLOW_PRIVATE_NETWORKS on the backend Deployment, since the
|
||||||
|
AutomationOrchestrator CR has no field for it and the backend otherwise rejects
|
||||||
|
issuers resolving to private IPs.
|
||||||
|
options:
|
||||||
|
automation_orchestrator_oidc_namespace:
|
||||||
|
description: Namespace of the AutomationOrchestrator instance.
|
||||||
|
type: str
|
||||||
|
default: automation-orchestrator
|
||||||
|
automation_orchestrator_oidc_instance_name:
|
||||||
|
description: Name of the AutomationOrchestrator CR (prefix of its Route, Deployments and Secrets).
|
||||||
|
type: str
|
||||||
|
default: automation-orchestrator
|
||||||
|
automation_orchestrator_oidc_url:
|
||||||
|
description: Public base URL of Automation Orchestrator. Empty derives it from the instance Route.
|
||||||
|
type: str
|
||||||
|
default: ""
|
||||||
|
automation_orchestrator_oidc_admin_user:
|
||||||
|
description: Local AO admin used to call the API (password read from the initial-admin-password Secret).
|
||||||
|
type: str
|
||||||
|
default: admin
|
||||||
|
automation_orchestrator_oidc_validate_certs:
|
||||||
|
description: Verify TLS when calling the AO API.
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
|
automation_orchestrator_oidc_keycloak_url:
|
||||||
|
description: Keycloak base URL (e.g. https://auth.example.com).
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
automation_orchestrator_oidc_keycloak_realm:
|
||||||
|
description: Keycloak realm holding the client and groups.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
automation_orchestrator_oidc_keycloak_admin_user:
|
||||||
|
description: Keycloak master-realm admin username.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
automation_orchestrator_oidc_keycloak_admin_password:
|
||||||
|
description: Keycloak master-realm admin password.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
no_log: true
|
||||||
|
automation_orchestrator_oidc_keycloak_validate_certs:
|
||||||
|
description: Verify TLS when calling the Keycloak admin API.
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
|
automation_orchestrator_oidc_client_id:
|
||||||
|
description: Keycloak client ID for Automation Orchestrator.
|
||||||
|
type: str
|
||||||
|
default: automation-orchestrator
|
||||||
|
automation_orchestrator_oidc_create_keycloak_groups:
|
||||||
|
description: Create the Keycloak groups named in group_mappings. Group membership is managed in Keycloak.
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
|
automation_orchestrator_oidc_provider_name:
|
||||||
|
description: Name of the identity provider in Automation Orchestrator (shown on the login page).
|
||||||
|
type: str
|
||||||
|
default: Keycloak
|
||||||
|
automation_orchestrator_oidc_group_mappings:
|
||||||
|
description: >-
|
||||||
|
Keycloak group to AO built-in group mappings. Users in no mapped group are refused login.
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
required: true
|
||||||
|
options:
|
||||||
|
idp_group:
|
||||||
|
description: Keycloak group name, as emitted in the "groups" claim.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
group:
|
||||||
|
description: AO built-in group.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
choices: [admins, auditors, users]
|
||||||
|
automation_orchestrator_oidc_rp_initiated_logout:
|
||||||
|
description: Log users out of Keycloak when they log out of Automation Orchestrator.
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
|
automation_orchestrator_oidc_force_update:
|
||||||
|
description: PATCH the identity provider even when unchanged (e.g. to push a rotated client secret).
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
automation_orchestrator_oidc_allow_private_networks:
|
||||||
|
description: Set APP_OIDC_ALLOW_PRIVATE_NETWORKS=true on the backend Deployment.
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
|
automation_orchestrator_oidc_no_log:
|
||||||
|
description: Set to false to expose secret values in task output. Never set false in shared environments.
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
17
roles/automation_orchestrator_oidc/meta/main.yml
Normal file
17
roles/automation_orchestrator_oidc/meta/main.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
galaxy_info:
|
||||||
|
author: ptoal
|
||||||
|
description: Configure Keycloak OIDC login for Automation Orchestrator
|
||||||
|
license: MIT
|
||||||
|
min_ansible_version: "2.16"
|
||||||
|
platforms:
|
||||||
|
- name: GenericLinux
|
||||||
|
versions:
|
||||||
|
- all
|
||||||
|
galaxy_tags:
|
||||||
|
- openshift
|
||||||
|
- aap
|
||||||
|
- keycloak
|
||||||
|
- oidc
|
||||||
|
|
||||||
|
dependencies: []
|
||||||
286
roles/automation_orchestrator_oidc/tasks/main.yml
Normal file
286
roles/automation_orchestrator_oidc/tasks/main.yml
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
---
|
||||||
|
# Configure Keycloak OIDC login for Automation Orchestrator.
|
||||||
|
#
|
||||||
|
# 1. Keycloak: mapped groups + confidential client with a "groups" claim mapper;
|
||||||
|
# the client secret is read back rather than stored elsewhere.
|
||||||
|
# 2. Backend Deployment: APP_OIDC_ALLOW_PRIVATE_NETWORKS (no CR field exists for it).
|
||||||
|
# 3. AO REST API: create/update the identity provider with group mappings.
|
||||||
|
#
|
||||||
|
# AO identity providers are runtime configuration in the AO database — the
|
||||||
|
# AutomationOrchestrator CR has no auth fields.
|
||||||
|
|
||||||
|
- name: Validate group mappings
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- automation_orchestrator_oidc_group_mappings | length > 0
|
||||||
|
- >-
|
||||||
|
automation_orchestrator_oidc_group_mappings | map(attribute='group')
|
||||||
|
| difference(__automation_orchestrator_oidc_valid_groups) | length == 0
|
||||||
|
fail_msg: >-
|
||||||
|
automation_orchestrator_oidc_group_mappings must map at least one Keycloak group to an
|
||||||
|
AO built-in group ({{ __automation_orchestrator_oidc_valid_groups | join(', ') }});
|
||||||
|
otherwise no Keycloak user can log in.
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 1: Resolve the Automation Orchestrator public URL
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Read Automation Orchestrator Route
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: route.openshift.io/v1
|
||||||
|
kind: Route
|
||||||
|
namespace: "{{ automation_orchestrator_oidc_namespace }}"
|
||||||
|
name: "{{ automation_orchestrator_oidc_instance_name }}"
|
||||||
|
register: __automation_orchestrator_oidc_route
|
||||||
|
when: automation_orchestrator_oidc_url | length == 0
|
||||||
|
failed_when: __automation_orchestrator_oidc_route.resources | length == 0
|
||||||
|
|
||||||
|
- name: Set Automation Orchestrator URL
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
__automation_orchestrator_oidc_url: >-
|
||||||
|
{{ (automation_orchestrator_oidc_url
|
||||||
|
if automation_orchestrator_oidc_url | length > 0
|
||||||
|
else 'https://' + __automation_orchestrator_oidc_route.resources[0].spec.host)
|
||||||
|
| regex_replace('/$', '') }}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 2: Keycloak client and groups
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Configure Keycloak
|
||||||
|
module_defaults:
|
||||||
|
community.general.keycloak_group: &keycloak_auth
|
||||||
|
auth_client_id: admin-cli
|
||||||
|
auth_keycloak_url: "{{ automation_orchestrator_oidc_keycloak_url }}"
|
||||||
|
auth_realm: master
|
||||||
|
auth_username: "{{ automation_orchestrator_oidc_keycloak_admin_user }}"
|
||||||
|
auth_password: "{{ automation_orchestrator_oidc_keycloak_admin_password }}"
|
||||||
|
validate_certs: "{{ automation_orchestrator_oidc_keycloak_validate_certs }}"
|
||||||
|
community.general.keycloak_clientsecret_info: *keycloak_auth
|
||||||
|
middleware_automation.keycloak.keycloak_client: *keycloak_auth
|
||||||
|
block:
|
||||||
|
- name: Create mapped Keycloak groups
|
||||||
|
community.general.keycloak_group:
|
||||||
|
realm: "{{ automation_orchestrator_oidc_keycloak_realm }}"
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: present
|
||||||
|
loop: "{{ automation_orchestrator_oidc_group_mappings | map(attribute='idp_group') | unique }}"
|
||||||
|
when: automation_orchestrator_oidc_create_keycloak_groups | bool
|
||||||
|
|
||||||
|
# No secret is passed: Keycloak generates one on create and keeps it on update.
|
||||||
|
- name: Create Automation Orchestrator OIDC client in Keycloak
|
||||||
|
middleware_automation.keycloak.keycloak_client:
|
||||||
|
realm: "{{ automation_orchestrator_oidc_keycloak_realm }}"
|
||||||
|
client_id: "{{ automation_orchestrator_oidc_client_id }}"
|
||||||
|
name: Automation Orchestrator
|
||||||
|
description: OIDC client for Automation Orchestrator at {{ __automation_orchestrator_oidc_url }}
|
||||||
|
enabled: true
|
||||||
|
protocol: openid-connect
|
||||||
|
public_client: false
|
||||||
|
standard_flow_enabled: true
|
||||||
|
implicit_flow_enabled: false
|
||||||
|
direct_access_grants_enabled: false
|
||||||
|
service_accounts_enabled: false
|
||||||
|
root_url: "{{ __automation_orchestrator_oidc_url }}"
|
||||||
|
redirect_uris:
|
||||||
|
- "{{ __automation_orchestrator_oidc_url }}/api/v1/auth/oidc/callback"
|
||||||
|
web_origins:
|
||||||
|
- "+"
|
||||||
|
attributes:
|
||||||
|
post.logout.redirect.uris: "{{ __automation_orchestrator_oidc_url }}##{{ __automation_orchestrator_oidc_url }}/*"
|
||||||
|
protocol_mappers:
|
||||||
|
- name: groups
|
||||||
|
protocol: openid-connect
|
||||||
|
protocolMapper: oidc-group-membership-mapper
|
||||||
|
config:
|
||||||
|
full.path: "false"
|
||||||
|
id.token.claim: "true"
|
||||||
|
access.token.claim: "true"
|
||||||
|
userinfo.token.claim: "true"
|
||||||
|
claim.name: groups
|
||||||
|
state: present
|
||||||
|
no_log: "{{ automation_orchestrator_oidc_no_log }}"
|
||||||
|
|
||||||
|
- name: Read Automation Orchestrator client secret from Keycloak
|
||||||
|
community.general.keycloak_clientsecret_info:
|
||||||
|
realm: "{{ automation_orchestrator_oidc_keycloak_realm }}"
|
||||||
|
client_id: "{{ automation_orchestrator_oidc_client_id }}"
|
||||||
|
register: __automation_orchestrator_oidc_clientsecret
|
||||||
|
no_log: "{{ automation_orchestrator_oidc_no_log }}"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 3: Allow private-network issuers on the backend
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Set APP_OIDC_ALLOW_PRIVATE_NETWORKS on backend Deployment
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: patched
|
||||||
|
merge_type: strategic-merge
|
||||||
|
api_version: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
namespace: "{{ automation_orchestrator_oidc_namespace }}"
|
||||||
|
name: "{{ automation_orchestrator_oidc_instance_name }}-backend"
|
||||||
|
definition:
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: backend
|
||||||
|
env:
|
||||||
|
- name: APP_OIDC_ALLOW_PRIVATE_NETWORKS
|
||||||
|
value: "{{ automation_orchestrator_oidc_allow_private_networks | string | lower }}"
|
||||||
|
register: __automation_orchestrator_oidc_backend_patch
|
||||||
|
|
||||||
|
- name: Wait for backend rollout
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
namespace: "{{ automation_orchestrator_oidc_namespace }}"
|
||||||
|
name: "{{ automation_orchestrator_oidc_instance_name }}-backend"
|
||||||
|
register: __automation_orchestrator_oidc_backend
|
||||||
|
until: >-
|
||||||
|
__automation_orchestrator_oidc_backend.resources | length > 0 and
|
||||||
|
(__automation_orchestrator_oidc_backend.resources[0].status.observedGeneration | default(0)) >=
|
||||||
|
__automation_orchestrator_oidc_backend.resources[0].metadata.generation and
|
||||||
|
(__automation_orchestrator_oidc_backend.resources[0].status.updatedReplicas | default(0)) ==
|
||||||
|
__automation_orchestrator_oidc_backend.resources[0].spec.replicas and
|
||||||
|
(__automation_orchestrator_oidc_backend.resources[0].status.availableReplicas | default(0)) ==
|
||||||
|
__automation_orchestrator_oidc_backend.resources[0].spec.replicas
|
||||||
|
retries: 30
|
||||||
|
delay: 10
|
||||||
|
when: __automation_orchestrator_oidc_backend_patch is changed
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 4: Register Keycloak as an AO identity provider
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Read AO initial admin password
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: v1
|
||||||
|
kind: Secret
|
||||||
|
namespace: "{{ automation_orchestrator_oidc_namespace }}"
|
||||||
|
name: "{{ automation_orchestrator_oidc_instance_name }}-initial-admin-password"
|
||||||
|
register: __automation_orchestrator_oidc_admin_secret
|
||||||
|
no_log: "{{ automation_orchestrator_oidc_no_log }}"
|
||||||
|
|
||||||
|
- name: Log in to the AO API
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ __automation_orchestrator_oidc_url }}/api/v1/auth/login"
|
||||||
|
method: POST
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
username: "{{ automation_orchestrator_oidc_admin_user }}"
|
||||||
|
password: "{{ __automation_orchestrator_oidc_admin_secret.resources[0].data.password | b64decode }}"
|
||||||
|
validate_certs: "{{ automation_orchestrator_oidc_validate_certs }}"
|
||||||
|
register: __automation_orchestrator_oidc_login
|
||||||
|
no_log: "{{ automation_orchestrator_oidc_no_log }}"
|
||||||
|
|
||||||
|
- name: Call the AO API
|
||||||
|
module_defaults:
|
||||||
|
ansible.builtin.uri:
|
||||||
|
headers:
|
||||||
|
Authorization: "Bearer {{ __automation_orchestrator_oidc_login.json.access_token }}"
|
||||||
|
validate_certs: "{{ automation_orchestrator_oidc_validate_certs }}"
|
||||||
|
block:
|
||||||
|
- name: List AO groups
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ __automation_orchestrator_oidc_url }}/api/v1/groups?limit=100"
|
||||||
|
register: __automation_orchestrator_oidc_groups
|
||||||
|
|
||||||
|
- name: List AO identity providers
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ __automation_orchestrator_oidc_url }}/api/v1/identity_providers?limit=100"
|
||||||
|
register: __automation_orchestrator_oidc_idps
|
||||||
|
|
||||||
|
- name: Build desired identity provider configuration
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
__automation_orchestrator_oidc_existing: >-
|
||||||
|
{{ __automation_orchestrator_oidc_idps.json.resources
|
||||||
|
| selectattr('name', '==', automation_orchestrator_oidc_provider_name) | first | default({}) }}
|
||||||
|
__automation_orchestrator_oidc_config:
|
||||||
|
provider_type: oidc
|
||||||
|
idp_type: custom
|
||||||
|
auto_discovery: true
|
||||||
|
issuer_url: "{{ __automation_orchestrator_oidc_issuer }}"
|
||||||
|
client_id: "{{ automation_orchestrator_oidc_client_id }}"
|
||||||
|
redirect_uri: "{{ __automation_orchestrator_oidc_url }}/api/v1/auth/oidc/callback"
|
||||||
|
scopes: openid profile email
|
||||||
|
group_jmespath_expression: groups
|
||||||
|
allow_all_authenticated: false
|
||||||
|
enable_rp_initiated_logout: "{{ automation_orchestrator_oidc_rp_initiated_logout | bool }}"
|
||||||
|
__automation_orchestrator_oidc_group_ids: >-
|
||||||
|
{{ __automation_orchestrator_oidc_groups.json.resources | items2dict(key_name='name', value_name='id') }}
|
||||||
|
__automation_orchestrator_oidc_entries: []
|
||||||
|
|
||||||
|
- name: Build group mapping entries
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
__automation_orchestrator_oidc_entries: >-
|
||||||
|
{{ __automation_orchestrator_oidc_entries + [{
|
||||||
|
'idp_group_value': item.idp_group,
|
||||||
|
'mapped_group_id': __automation_orchestrator_oidc_group_ids[item.group]}] }}
|
||||||
|
loop: "{{ automation_orchestrator_oidc_group_mappings }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.idp_group }} -> {{ item.group }}"
|
||||||
|
|
||||||
|
# Compare everything except client_secret (never returned by the API).
|
||||||
|
- name: Determine whether the identity provider needs updating
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
__automation_orchestrator_oidc_needs_update: >-
|
||||||
|
{{ automation_orchestrator_oidc_force_update | bool
|
||||||
|
or not (__automation_orchestrator_oidc_existing.enabled | default(false))
|
||||||
|
or (__automation_orchestrator_oidc_existing.configuration | default({}))
|
||||||
|
!= ((__automation_orchestrator_oidc_existing.configuration | default({}))
|
||||||
|
| combine(__automation_orchestrator_oidc_config))
|
||||||
|
or ((__automation_orchestrator_oidc_existing.configuration.group_mapping_entries | default([])
|
||||||
|
| map(attribute='idp_group_value') | zip(__automation_orchestrator_oidc_existing.configuration.group_mapping_entries
|
||||||
|
| default([]) | map(attribute='mapped_group_id')) | map('join', '=') | sort)
|
||||||
|
!= (__automation_orchestrator_oidc_entries | map(attribute='idp_group_value')
|
||||||
|
| zip(__automation_orchestrator_oidc_entries | map(attribute='mapped_group_id'))
|
||||||
|
| map('join', '=') | sort)) }}
|
||||||
|
when: __automation_orchestrator_oidc_existing | length > 0
|
||||||
|
|
||||||
|
- name: Create Keycloak identity provider in AO
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ __automation_orchestrator_oidc_url }}/api/v1/identity_providers"
|
||||||
|
method: POST
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: "{{ automation_orchestrator_oidc_provider_name }}"
|
||||||
|
description: Keycloak realm {{ automation_orchestrator_oidc_keycloak_realm }}
|
||||||
|
configuration: >-
|
||||||
|
{{ __automation_orchestrator_oidc_config | combine({
|
||||||
|
'client_secret': __automation_orchestrator_oidc_clientsecret.clientsecret_info.value,
|
||||||
|
'group_mapping_entries': __automation_orchestrator_oidc_entries}) }}
|
||||||
|
status_code: 201
|
||||||
|
changed_when: true
|
||||||
|
when: __automation_orchestrator_oidc_existing | length == 0
|
||||||
|
no_log: "{{ automation_orchestrator_oidc_no_log }}"
|
||||||
|
|
||||||
|
- name: Update Keycloak identity provider in AO
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ __automation_orchestrator_oidc_url }}/api/v1/identity_providers/{{ __automation_orchestrator_oidc_existing.id }}"
|
||||||
|
method: PATCH
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
enabled: true
|
||||||
|
configuration: >-
|
||||||
|
{{ __automation_orchestrator_oidc_config | combine({
|
||||||
|
'client_secret': __automation_orchestrator_oidc_clientsecret.clientsecret_info.value,
|
||||||
|
'group_mapping_entries': __automation_orchestrator_oidc_entries}) }}
|
||||||
|
changed_when: true
|
||||||
|
when:
|
||||||
|
- __automation_orchestrator_oidc_existing | length > 0
|
||||||
|
- __automation_orchestrator_oidc_needs_update | bool
|
||||||
|
no_log: "{{ automation_orchestrator_oidc_no_log }}"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 5: Summary
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Display Automation Orchestrator OIDC summary
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "Keycloak OIDC configured for Automation Orchestrator:"
|
||||||
|
- " Login URL : {{ __automation_orchestrator_oidc_url }}"
|
||||||
|
- " Issuer : {{ __automation_orchestrator_oidc_issuer }}"
|
||||||
|
- " Client : {{ automation_orchestrator_oidc_client_id }}"
|
||||||
|
- " Mappings : {{ automation_orchestrator_oidc_group_mappings | map(attribute='idp_group')
|
||||||
|
| zip(automation_orchestrator_oidc_group_mappings | map(attribute='group'))
|
||||||
|
| map('join', ' -> ') | join(', ') }}"
|
||||||
|
- ""
|
||||||
|
- "Add users to the mapped Keycloak groups to grant access."
|
||||||
8
roles/automation_orchestrator_oidc/vars/main.yml
Normal file
8
roles/automation_orchestrator_oidc/vars/main.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
# Computed internal variables - do not override
|
||||||
|
__automation_orchestrator_oidc_valid_groups:
|
||||||
|
- admins
|
||||||
|
- auditors
|
||||||
|
- users
|
||||||
|
__automation_orchestrator_oidc_issuer: >-
|
||||||
|
{{ automation_orchestrator_oidc_keycloak_url | regex_replace('/$', '') }}/realms/{{ automation_orchestrator_oidc_keycloak_realm }}
|
||||||
81
roles/automation_orchestrator_operator/defaults/main.yml
Normal file
81
roles/automation_orchestrator_operator/defaults/main.yml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
# --- OLM subscription ---
|
||||||
|
# Automation Orchestrator is published in redhat-operators. To install a pre-release
|
||||||
|
# build instead (as aapctl's scripts/create-catalogsources.sh does), set:
|
||||||
|
# automation_orchestrator_operator_catalog_source_name: cs-automation-orchestrator
|
||||||
|
# automation_orchestrator_operator_catalog_image: quay.io/aap/ansible-automation-platform/automation-orchestrator-operator-index@sha256:...
|
||||||
|
# plus automation_orchestrator_operator_image_digest_mirrors below.
|
||||||
|
#
|
||||||
|
# To adopt an existing install (e.g. from OperatorHub), point operator_namespace at
|
||||||
|
# it: an existing OperatorGroup in that namespace is reused, not duplicated, and an
|
||||||
|
# already-installed CSV skips InstallPlan approval.
|
||||||
|
automation_orchestrator_operator_operator_namespace: automation-orchestrator
|
||||||
|
automation_orchestrator_operator_channel: stable
|
||||||
|
automation_orchestrator_operator_name: automation-orchestrator-operator
|
||||||
|
automation_orchestrator_operator_catalog_source_name: redhat-operators
|
||||||
|
# Index image for a custom CatalogSource; "" uses the existing catalog_source_name as-is.
|
||||||
|
automation_orchestrator_operator_catalog_image: ""
|
||||||
|
# Manual: the role approves the initial InstallPlan only (upgrades stay pending).
|
||||||
|
automation_orchestrator_operator_install_plan_approval: Manual
|
||||||
|
automation_orchestrator_operator_wait_timeout: 600
|
||||||
|
|
||||||
|
# --- Pre-release image mirrors (ImageDigestMirrorSet) ---
|
||||||
|
# Only needed for pre-release builds whose images are not on registry.redhat.io.
|
||||||
|
# Cluster-wide; applying it triggers a MachineConfigPool rollout. Pre-release example:
|
||||||
|
# - source: registry.redhat.io/ansible-automation-platform-27-next
|
||||||
|
# mirrors: [quay.io/aap/ansible-automation-platform-27-next]
|
||||||
|
# - source: registry.redhat.io/ansible-automation-platform-27
|
||||||
|
# mirrors: [quay.io/aap/ansible-automation-platform-27-next]
|
||||||
|
automation_orchestrator_operator_idms_name: idms-aap-27-next
|
||||||
|
automation_orchestrator_operator_image_digest_mirrors: []
|
||||||
|
automation_orchestrator_operator_mcp_wait_timeout: 1800
|
||||||
|
|
||||||
|
# --- AutomationOrchestrator CR ---
|
||||||
|
# Same as the operator namespace by default (aapctl runs OLM + CR in one namespace);
|
||||||
|
# override only if you need to split the cluster-scoped operator from the app instance.
|
||||||
|
automation_orchestrator_operator_app_namespace: "{{ automation_orchestrator_operator_operator_namespace }}"
|
||||||
|
automation_orchestrator_operator_instance_name: automation-orchestrator
|
||||||
|
|
||||||
|
# --- PostgreSQL (bring-your-own) ---
|
||||||
|
# Defaults match the Secrets/Service created by the cloudnativepg_operator role
|
||||||
|
# (run it first, or point these at a customer-managed PostgreSQL instance instead).
|
||||||
|
automation_orchestrator_operator_postgres_host: orchestrator-postgres-rw
|
||||||
|
automation_orchestrator_operator_postgres_port: 5432
|
||||||
|
automation_orchestrator_operator_postgres_ssl_mode: verify-ca
|
||||||
|
# Name of a pre-existing Secret (key ca.crt). CloudNativePG auto-generates
|
||||||
|
# "<cluster-name>-ca" for its Cluster CR. Only used with sslMode verify-ca/verify-full.
|
||||||
|
automation_orchestrator_operator_postgres_ca_cert_secret: orchestrator-postgres-ca
|
||||||
|
|
||||||
|
# Names of pre-existing Secrets (keys: database, username, password) — created by
|
||||||
|
# cloudnativepg_operator, or your own Secrets when using a customer-managed PostgreSQL.
|
||||||
|
automation_orchestrator_operator_backend_db_secret_name: orchestrator-postgres-secret
|
||||||
|
automation_orchestrator_operator_temporal_db_secret_name: temporal-postgres-secret
|
||||||
|
|
||||||
|
# --- Ingress ---
|
||||||
|
# Route (OpenShift) or Ingress (AKS); the operator manages the resource itself.
|
||||||
|
automation_orchestrator_operator_ingress_type: Route
|
||||||
|
automation_orchestrator_operator_ingress_host: ""
|
||||||
|
automation_orchestrator_operator_ingress_annotations: {}
|
||||||
|
# Only used when ingress_type is Ingress — name of a pre-existing kubernetes.io/tls Secret.
|
||||||
|
automation_orchestrator_operator_ingress_tls_secret_name: ""
|
||||||
|
|
||||||
|
# --- Component workload images (only needed for private registries) ---
|
||||||
|
automation_orchestrator_operator_image_pull_secrets: []
|
||||||
|
|
||||||
|
# --- Per-component overrides (replicas, resources, affinity, tolerations, nodeSelector) ---
|
||||||
|
# Passed through as-is into the matching spec.<component> block; leave {} for operator defaults.
|
||||||
|
automation_orchestrator_operator_backend: {}
|
||||||
|
automation_orchestrator_operator_ui: {}
|
||||||
|
automation_orchestrator_operator_worker: {}
|
||||||
|
automation_orchestrator_operator_background_worker: {}
|
||||||
|
automation_orchestrator_operator_redis: {}
|
||||||
|
automation_orchestrator_operator_temporal_server: {}
|
||||||
|
|
||||||
|
# Escape hatch: recursively merged into the generated spec last, for fields
|
||||||
|
# (fileStorage, otel, tls, secrets, workflowHttpRequestAllowedHosts, ...) not
|
||||||
|
# otherwise exposed as dedicated variables above.
|
||||||
|
automation_orchestrator_operator_extra_spec: {}
|
||||||
|
|
||||||
|
# --- Debugging ---
|
||||||
|
# Set to false to disable no_log and expose secret values in task output.
|
||||||
|
automation_orchestrator_operator_no_log: true
|
||||||
158
roles/automation_orchestrator_operator/meta/argument_specs.yml
Normal file
158
roles/automation_orchestrator_operator/meta/argument_specs.yml
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
---
|
||||||
|
argument_specs:
|
||||||
|
main:
|
||||||
|
short_description: Install Automation Orchestrator via its OLM operator (AutomationOrchestrator CR)
|
||||||
|
description:
|
||||||
|
- Installs the Automation Orchestrator operator via OLM (redhat-operators by default, or a
|
||||||
|
custom CatalogSource for pre-release builds) and creates a single
|
||||||
|
AutomationOrchestrator CR. Adopts an existing install (OperatorGroup, installed CSV)
|
||||||
|
when operator_namespace points at one.
|
||||||
|
- >-
|
||||||
|
Automation Orchestrator is bring-your-own-PostgreSQL: this role only references
|
||||||
|
existing K8s Secrets (keys: database, username, password) — it does not create the
|
||||||
|
database/user or their credential Secret. Defaults match the cloudnativepg_operator
|
||||||
|
role's output; run that role first, or point postgres_* variables and the secret-name
|
||||||
|
variables at a customer-managed PostgreSQL instance.
|
||||||
|
- Modeled on https://github.com/ansible/aapctl and https://github.com/automation-nexus/automation-orchestrator-operator.
|
||||||
|
options:
|
||||||
|
automation_orchestrator_operator_operator_namespace:
|
||||||
|
description: Namespace for the Automation Orchestrator operator and (by default) its CR.
|
||||||
|
type: str
|
||||||
|
default: automation-orchestrator
|
||||||
|
automation_orchestrator_operator_channel:
|
||||||
|
description: OLM subscription channel.
|
||||||
|
type: str
|
||||||
|
default: stable
|
||||||
|
automation_orchestrator_operator_name:
|
||||||
|
description: Operator package name in the catalog.
|
||||||
|
type: str
|
||||||
|
default: automation-orchestrator-operator
|
||||||
|
automation_orchestrator_operator_catalog_source_name:
|
||||||
|
description: CatalogSource to subscribe from (created by this role only when catalog_image is set).
|
||||||
|
type: str
|
||||||
|
default: redhat-operators
|
||||||
|
automation_orchestrator_operator_catalog_image:
|
||||||
|
description: >-
|
||||||
|
Index image for a custom CatalogSource (e.g. a pre-release build). Empty string
|
||||||
|
uses the existing catalog_source_name without creating a CatalogSource.
|
||||||
|
type: str
|
||||||
|
default: ""
|
||||||
|
automation_orchestrator_operator_install_plan_approval:
|
||||||
|
description: >-
|
||||||
|
Subscription installPlanApproval (Manual or Automatic). With Manual, the role approves
|
||||||
|
only the initial InstallPlan; once a CSV is installed, upgrades are left pending.
|
||||||
|
type: str
|
||||||
|
choices: [Manual, Automatic]
|
||||||
|
default: Manual
|
||||||
|
automation_orchestrator_operator_wait_timeout:
|
||||||
|
description: Seconds to wait for the operator and instance to become ready.
|
||||||
|
type: int
|
||||||
|
default: 600
|
||||||
|
automation_orchestrator_operator_idms_name:
|
||||||
|
description: Name of the cluster-wide ImageDigestMirrorSet for pre-release images.
|
||||||
|
type: str
|
||||||
|
default: idms-aap-27-next
|
||||||
|
automation_orchestrator_operator_image_digest_mirrors:
|
||||||
|
description: >-
|
||||||
|
spec.imageDigestMirrors entries ({source, mirrors}) for the IDMS. Applying a change
|
||||||
|
triggers a MachineConfigPool rollout. Only needed for pre-release builds; [] skips it.
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
default: []
|
||||||
|
automation_orchestrator_operator_mcp_wait_timeout:
|
||||||
|
description: Seconds to wait for the MachineConfigPool rollout after the IDMS changes.
|
||||||
|
type: int
|
||||||
|
default: 1800
|
||||||
|
automation_orchestrator_operator_app_namespace:
|
||||||
|
description: >-
|
||||||
|
Namespace for the AutomationOrchestrator instance. Exactly one CR is allowed per namespace.
|
||||||
|
Defaults to the same namespace as the operator.
|
||||||
|
type: str
|
||||||
|
default: "{operator_namespace}"
|
||||||
|
automation_orchestrator_operator_instance_name:
|
||||||
|
description: Name of the AutomationOrchestrator CR.
|
||||||
|
type: str
|
||||||
|
default: automation-orchestrator
|
||||||
|
automation_orchestrator_operator_postgres_host:
|
||||||
|
description: PostgreSQL host shared by the backend and Temporal databases.
|
||||||
|
type: str
|
||||||
|
default: orchestrator-postgres-rw
|
||||||
|
automation_orchestrator_operator_postgres_port:
|
||||||
|
description: PostgreSQL port.
|
||||||
|
type: int
|
||||||
|
default: 5432
|
||||||
|
automation_orchestrator_operator_postgres_ssl_mode:
|
||||||
|
description: PostgreSQL SSL mode (disable, allow, prefer, require, verify-ca, verify-full).
|
||||||
|
type: str
|
||||||
|
default: verify-ca
|
||||||
|
automation_orchestrator_operator_postgres_ca_cert_secret:
|
||||||
|
description: >-
|
||||||
|
Name of a pre-existing Secret (key ca.crt) with the PostgreSQL server CA certificate.
|
||||||
|
Required when sslMode is verify-ca or verify-full.
|
||||||
|
type: str
|
||||||
|
default: orchestrator-postgres-ca
|
||||||
|
automation_orchestrator_operator_backend_db_secret_name:
|
||||||
|
description: Name of the pre-existing Secret with the backend database credentials.
|
||||||
|
type: str
|
||||||
|
default: orchestrator-postgres-secret
|
||||||
|
automation_orchestrator_operator_temporal_db_secret_name:
|
||||||
|
description: Name of the pre-existing Secret with the Temporal database credentials.
|
||||||
|
type: str
|
||||||
|
default: temporal-postgres-secret
|
||||||
|
automation_orchestrator_operator_ingress_type:
|
||||||
|
description: External access type (Route on OpenShift, Ingress on AKS, or None).
|
||||||
|
type: str
|
||||||
|
default: Route
|
||||||
|
automation_orchestrator_operator_ingress_host:
|
||||||
|
description: Custom hostname for external access. Leave unset to use the operator's auto-generated hostname.
|
||||||
|
type: str
|
||||||
|
default: ""
|
||||||
|
automation_orchestrator_operator_ingress_annotations:
|
||||||
|
description: Annotations applied to the generated Route/Ingress resource.
|
||||||
|
type: dict
|
||||||
|
default: {}
|
||||||
|
automation_orchestrator_operator_ingress_tls_secret_name:
|
||||||
|
description: Name of a pre-existing kubernetes.io/tls Secret. Only used when ingress_type is Ingress.
|
||||||
|
type: str
|
||||||
|
default: ""
|
||||||
|
automation_orchestrator_operator_image_pull_secrets:
|
||||||
|
description: List of {name} references to Secrets used to pull component workload images.
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
default: []
|
||||||
|
automation_orchestrator_operator_backend:
|
||||||
|
description: Passthrough overrides (replicas, resources, affinity, tolerations, nodeSelector) for spec.backend.
|
||||||
|
type: dict
|
||||||
|
default: {}
|
||||||
|
automation_orchestrator_operator_ui:
|
||||||
|
description: Passthrough overrides for spec.ui.
|
||||||
|
type: dict
|
||||||
|
default: {}
|
||||||
|
automation_orchestrator_operator_worker:
|
||||||
|
description: Passthrough overrides for spec.worker.
|
||||||
|
type: dict
|
||||||
|
default: {}
|
||||||
|
automation_orchestrator_operator_background_worker:
|
||||||
|
description: >-
|
||||||
|
Passthrough overrides for spec.backgroundWorker. Note: replicas has no effect —
|
||||||
|
the operator always manages this component via an HPA.
|
||||||
|
type: dict
|
||||||
|
default: {}
|
||||||
|
automation_orchestrator_operator_redis:
|
||||||
|
description: Passthrough overrides for spec.redis.
|
||||||
|
type: dict
|
||||||
|
default: {}
|
||||||
|
automation_orchestrator_operator_temporal_server:
|
||||||
|
description: Passthrough overrides for spec.temporal.server.
|
||||||
|
type: dict
|
||||||
|
default: {}
|
||||||
|
automation_orchestrator_operator_extra_spec:
|
||||||
|
description: >-
|
||||||
|
Arbitrary dict recursively merged into the generated CR spec last — escape hatch for
|
||||||
|
fields not otherwise exposed (fileStorage, otel, tls, secrets, workflowHttpRequestAllowedHosts, ...).
|
||||||
|
type: dict
|
||||||
|
default: {}
|
||||||
|
automation_orchestrator_operator_no_log:
|
||||||
|
description: Set to false to disable no_log and expose secret values in task output. Never set false in shared environments.
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
18
roles/automation_orchestrator_operator/meta/main.yml
Normal file
18
roles/automation_orchestrator_operator/meta/main.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
galaxy_info:
|
||||||
|
author: ptoal
|
||||||
|
description: Install Automation Orchestrator via its OpenShift/AKS OLM operator
|
||||||
|
license: MIT
|
||||||
|
min_ansible_version: "2.16"
|
||||||
|
platforms:
|
||||||
|
- name: GenericLinux
|
||||||
|
versions:
|
||||||
|
- all
|
||||||
|
galaxy_tags:
|
||||||
|
- openshift
|
||||||
|
- aap
|
||||||
|
- operator
|
||||||
|
- olm
|
||||||
|
- ansible
|
||||||
|
|
||||||
|
dependencies: []
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
# Apply the ImageDigestMirrorSet that redirects pre-release AAP 2.7 images from
|
||||||
|
# registry.redhat.io to quay.io, then wait for the resulting MachineConfigPool
|
||||||
|
# rollout (matches https://github.com/ansible/aapctl scripts/create-catalogsources.sh).
|
||||||
|
#
|
||||||
|
# The IDMS is cluster-wide: it affects every digest pull from the source repos,
|
||||||
|
# not just Automation Orchestrator. The API may be briefly unavailable during the
|
||||||
|
# rollout on SNO, so the waits tolerate failed reads and keep retrying.
|
||||||
|
|
||||||
|
- name: Read MachineConfigPools before applying IDMS
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: machineconfiguration.openshift.io/v1
|
||||||
|
kind: MachineConfigPool
|
||||||
|
register: __automation_orchestrator_operator_mcp_before
|
||||||
|
|
||||||
|
- name: Create ImageDigestMirrorSet for pre-release images
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
definition:
|
||||||
|
apiVersion: config.openshift.io/v1
|
||||||
|
kind: ImageDigestMirrorSet
|
||||||
|
metadata:
|
||||||
|
name: "{{ automation_orchestrator_operator_idms_name }}"
|
||||||
|
spec:
|
||||||
|
imageDigestMirrors: "{{ automation_orchestrator_operator_image_digest_mirrors }}"
|
||||||
|
register: __automation_orchestrator_operator_idms
|
||||||
|
|
||||||
|
# Without this, the Updated wait below could pass on the pools' pre-IDMS state
|
||||||
|
# before the MCO has rendered the new MachineConfig.
|
||||||
|
- name: Wait for MCO to render new MachineConfigs
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: machineconfiguration.openshift.io/v1
|
||||||
|
kind: MachineConfigPool
|
||||||
|
register: __automation_orchestrator_operator_mcp
|
||||||
|
until: >-
|
||||||
|
(__automation_orchestrator_operator_mcp.resources | default([]) | length) > 0 and
|
||||||
|
(__automation_orchestrator_operator_mcp.resources
|
||||||
|
| map(attribute='spec.configuration.name')
|
||||||
|
| intersect(__automation_orchestrator_operator_mcp_before.resources | map(attribute='spec.configuration.name'))
|
||||||
|
| length) == 0
|
||||||
|
retries: "{{ __automation_orchestrator_operator_mcp_wait_retries }}"
|
||||||
|
delay: 10
|
||||||
|
when: __automation_orchestrator_operator_idms is changed
|
||||||
|
|
||||||
|
# Always runs, so a re-run also catches a rollout interrupted on a previous run.
|
||||||
|
- name: Wait for MachineConfigPool rollout to complete
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: machineconfiguration.openshift.io/v1
|
||||||
|
kind: MachineConfigPool
|
||||||
|
register: __automation_orchestrator_operator_mcp
|
||||||
|
until: >-
|
||||||
|
(__automation_orchestrator_operator_mcp.resources | default([]) | length) > 0 and
|
||||||
|
(__automation_orchestrator_operator_mcp.resources | map(attribute='spec.configuration.name') | list) ==
|
||||||
|
(__automation_orchestrator_operator_mcp.resources | map(attribute='status.configuration.name', default='') | list) and
|
||||||
|
(__automation_orchestrator_operator_mcp.resources
|
||||||
|
| map(attribute='status.conditions', default=[])
|
||||||
|
| map('selectattr', 'type', '==', 'Updated')
|
||||||
|
| map('selectattr', 'status', '==', 'True')
|
||||||
|
| map('list')
|
||||||
|
| reject('equalto', [])
|
||||||
|
| list | length) == (__automation_orchestrator_operator_mcp.resources | length)
|
||||||
|
retries: "{{ __automation_orchestrator_operator_mcp_wait_retries }}"
|
||||||
|
delay: 10
|
||||||
271
roles/automation_orchestrator_operator/tasks/main.yml
Normal file
271
roles/automation_orchestrator_operator/tasks/main.yml
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
---
|
||||||
|
# Install Automation Orchestrator via its OpenShift OLM operator.
|
||||||
|
#
|
||||||
|
# Automation Orchestrator is bring-your-own-PostgreSQL: this role only creates the
|
||||||
|
# OperatorGroup/Subscription and the AutomationOrchestrator CR — it references
|
||||||
|
# existing K8s Secrets (keys: database/username/password, or host/port/database/
|
||||||
|
# username/password for CNPG's basic-auth secrets) rather than creating them.
|
||||||
|
# Pair with the cloudnativepg_operator role (defaults match its secret names) or
|
||||||
|
# point postgres_* variables at a customer-managed PostgreSQL instance.
|
||||||
|
#
|
||||||
|
# Modeled on https://github.com/ansible/aapctl (internal/install/automationorchestrator)
|
||||||
|
# and https://github.com/automation-nexus/automation-orchestrator-operator.
|
||||||
|
# With Manual install-plan approval (the default) this role approves the initial
|
||||||
|
# InstallPlan itself; later upgrade InstallPlans are left for manual approval.
|
||||||
|
#
|
||||||
|
# All tasks are idempotent (kubernetes.core.k8s state: present).
|
||||||
|
|
||||||
|
- name: Verify PostgreSQL host is set
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- automation_orchestrator_operator_postgres_host | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
automation_orchestrator_operator_postgres_host must be set. Automation Orchestrator is
|
||||||
|
bring-your-own-PostgreSQL — either run the cloudnativepg_operator role first (its defaults
|
||||||
|
match this role's) or point this at a customer-managed PostgreSQL instance.
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 0: Mirror pre-release AAP 2.7 images (IDMS) — triggers an MCP rollout
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Configure pre-release image mirrors
|
||||||
|
ansible.builtin.include_tasks: image_mirrors.yml
|
||||||
|
when: automation_orchestrator_operator_image_digest_mirrors | length > 0
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 1: Install Automation Orchestrator operator via OLM
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Create Automation Orchestrator namespace
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
definition:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: "{{ automation_orchestrator_operator_operator_namespace }}"
|
||||||
|
|
||||||
|
- name: Create Automation Orchestrator app namespace
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
definition:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: "{{ automation_orchestrator_operator_app_namespace }}"
|
||||||
|
when: automation_orchestrator_operator_app_namespace != automation_orchestrator_operator_operator_namespace
|
||||||
|
|
||||||
|
- name: Create CatalogSource for Automation Orchestrator
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
definition:
|
||||||
|
apiVersion: operators.coreos.com/v1alpha1
|
||||||
|
kind: CatalogSource
|
||||||
|
metadata:
|
||||||
|
name: "{{ automation_orchestrator_operator_catalog_source_name }}"
|
||||||
|
namespace: openshift-marketplace
|
||||||
|
spec:
|
||||||
|
sourceType: grpc
|
||||||
|
image: "{{ automation_orchestrator_operator_catalog_image }}"
|
||||||
|
displayName: Automation Orchestrator
|
||||||
|
when: automation_orchestrator_operator_catalog_image | length > 0
|
||||||
|
|
||||||
|
# OLM allows only one OperatorGroup per namespace; reuse any existing one
|
||||||
|
# (e.g. the auto-named group an OperatorHub install creates).
|
||||||
|
- name: Look up existing OperatorGroups
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: operators.coreos.com/v1
|
||||||
|
kind: OperatorGroup
|
||||||
|
namespace: "{{ automation_orchestrator_operator_operator_namespace }}"
|
||||||
|
register: __automation_orchestrator_operator_ogs
|
||||||
|
|
||||||
|
- name: Create OperatorGroup for Automation Orchestrator
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
definition:
|
||||||
|
apiVersion: operators.coreos.com/v1
|
||||||
|
kind: OperatorGroup
|
||||||
|
metadata:
|
||||||
|
name: "{{ automation_orchestrator_operator_name }}"
|
||||||
|
namespace: "{{ automation_orchestrator_operator_operator_namespace }}"
|
||||||
|
spec: {}
|
||||||
|
when: __automation_orchestrator_operator_ogs.resources | length == 0
|
||||||
|
|
||||||
|
- name: Subscribe to Automation Orchestrator operator
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
definition:
|
||||||
|
apiVersion: operators.coreos.com/v1alpha1
|
||||||
|
kind: Subscription
|
||||||
|
metadata:
|
||||||
|
name: "{{ automation_orchestrator_operator_name }}"
|
||||||
|
namespace: "{{ automation_orchestrator_operator_operator_namespace }}"
|
||||||
|
spec:
|
||||||
|
channel: "{{ automation_orchestrator_operator_channel }}"
|
||||||
|
installPlanApproval: "{{ automation_orchestrator_operator_install_plan_approval }}"
|
||||||
|
name: "{{ automation_orchestrator_operator_name }}"
|
||||||
|
source: "{{ automation_orchestrator_operator_catalog_source_name }}"
|
||||||
|
sourceNamespace: openshift-marketplace
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 2: Approve the initial InstallPlan (if needed) and wait for the CSV
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Once a CSV is installed, pending InstallPlans are upgrades — leave those for
|
||||||
|
# manual approval rather than approving them on every run.
|
||||||
|
- name: Wait for OLM to resolve the Subscription
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: operators.coreos.com/v1alpha1
|
||||||
|
kind: Subscription
|
||||||
|
namespace: "{{ automation_orchestrator_operator_operator_namespace }}"
|
||||||
|
name: "{{ automation_orchestrator_operator_name }}"
|
||||||
|
register: __automation_orchestrator_operator_sub
|
||||||
|
until: >-
|
||||||
|
__automation_orchestrator_operator_sub.resources | length > 0 and
|
||||||
|
((__automation_orchestrator_operator_sub.resources[0].status.installedCSV | default('')) | length > 0 or
|
||||||
|
(__automation_orchestrator_operator_sub.resources[0].status.installPlanRef.name | default('')) | length > 0)
|
||||||
|
retries: "{{ __automation_orchestrator_operator_wait_retries }}"
|
||||||
|
delay: 10
|
||||||
|
|
||||||
|
- name: Resolve installed CSV name
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
__automation_orchestrator_operator_csv_name: >-
|
||||||
|
{{ __automation_orchestrator_operator_sub.resources[0].status.installedCSV | default('') }}
|
||||||
|
|
||||||
|
- name: Approve the initial InstallPlan
|
||||||
|
when: __automation_orchestrator_operator_csv_name | length == 0
|
||||||
|
block:
|
||||||
|
- name: Read the InstallPlan
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: operators.coreos.com/v1alpha1
|
||||||
|
kind: InstallPlan
|
||||||
|
namespace: "{{ automation_orchestrator_operator_operator_namespace }}"
|
||||||
|
name: "{{ __automation_orchestrator_operator_sub.resources[0].status.installPlanRef.name }}"
|
||||||
|
register: __automation_orchestrator_operator_installplan
|
||||||
|
|
||||||
|
- name: Approve the InstallPlan
|
||||||
|
kubernetes.core.k8s_json_patch:
|
||||||
|
api_version: operators.coreos.com/v1alpha1
|
||||||
|
kind: InstallPlan
|
||||||
|
namespace: "{{ automation_orchestrator_operator_operator_namespace }}"
|
||||||
|
name: "{{ __automation_orchestrator_operator_sub.resources[0].status.installPlanRef.name }}"
|
||||||
|
patch:
|
||||||
|
- op: add
|
||||||
|
path: /spec/approved
|
||||||
|
value: true
|
||||||
|
when: not (__automation_orchestrator_operator_installplan.resources[0].spec.approved | default(false))
|
||||||
|
|
||||||
|
- name: Resolve CSV name from the InstallPlan
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
__automation_orchestrator_operator_csv_name: >-
|
||||||
|
{{ __automation_orchestrator_operator_installplan.resources[0].spec.clusterServiceVersionNames[0] }}
|
||||||
|
|
||||||
|
- name: Wait for Automation Orchestrator CSV to succeed
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: operators.coreos.com/v1alpha1
|
||||||
|
kind: ClusterServiceVersion
|
||||||
|
namespace: "{{ automation_orchestrator_operator_operator_namespace }}"
|
||||||
|
name: "{{ __automation_orchestrator_operator_csv_name }}"
|
||||||
|
register: __automation_orchestrator_operator_csv
|
||||||
|
until: >-
|
||||||
|
__automation_orchestrator_operator_csv.resources | length > 0 and
|
||||||
|
__automation_orchestrator_operator_csv.resources[0].status.phase | default('') == 'Succeeded'
|
||||||
|
retries: "{{ __automation_orchestrator_operator_wait_retries }}"
|
||||||
|
delay: 10
|
||||||
|
|
||||||
|
- name: Wait for AutomationOrchestrator CRD to be available
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
name: automationorchestrators.aap.ansible.com
|
||||||
|
register: __automation_orchestrator_operator_crd
|
||||||
|
until: __automation_orchestrator_operator_crd.resources | length > 0
|
||||||
|
retries: "{{ __automation_orchestrator_operator_wait_retries }}"
|
||||||
|
delay: 10
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 3: Deploy the AutomationOrchestrator instance
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Build AutomationOrchestrator spec
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
__automation_orchestrator_operator_spec: >-
|
||||||
|
{{
|
||||||
|
{
|
||||||
|
'imagePullSecrets': automation_orchestrator_operator_image_pull_secrets,
|
||||||
|
'postgres': {
|
||||||
|
'host': automation_orchestrator_operator_postgres_host,
|
||||||
|
'port': automation_orchestrator_operator_postgres_port,
|
||||||
|
'sslMode': automation_orchestrator_operator_postgres_ssl_mode,
|
||||||
|
'backendDatabase': {'secretRef': {'name': automation_orchestrator_operator_backend_db_secret_name}},
|
||||||
|
'temporalDatabase': {'secretRef': {'name': automation_orchestrator_operator_temporal_db_secret_name}},
|
||||||
|
},
|
||||||
|
'ingress': {
|
||||||
|
'type': automation_orchestrator_operator_ingress_type,
|
||||||
|
'annotations': automation_orchestrator_operator_ingress_annotations,
|
||||||
|
},
|
||||||
|
'backend': automation_orchestrator_operator_backend,
|
||||||
|
'ui': automation_orchestrator_operator_ui,
|
||||||
|
'worker': automation_orchestrator_operator_worker,
|
||||||
|
'backgroundWorker': automation_orchestrator_operator_background_worker,
|
||||||
|
'redis': automation_orchestrator_operator_redis,
|
||||||
|
'temporal': {'server': automation_orchestrator_operator_temporal_server},
|
||||||
|
}
|
||||||
|
| combine(
|
||||||
|
{'postgres': {'caCertSecretRef': {'name': automation_orchestrator_operator_postgres_ca_cert_secret}}} if automation_orchestrator_operator_postgres_ca_cert_secret else {},
|
||||||
|
recursive=True
|
||||||
|
)
|
||||||
|
| combine(
|
||||||
|
{'ingress': {'host': automation_orchestrator_operator_ingress_host}} if automation_orchestrator_operator_ingress_host else {},
|
||||||
|
recursive=True
|
||||||
|
)
|
||||||
|
| combine(
|
||||||
|
{'ingress': {'tlsSecretRef': {'name': automation_orchestrator_operator_ingress_tls_secret_name}}}
|
||||||
|
if (automation_orchestrator_operator_ingress_type == 'Ingress' and automation_orchestrator_operator_ingress_tls_secret_name)
|
||||||
|
else {},
|
||||||
|
recursive=True
|
||||||
|
)
|
||||||
|
| combine(automation_orchestrator_operator_extra_spec, recursive=True)
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Create AutomationOrchestrator instance
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
definition:
|
||||||
|
apiVersion: aap.ansible.com/v1alpha1
|
||||||
|
kind: AutomationOrchestrator
|
||||||
|
metadata:
|
||||||
|
name: "{{ automation_orchestrator_operator_instance_name }}"
|
||||||
|
namespace: "{{ automation_orchestrator_operator_app_namespace }}"
|
||||||
|
spec: "{{ __automation_orchestrator_operator_spec }}"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 4: Wait for the instance to be ready
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Wait for AutomationOrchestrator to be ready
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: aap.ansible.com/v1alpha1
|
||||||
|
kind: AutomationOrchestrator
|
||||||
|
namespace: "{{ automation_orchestrator_operator_app_namespace }}"
|
||||||
|
name: "{{ automation_orchestrator_operator_instance_name }}"
|
||||||
|
register: __automation_orchestrator_operator_status
|
||||||
|
ignore_errors: true
|
||||||
|
until: >-
|
||||||
|
__automation_orchestrator_operator_status.resources is defined and
|
||||||
|
__automation_orchestrator_operator_status.resources | length > 0 and
|
||||||
|
(__automation_orchestrator_operator_status.resources[0].status.conditions | default([])
|
||||||
|
| selectattr('type', '==', 'Ready')
|
||||||
|
| selectattr('status', '==', 'True') | list | length > 0)
|
||||||
|
retries: "{{ __automation_orchestrator_operator_wait_retries }}"
|
||||||
|
delay: 10
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 5: Display summary
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Display Automation Orchestrator deployment summary
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "Automation Orchestrator deployment complete!"
|
||||||
|
- " Namespace : {{ automation_orchestrator_operator_app_namespace }}"
|
||||||
|
- " Instance : {{ automation_orchestrator_operator_instance_name }}"
|
||||||
|
- " Ingress type: {{ automation_orchestrator_operator_ingress_type }}"
|
||||||
|
- ""
|
||||||
|
- "Find the UI hostname with: oc get routes -n {{ automation_orchestrator_operator_app_namespace }}"
|
||||||
|
- "The initial admin password is auto-generated unless spec.secrets.initialAdminPasswordSecretRef is set:"
|
||||||
|
- " oc get secret {{ automation_orchestrator_operator_instance_name }}-initial-admin-password -n {{ automation_orchestrator_operator_app_namespace }} -o jsonpath='{.data.password}' | base64 -d"
|
||||||
4
roles/automation_orchestrator_operator/vars/main.yml
Normal file
4
roles/automation_orchestrator_operator/vars/main.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
# Computed internal variables - do not override
|
||||||
|
__automation_orchestrator_operator_wait_retries: "{{ (automation_orchestrator_operator_wait_timeout / 10) | int }}"
|
||||||
|
__automation_orchestrator_operator_mcp_wait_retries: "{{ (automation_orchestrator_operator_mcp_wait_timeout / 10) | int }}"
|
||||||
53
roles/cloudnativepg_operator/defaults/main.yml
Normal file
53
roles/cloudnativepg_operator/defaults/main.yml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
# --- OLM subscription ---
|
||||||
|
# CloudNativePG is in the certified-operators catalog by default (no custom
|
||||||
|
# CatalogSource needed, unlike automation_orchestrator_operator).
|
||||||
|
cloudnativepg_operator_operator_namespace: cloudnative-pg
|
||||||
|
cloudnativepg_operator_channel: stable-v1
|
||||||
|
cloudnativepg_operator_name: cloudnative-pg
|
||||||
|
cloudnativepg_operator_catalog_source: certified-operators
|
||||||
|
cloudnativepg_operator_wait_timeout: 600
|
||||||
|
|
||||||
|
# --- Cluster CR ---
|
||||||
|
# Namespace for the Cluster, credential Secrets, and Database CRs. Defaults to
|
||||||
|
# the automation_orchestrator_operator app namespace — keep the two in sync if overridden.
|
||||||
|
cloudnativepg_operator_cluster_namespace: automation-orchestrator
|
||||||
|
cloudnativepg_operator_cluster_name: orchestrator-postgres
|
||||||
|
cloudnativepg_operator_owner_user: orchestrator
|
||||||
|
cloudnativepg_operator_instances: 1
|
||||||
|
# A primary PDB on a single instance is permanently at its limit (PodDisruptionBudgetAtLimit)
|
||||||
|
# and blocks node drains; CNPG recommends disabling it for single-instance dev clusters.
|
||||||
|
cloudnativepg_operator_enable_pdb: "{{ cloudnativepg_operator_instances | int > 1 }}"
|
||||||
|
cloudnativepg_operator_image_name: ghcr.io/cloudnative-pg/postgresql:15
|
||||||
|
cloudnativepg_operator_storage_size: 5Gi
|
||||||
|
cloudnativepg_operator_storage_class: ""
|
||||||
|
cloudnativepg_operator_max_connections: 200
|
||||||
|
|
||||||
|
# --- Databases (Database CRs, all owned by cloudnativepg_operator_owner_user) ---
|
||||||
|
cloudnativepg_operator_databases:
|
||||||
|
- name: orchestrator
|
||||||
|
dbname: orchestrator
|
||||||
|
- name: temporal
|
||||||
|
dbname: temporal
|
||||||
|
- name: temporal-visibility
|
||||||
|
dbname: temporal_visibility
|
||||||
|
|
||||||
|
# --- Credential secrets (type kubernetes.io/basic-auth) ---
|
||||||
|
# All share the same generated username/password; only the referenced database differs.
|
||||||
|
# secrets[0] is used as the CNPG bootstrap.initdb secret (creates the owner role + first DB).
|
||||||
|
cloudnativepg_operator_secrets:
|
||||||
|
- name: orchestrator-postgres-secret
|
||||||
|
database: orchestrator
|
||||||
|
- name: temporal-postgres-secret
|
||||||
|
database: temporal
|
||||||
|
- name: temporal-visibility-postgres-secret
|
||||||
|
database: temporal_visibility
|
||||||
|
|
||||||
|
# --- Vault ---
|
||||||
|
# KV2 path/key where the generated postgres password is stored and retrieved.
|
||||||
|
cloudnativepg_operator_vault_mount: kv
|
||||||
|
cloudnativepg_operator_vault_path: cloudnativepg/orchestrator-postgres
|
||||||
|
|
||||||
|
# --- Debugging ---
|
||||||
|
# Set to false to disable no_log and expose secret values in task output.
|
||||||
|
cloudnativepg_operator_no_log: true
|
||||||
94
roles/cloudnativepg_operator/meta/argument_specs.yml
Normal file
94
roles/cloudnativepg_operator/meta/argument_specs.yml
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
---
|
||||||
|
argument_specs:
|
||||||
|
main:
|
||||||
|
short_description: Install CloudNativePG via OLM and provision a PostgreSQL Cluster + Databases
|
||||||
|
description:
|
||||||
|
- Installs the CloudNativePG operator via OLM (certified-operators catalog, Manual install-plan
|
||||||
|
approval which this role approves itself) and creates a single-instance PostgreSQL Cluster CR
|
||||||
|
with Database CRs for the orchestrator, temporal, and temporal_visibility databases — all owned
|
||||||
|
by one shared PostgreSQL role.
|
||||||
|
- >-
|
||||||
|
WARNING: this is a demo/dev-oriented, non-HA PostgreSQL deployment. For production, point
|
||||||
|
automation_orchestrator_operator at a customer-managed PostgreSQL instance instead.
|
||||||
|
- The generated owner-role password is stored in Vault at cloudnativepg_operator_vault_path
|
||||||
|
and reused on subsequent runs for idempotency.
|
||||||
|
options:
|
||||||
|
cloudnativepg_operator_operator_namespace:
|
||||||
|
description: Namespace for the CloudNativePG operator.
|
||||||
|
type: str
|
||||||
|
default: cloudnative-pg
|
||||||
|
cloudnativepg_operator_channel:
|
||||||
|
description: OLM subscription channel.
|
||||||
|
type: str
|
||||||
|
default: stable-v1
|
||||||
|
cloudnativepg_operator_name:
|
||||||
|
description: Operator package name in the catalog.
|
||||||
|
type: str
|
||||||
|
default: cloudnative-pg
|
||||||
|
cloudnativepg_operator_catalog_source:
|
||||||
|
description: Name of the CatalogSource to subscribe from.
|
||||||
|
type: str
|
||||||
|
default: certified-operators
|
||||||
|
cloudnativepg_operator_wait_timeout:
|
||||||
|
description: Seconds to wait for the operator, CSV, and Database CRs to become ready.
|
||||||
|
type: int
|
||||||
|
default: 600
|
||||||
|
cloudnativepg_operator_cluster_namespace:
|
||||||
|
description: Namespace for the Cluster CR, credential Secrets, and Database CRs.
|
||||||
|
type: str
|
||||||
|
default: automation-orchestrator
|
||||||
|
cloudnativepg_operator_cluster_name:
|
||||||
|
description: Name of the CloudNativePG Cluster CR.
|
||||||
|
type: str
|
||||||
|
default: orchestrator-postgres
|
||||||
|
cloudnativepg_operator_owner_user:
|
||||||
|
description: PostgreSQL role that owns all provisioned databases.
|
||||||
|
type: str
|
||||||
|
default: orchestrator
|
||||||
|
cloudnativepg_operator_instances:
|
||||||
|
description: Number of Postgres instances in the Cluster (1 = no HA).
|
||||||
|
type: int
|
||||||
|
default: 1
|
||||||
|
cloudnativepg_operator_enable_pdb:
|
||||||
|
description: >-
|
||||||
|
Create the CNPG primary PodDisruptionBudget. Defaults to true only when instances > 1;
|
||||||
|
a single-instance PDB is always at its limit and blocks node drains.
|
||||||
|
type: bool
|
||||||
|
cloudnativepg_operator_image_name:
|
||||||
|
description: PostgreSQL container image for the Cluster.
|
||||||
|
type: str
|
||||||
|
default: ghcr.io/cloudnative-pg/postgresql:15
|
||||||
|
cloudnativepg_operator_storage_size:
|
||||||
|
description: Size of the Cluster's PersistentVolumeClaim.
|
||||||
|
type: str
|
||||||
|
default: 5Gi
|
||||||
|
cloudnativepg_operator_storage_class:
|
||||||
|
description: StorageClass for the Cluster's PVC. Empty string uses the cluster default.
|
||||||
|
type: str
|
||||||
|
default: ""
|
||||||
|
cloudnativepg_operator_max_connections:
|
||||||
|
description: postgresql.parameters.max_connections. Set to 0 to omit and use the Postgres default.
|
||||||
|
type: int
|
||||||
|
default: 200
|
||||||
|
cloudnativepg_operator_databases:
|
||||||
|
description: List of {name, dbname} Database CRs to create, all owned by cloudnativepg_operator_owner_user.
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
cloudnativepg_operator_secrets:
|
||||||
|
description: >-
|
||||||
|
List of {name, database} credential Secrets to create (type kubernetes.io/basic-auth), all
|
||||||
|
sharing the same generated username/password. secrets[0] is used as the CNPG bootstrap.initdb secret.
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
cloudnativepg_operator_vault_mount:
|
||||||
|
description: Vault KV2 mount point where the generated owner-role password is stored.
|
||||||
|
type: str
|
||||||
|
default: kv
|
||||||
|
cloudnativepg_operator_vault_path:
|
||||||
|
description: Vault KV2 path where the generated owner-role password is stored.
|
||||||
|
type: str
|
||||||
|
default: cloudnativepg/orchestrator-postgres
|
||||||
|
cloudnativepg_operator_no_log:
|
||||||
|
description: Set to false to disable no_log and expose secret values in task output. Never set false in shared environments.
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
18
roles/cloudnativepg_operator/meta/main.yml
Normal file
18
roles/cloudnativepg_operator/meta/main.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
galaxy_info:
|
||||||
|
author: ptoal
|
||||||
|
description: Install CloudNativePG via OLM and provision a PostgreSQL Cluster for Automation Orchestrator
|
||||||
|
license: MIT
|
||||||
|
min_ansible_version: "2.16"
|
||||||
|
platforms:
|
||||||
|
- name: GenericLinux
|
||||||
|
versions:
|
||||||
|
- all
|
||||||
|
galaxy_tags:
|
||||||
|
- openshift
|
||||||
|
- postgresql
|
||||||
|
- cnpg
|
||||||
|
- operator
|
||||||
|
- olm
|
||||||
|
|
||||||
|
dependencies: []
|
||||||
230
roles/cloudnativepg_operator/tasks/main.yml
Normal file
230
roles/cloudnativepg_operator/tasks/main.yml
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
---
|
||||||
|
# Install CloudNativePG via OLM and provision the PostgreSQL Cluster + Database CRs
|
||||||
|
# and credential Secrets that back Automation Orchestrator (backend + Temporal +
|
||||||
|
# Temporal Visibility databases, one shared owner role).
|
||||||
|
#
|
||||||
|
# WARNING: this is a single-instance (no HA), operator-provisioned PostgreSQL
|
||||||
|
# cluster intended for demo/dev use. For production, point automation_orchestrator_operator
|
||||||
|
# at a customer-managed PostgreSQL instance instead of running this role.
|
||||||
|
#
|
||||||
|
# Modeled on https://github.com/ansible/aapctl (internal/install/cloudnativepg).
|
||||||
|
# CloudNativePG's OLM subscription uses Manual install-plan approval, so this role
|
||||||
|
# approves the InstallPlan itself rather than relying on Automatic.
|
||||||
|
#
|
||||||
|
# All tasks are idempotent (kubernetes.core.k8s state: present).
|
||||||
|
|
||||||
|
- name: Resolve postgres credentials from Vault
|
||||||
|
ansible.builtin.include_tasks: vault_secrets.yml
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 1: Install CloudNativePG operator via OLM
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Create CloudNativePG operator namespace
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
definition:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: "{{ cloudnativepg_operator_operator_namespace }}"
|
||||||
|
|
||||||
|
- name: Create CloudNativePG OperatorGroup
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
definition:
|
||||||
|
apiVersion: operators.coreos.com/v1
|
||||||
|
kind: OperatorGroup
|
||||||
|
metadata:
|
||||||
|
name: "{{ cloudnativepg_operator_name }}"
|
||||||
|
namespace: "{{ cloudnativepg_operator_operator_namespace }}"
|
||||||
|
spec: {}
|
||||||
|
|
||||||
|
- name: Subscribe to CloudNativePG operator
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
definition:
|
||||||
|
apiVersion: operators.coreos.com/v1alpha1
|
||||||
|
kind: Subscription
|
||||||
|
metadata:
|
||||||
|
name: "{{ cloudnativepg_operator_name }}"
|
||||||
|
namespace: "{{ cloudnativepg_operator_operator_namespace }}"
|
||||||
|
spec:
|
||||||
|
channel: "{{ cloudnativepg_operator_channel }}"
|
||||||
|
installPlanApproval: Manual
|
||||||
|
name: "{{ cloudnativepg_operator_name }}"
|
||||||
|
source: "{{ cloudnativepg_operator_catalog_source }}"
|
||||||
|
sourceNamespace: openshift-marketplace
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 2: Approve the Manual InstallPlan and wait for the CSV to succeed
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Wait for OLM to create an InstallPlan for the Subscription
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: operators.coreos.com/v1alpha1
|
||||||
|
kind: Subscription
|
||||||
|
namespace: "{{ cloudnativepg_operator_operator_namespace }}"
|
||||||
|
name: "{{ cloudnativepg_operator_name }}"
|
||||||
|
register: __cloudnativepg_operator_sub
|
||||||
|
until: >-
|
||||||
|
__cloudnativepg_operator_sub.resources | length > 0 and
|
||||||
|
(__cloudnativepg_operator_sub.resources[0].status.installPlanRef.name | default('')) | length > 0
|
||||||
|
retries: "{{ __cloudnativepg_operator_wait_retries }}"
|
||||||
|
delay: 10
|
||||||
|
|
||||||
|
- name: Read the InstallPlan
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: operators.coreos.com/v1alpha1
|
||||||
|
kind: InstallPlan
|
||||||
|
namespace: "{{ cloudnativepg_operator_operator_namespace }}"
|
||||||
|
name: "{{ __cloudnativepg_operator_sub.resources[0].status.installPlanRef.name }}"
|
||||||
|
register: __cloudnativepg_operator_installplan
|
||||||
|
|
||||||
|
- name: Approve the InstallPlan
|
||||||
|
kubernetes.core.k8s_json_patch:
|
||||||
|
api_version: operators.coreos.com/v1alpha1
|
||||||
|
kind: InstallPlan
|
||||||
|
namespace: "{{ cloudnativepg_operator_operator_namespace }}"
|
||||||
|
name: "{{ __cloudnativepg_operator_sub.resources[0].status.installPlanRef.name }}"
|
||||||
|
patch:
|
||||||
|
- op: add
|
||||||
|
path: /spec/approved
|
||||||
|
value: true
|
||||||
|
|
||||||
|
- name: Wait for CloudNativePG CSV to succeed
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: operators.coreos.com/v1alpha1
|
||||||
|
kind: ClusterServiceVersion
|
||||||
|
namespace: "{{ cloudnativepg_operator_operator_namespace }}"
|
||||||
|
name: "{{ __cloudnativepg_operator_installplan.resources[0].spec.clusterServiceVersionNames[0] }}"
|
||||||
|
register: __cloudnativepg_operator_csv
|
||||||
|
until: >-
|
||||||
|
__cloudnativepg_operator_csv.resources | length > 0 and
|
||||||
|
__cloudnativepg_operator_csv.resources[0].status.phase | default('') == 'Succeeded'
|
||||||
|
retries: "{{ __cloudnativepg_operator_wait_retries }}"
|
||||||
|
delay: 10
|
||||||
|
|
||||||
|
- name: Wait for CloudNativePG controller manager webhook to be reachable
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: v1
|
||||||
|
kind: Endpoints
|
||||||
|
namespace: "{{ cloudnativepg_operator_operator_namespace }}"
|
||||||
|
name: cnpg-controller-manager-service
|
||||||
|
register: __cloudnativepg_operator_endpoints
|
||||||
|
until: >-
|
||||||
|
__cloudnativepg_operator_endpoints.resources | length > 0 and
|
||||||
|
(__cloudnativepg_operator_endpoints.resources[0].subsets | default([])
|
||||||
|
| map(attribute='addresses', default=[]) | flatten | length) > 0
|
||||||
|
retries: "{{ __cloudnativepg_operator_wait_retries }}"
|
||||||
|
delay: 10
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 3: Credential secrets (type kubernetes.io/basic-auth)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Create cluster namespace
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
definition:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: "{{ cloudnativepg_operator_cluster_namespace }}"
|
||||||
|
|
||||||
|
- name: Create postgres credential secrets
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
definition:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
namespace: "{{ cloudnativepg_operator_cluster_namespace }}"
|
||||||
|
type: kubernetes.io/basic-auth
|
||||||
|
stringData:
|
||||||
|
username: "{{ cloudnativepg_operator_owner_user }}"
|
||||||
|
password: "{{ __cloudnativepg_operator_password }}"
|
||||||
|
host: "{{ cloudnativepg_operator_cluster_name }}-rw.{{ cloudnativepg_operator_cluster_namespace }}.svc"
|
||||||
|
port: "5432"
|
||||||
|
database: "{{ item.database }}"
|
||||||
|
loop: "{{ cloudnativepg_operator_secrets }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
no_log: "{{ cloudnativepg_operator_no_log }}"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 4: PostgreSQL Cluster
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Create PostgreSQL Cluster
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
definition:
|
||||||
|
apiVersion: postgresql.cnpg.io/v1
|
||||||
|
kind: Cluster
|
||||||
|
metadata:
|
||||||
|
name: "{{ cloudnativepg_operator_cluster_name }}"
|
||||||
|
namespace: "{{ cloudnativepg_operator_cluster_namespace }}"
|
||||||
|
spec: >-
|
||||||
|
{{
|
||||||
|
{
|
||||||
|
'instances': cloudnativepg_operator_instances,
|
||||||
|
'enablePDB': cloudnativepg_operator_enable_pdb | bool,
|
||||||
|
'imageName': cloudnativepg_operator_image_name,
|
||||||
|
'bootstrap': {
|
||||||
|
'initdb': {
|
||||||
|
'owner': cloudnativepg_operator_owner_user,
|
||||||
|
'secret': {'name': cloudnativepg_operator_secrets[0].name},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'storage': {'size': cloudnativepg_operator_storage_size},
|
||||||
|
}
|
||||||
|
| combine({'storage': {'storageClass': cloudnativepg_operator_storage_class}} if cloudnativepg_operator_storage_class else {}, recursive=True)
|
||||||
|
| combine({'postgresql': {'parameters': {'max_connections': cloudnativepg_operator_max_connections | string}}} if cloudnativepg_operator_max_connections else {}, recursive=True)
|
||||||
|
}}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 5: Logical databases
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Create Database CRs
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
definition:
|
||||||
|
apiVersion: postgresql.cnpg.io/v1
|
||||||
|
kind: Database
|
||||||
|
metadata:
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
namespace: "{{ cloudnativepg_operator_cluster_namespace }}"
|
||||||
|
spec:
|
||||||
|
name: "{{ item.dbname }}"
|
||||||
|
owner: "{{ cloudnativepg_operator_owner_user }}"
|
||||||
|
cluster:
|
||||||
|
name: "{{ cloudnativepg_operator_cluster_name }}"
|
||||||
|
loop: "{{ cloudnativepg_operator_databases }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.name }}"
|
||||||
|
|
||||||
|
- name: Wait for Database CRs to be applied
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: postgresql.cnpg.io/v1
|
||||||
|
kind: Database
|
||||||
|
namespace: "{{ cloudnativepg_operator_cluster_namespace }}"
|
||||||
|
register: __cloudnativepg_operator_databases_status
|
||||||
|
until: >-
|
||||||
|
__cloudnativepg_operator_databases_status.resources | length >= (cloudnativepg_operator_databases | length) and
|
||||||
|
(__cloudnativepg_operator_databases_status.resources
|
||||||
|
| rejectattr('status.applied', 'undefined')
|
||||||
|
| selectattr('status.applied')
|
||||||
|
| list | length) == (__cloudnativepg_operator_databases_status.resources | length)
|
||||||
|
retries: "{{ __cloudnativepg_operator_wait_retries }}"
|
||||||
|
delay: 10
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 6: Summary
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
- name: Display CloudNativePG deployment summary
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "CloudNativePG PostgreSQL cluster ready!"
|
||||||
|
- " Operator namespace : {{ cloudnativepg_operator_operator_namespace }}"
|
||||||
|
- " Cluster : {{ cloudnativepg_operator_cluster_name }} ({{ cloudnativepg_operator_cluster_namespace }})"
|
||||||
|
- " Owner role : {{ cloudnativepg_operator_owner_user }}"
|
||||||
|
- " Databases : {{ cloudnativepg_operator_databases | map(attribute='dbname') | join(', ') }}"
|
||||||
|
- " Credential secrets : {{ cloudnativepg_operator_secrets | map(attribute='name') | join(', ') }}"
|
||||||
26
roles/cloudnativepg_operator/tasks/vault_secrets.yml
Normal file
26
roles/cloudnativepg_operator/tasks/vault_secrets.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
# Read the shared postgres owner password from Vault; generate and store it if missing.
|
||||||
|
# Sets __cloudnativepg_operator_password, consumed by the K8s secret tasks in main.yml.
|
||||||
|
|
||||||
|
- name: Read cloudnativepg postgres password from Vault
|
||||||
|
community.hashi_vault.vault_kv2_get:
|
||||||
|
path: "{{ cloudnativepg_operator_vault_path }}"
|
||||||
|
engine_mount_point: "{{ cloudnativepg_operator_vault_mount }}"
|
||||||
|
register: __cloudnativepg_operator_vault_read
|
||||||
|
failed_when: false
|
||||||
|
no_log: "{{ cloudnativepg_operator_no_log }}"
|
||||||
|
|
||||||
|
- name: Resolve postgres password (generate if missing)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
__cloudnativepg_operator_password: >-
|
||||||
|
{{ (__cloudnativepg_operator_vault_read.secret | default({})).password
|
||||||
|
| default(lookup('community.general.random_string', length=32, special=false)) }}
|
||||||
|
no_log: "{{ cloudnativepg_operator_no_log }}"
|
||||||
|
|
||||||
|
- name: Write postgres password to Vault
|
||||||
|
community.hashi_vault.vault_kv2_write:
|
||||||
|
path: "{{ cloudnativepg_operator_vault_path }}"
|
||||||
|
engine_mount_point: "{{ cloudnativepg_operator_vault_mount }}"
|
||||||
|
data:
|
||||||
|
password: "{{ __cloudnativepg_operator_password }}"
|
||||||
|
no_log: "{{ cloudnativepg_operator_no_log }}"
|
||||||
3
roles/cloudnativepg_operator/vars/main.yml
Normal file
3
roles/cloudnativepg_operator/vars/main.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
# Computed internal variables - do not override
|
||||||
|
__cloudnativepg_operator_wait_retries: "{{ (cloudnativepg_operator_wait_timeout / 10) | int }}"
|
||||||
13
roles/sno_alert_tuning/defaults/main.yml
Normal file
13
roles/sno_alert_tuning/defaults/main.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
# Platform alerts that are false positives on a single-control-plane cluster.
|
||||||
|
# Dropped via an AlertRelabelConfig (monitoring.openshift.io/v1), so they never
|
||||||
|
# reach Alertmanager. Alerts are matched by exact alertname.
|
||||||
|
#
|
||||||
|
# KubeCPUOvercommit / KubeMemoryOvercommit: the HA branch of these rules assumes
|
||||||
|
# the cluster survives losing its largest node. With one control-plane node plus
|
||||||
|
# smaller workers, that node is master-0 — losing it loses the cluster anyway.
|
||||||
|
sno_alert_tuning_dropped_alerts:
|
||||||
|
- KubeCPUOvercommit
|
||||||
|
- KubeMemoryOvercommit
|
||||||
|
|
||||||
|
sno_alert_tuning_config_name: sno-single-control-plane
|
||||||
17
roles/sno_alert_tuning/meta/argument_specs.yml
Normal file
17
roles/sno_alert_tuning/meta/argument_specs.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
argument_specs:
|
||||||
|
main:
|
||||||
|
short_description: Drop platform alerts that are false positives on a single-control-plane cluster
|
||||||
|
description:
|
||||||
|
- Creates an AlertRelabelConfig in openshift-monitoring that drops the listed alerts
|
||||||
|
before they reach Alertmanager. An empty list removes the AlertRelabelConfig.
|
||||||
|
options:
|
||||||
|
sno_alert_tuning_dropped_alerts:
|
||||||
|
description: Exact alertnames to drop.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
default: [KubeCPUOvercommit, KubeMemoryOvercommit]
|
||||||
|
sno_alert_tuning_config_name:
|
||||||
|
description: Name of the AlertRelabelConfig.
|
||||||
|
type: str
|
||||||
|
default: sno-single-control-plane
|
||||||
16
roles/sno_alert_tuning/meta/main.yml
Normal file
16
roles/sno_alert_tuning/meta/main.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
galaxy_info:
|
||||||
|
author: ptoal
|
||||||
|
description: Drop platform alerts that are false positives on single-node OpenShift
|
||||||
|
license: MIT
|
||||||
|
min_ansible_version: "2.16"
|
||||||
|
platforms:
|
||||||
|
- name: GenericLinux
|
||||||
|
versions:
|
||||||
|
- all
|
||||||
|
galaxy_tags:
|
||||||
|
- openshift
|
||||||
|
- monitoring
|
||||||
|
- sno
|
||||||
|
|
||||||
|
dependencies: []
|
||||||
19
roles/sno_alert_tuning/tasks/main.yml
Normal file
19
roles/sno_alert_tuning/tasks/main.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
# Drop platform alerts that only fire because the cluster has a single
|
||||||
|
# control-plane node. Uses the supported AlertRelabelConfig API rather than
|
||||||
|
# silences (which expire) or editing cluster-monitoring-operator rules (reverted).
|
||||||
|
|
||||||
|
- name: Drop single-control-plane false-positive alerts
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: "{{ 'present' if sno_alert_tuning_dropped_alerts | length > 0 else 'absent' }}"
|
||||||
|
definition:
|
||||||
|
apiVersion: monitoring.openshift.io/v1
|
||||||
|
kind: AlertRelabelConfig
|
||||||
|
metadata:
|
||||||
|
name: "{{ sno_alert_tuning_config_name }}"
|
||||||
|
namespace: openshift-monitoring
|
||||||
|
spec:
|
||||||
|
configs:
|
||||||
|
- sourceLabels: [alertname]
|
||||||
|
regex: "{{ sno_alert_tuning_dropped_alerts | map('regex_escape') | join('|') }}"
|
||||||
|
action: Drop
|
||||||
9
roles/sno_etcd_defrag/defaults/main.yml
Normal file
9
roles/sno_etcd_defrag/defaults/main.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
# Defragment only when the in-use fraction of the etcd database drops below this
|
||||||
|
# ratio and the database exceeds the minimum size (mirrors the thresholds of the
|
||||||
|
# etcdDatabaseHighFragmentationRatio alert: < 50% in use and > 100 MiB).
|
||||||
|
sno_etcd_defrag_max_in_use_ratio: 0.5
|
||||||
|
sno_etcd_defrag_min_db_size_bytes: 104857600
|
||||||
|
sno_etcd_defrag_command_timeout: 60
|
||||||
|
# Set true to defragment regardless of the thresholds.
|
||||||
|
sno_etcd_defrag_force: false
|
||||||
26
roles/sno_etcd_defrag/meta/argument_specs.yml
Normal file
26
roles/sno_etcd_defrag/meta/argument_specs.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
argument_specs:
|
||||||
|
main:
|
||||||
|
short_description: Defragment etcd on a single-control-plane OpenShift cluster
|
||||||
|
description:
|
||||||
|
- cluster-etcd-operator disables automatic defragmentation on SingleReplica topology.
|
||||||
|
This role defragments the local member when fragmentation exceeds the thresholds of
|
||||||
|
the etcdDatabaseHighFragmentationRatio alert, then clears any NOSPACE alarm.
|
||||||
|
- Blocks etcd for the duration of the defrag. Requires pods/exec in openshift-etcd.
|
||||||
|
options:
|
||||||
|
sno_etcd_defrag_max_in_use_ratio:
|
||||||
|
description: Defragment when dbSizeInUse / dbSize is below this ratio.
|
||||||
|
type: float
|
||||||
|
default: 0.5
|
||||||
|
sno_etcd_defrag_min_db_size_bytes:
|
||||||
|
description: Skip defragmentation when the database is smaller than this.
|
||||||
|
type: int
|
||||||
|
default: 104857600
|
||||||
|
sno_etcd_defrag_command_timeout:
|
||||||
|
description: etcdctl --command-timeout for the defrag, in seconds.
|
||||||
|
type: int
|
||||||
|
default: 60
|
||||||
|
sno_etcd_defrag_force:
|
||||||
|
description: Defragment regardless of the thresholds.
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
16
roles/sno_etcd_defrag/meta/main.yml
Normal file
16
roles/sno_etcd_defrag/meta/main.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
galaxy_info:
|
||||||
|
author: ptoal
|
||||||
|
description: Defragment etcd on single-node OpenShift (automatic defrag is disabled there)
|
||||||
|
license: MIT
|
||||||
|
min_ansible_version: "2.16"
|
||||||
|
platforms:
|
||||||
|
- name: GenericLinux
|
||||||
|
versions:
|
||||||
|
- all
|
||||||
|
galaxy_tags:
|
||||||
|
- openshift
|
||||||
|
- etcd
|
||||||
|
- sno
|
||||||
|
|
||||||
|
dependencies: []
|
||||||
103
roles/sno_etcd_defrag/tasks/main.yml
Normal file
103
roles/sno_etcd_defrag/tasks/main.yml
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
---
|
||||||
|
# Defragment etcd on a single-control-plane cluster.
|
||||||
|
#
|
||||||
|
# cluster-etcd-operator disables its defrag controller on SingleReplica topology
|
||||||
|
# (Etcd/cluster condition DefragControllerDisabled=True), so fragmentation grows
|
||||||
|
# until etcdDatabaseHighFragmentationRatio fires. This role follows the documented
|
||||||
|
# manual procedure: defrag the local member, then clear any NOSPACE alarm.
|
||||||
|
#
|
||||||
|
# Defragmenting the only member blocks etcd reads/writes for the duration (seconds
|
||||||
|
# for a few hundred MiB) — schedule it outside busy periods.
|
||||||
|
#
|
||||||
|
# Requires pods/exec in openshift-etcd (cluster-admin kubeconfig).
|
||||||
|
|
||||||
|
- name: Find the etcd pod
|
||||||
|
kubernetes.core.k8s_info:
|
||||||
|
api_version: v1
|
||||||
|
kind: Pod
|
||||||
|
namespace: openshift-etcd
|
||||||
|
label_selectors:
|
||||||
|
- app=etcd
|
||||||
|
field_selectors:
|
||||||
|
- status.phase=Running
|
||||||
|
register: __sno_etcd_defrag_pods
|
||||||
|
failed_when: __sno_etcd_defrag_pods.resources | length != 1
|
||||||
|
|
||||||
|
- name: Set etcd pod name
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
__sno_etcd_defrag_pod: "{{ __sno_etcd_defrag_pods.resources[0].metadata.name }}"
|
||||||
|
|
||||||
|
- name: Read etcd database size before defrag
|
||||||
|
kubernetes.core.k8s_exec:
|
||||||
|
namespace: openshift-etcd
|
||||||
|
pod: "{{ __sno_etcd_defrag_pod }}"
|
||||||
|
container: etcdctl
|
||||||
|
command: etcdctl endpoint status -w json
|
||||||
|
register: __sno_etcd_defrag_before
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Evaluate fragmentation
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
__sno_etcd_defrag_status_before: "{{ (__sno_etcd_defrag_before.stdout | from_json)[0].Status }}"
|
||||||
|
|
||||||
|
- name: Decide whether to defragment
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
__sno_etcd_defrag_needed: >-
|
||||||
|
{{ sno_etcd_defrag_force | bool or (
|
||||||
|
__sno_etcd_defrag_status_before.dbSize | int > sno_etcd_defrag_min_db_size_bytes | int and
|
||||||
|
(__sno_etcd_defrag_status_before.dbSizeInUse | float / __sno_etcd_defrag_status_before.dbSize | float)
|
||||||
|
< sno_etcd_defrag_max_in_use_ratio | float) }}
|
||||||
|
|
||||||
|
- name: Defragment etcd
|
||||||
|
when: __sno_etcd_defrag_needed | bool
|
||||||
|
block:
|
||||||
|
- name: Defragment the local etcd member
|
||||||
|
kubernetes.core.k8s_exec:
|
||||||
|
namespace: openshift-etcd
|
||||||
|
pod: "{{ __sno_etcd_defrag_pod }}"
|
||||||
|
container: etcdctl
|
||||||
|
command: >-
|
||||||
|
sh -c 'unset ETCDCTL_ENDPOINTS;
|
||||||
|
etcdctl --command-timeout={{ sno_etcd_defrag_command_timeout }}s --endpoints=https://localhost:2379 defrag'
|
||||||
|
register: __sno_etcd_defrag_result
|
||||||
|
changed_when: true
|
||||||
|
failed_when: __sno_etcd_defrag_result.rc != 0
|
||||||
|
|
||||||
|
- name: List etcd alarms
|
||||||
|
kubernetes.core.k8s_exec:
|
||||||
|
namespace: openshift-etcd
|
||||||
|
pod: "{{ __sno_etcd_defrag_pod }}"
|
||||||
|
container: etcdctl
|
||||||
|
command: etcdctl alarm list
|
||||||
|
register: __sno_etcd_defrag_alarms
|
||||||
|
changed_when: false
|
||||||
|
retries: 6
|
||||||
|
delay: 10
|
||||||
|
until: __sno_etcd_defrag_alarms.rc | default(1) == 0
|
||||||
|
|
||||||
|
- name: Disarm NOSPACE alarm
|
||||||
|
kubernetes.core.k8s_exec:
|
||||||
|
namespace: openshift-etcd
|
||||||
|
pod: "{{ __sno_etcd_defrag_pod }}"
|
||||||
|
container: etcdctl
|
||||||
|
command: etcdctl alarm disarm
|
||||||
|
changed_when: true
|
||||||
|
when: "'NOSPACE' in __sno_etcd_defrag_alarms.stdout"
|
||||||
|
|
||||||
|
- name: Read etcd database size after defrag
|
||||||
|
kubernetes.core.k8s_exec:
|
||||||
|
namespace: openshift-etcd
|
||||||
|
pod: "{{ __sno_etcd_defrag_pod }}"
|
||||||
|
container: etcdctl
|
||||||
|
command: etcdctl endpoint status -w json
|
||||||
|
register: __sno_etcd_defrag_after
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Display etcd defrag summary
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "etcd {{ 'defragmented' if __sno_etcd_defrag_needed | bool else 'not defragmented (below thresholds)' }}"
|
||||||
|
- " Before : {{ (__sno_etcd_defrag_status_before.dbSize / 1048576) | round(0) | int }} MiB allocated,
|
||||||
|
{{ (__sno_etcd_defrag_status_before.dbSizeInUse / 1048576) | round(0) | int }} MiB in use"
|
||||||
|
- " After : {{ ((__sno_etcd_defrag_after.stdout | from_json)[0].Status.dbSize / 1048576) | round(0) | int ~ ' MiB allocated'
|
||||||
|
if __sno_etcd_defrag_needed | bool else 'n/a' }}"
|
||||||
Reference in New Issue
Block a user