diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c49cd2927..c0792c675 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -57,10 +57,12 @@ /roles/sideload_kernel @redhatci/platform # ztp +/roles/acm_spoke_mgmt @redhatci/ztp /roles/configure_ztp_gitops_repo @redhatci/ztp /roles/configure_ztp_gitops_apps @redhatci/ztp /roles/gitops_configure_repo @redhatci/ztp /roles/install_operator_gitops @redhatci/ztp +/roles/remove_ztp_gitops_resources @redhatci/ztp # maintainers /.github @redhatci/maintain diff --git a/README.md b/README.md index a658d8bcb..ae3987df3 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Name | Description [redhatci.ocp.acm_hypershift](https://github.com/redhatci/ansible-collection-redhatci-ocp/blob/main/roles/acm_hypershift/README.md) | Deployment of Hypershift (Hosted Control Planes) through ACM (Advanced Cluster Management). [redhatci.ocp.acm_setup](https://github.com/redhatci/ansible-collection-redhatci-ocp/blob/main/roles/acm_setup/README.md) | Performs the Advanced Cluster Management (ACM) post-installation tasks [redhatci.ocp.acm_sno](https://github.com/redhatci/ansible-collection-redhatci-ocp/blob/main/roles/acm_sno/README.md) | Deployment of SNO (Single Node OpenShift) instances using ACM (Advanced Cluster Management) +[redhatci.ocp.acm_spoke_mgmt](https://github.com/redhatci/ansible-collection-redhatci-ocp/blob/main/roles/acm_spoke_mgmt/README.md) | This role allows to perform multiple management operations related to a spoke cluster,e.g. attach a spoke cluster to a given hub cluster, or detach a spoke cluster from a given hub cluster. [redhatci.ocp.add_day2_node](https://github.com/redhatci/ansible-collection-redhatci-ocp/blob/main/roles/add_day2_node/README.md) | Adds as nodes to a pre-existing cluster using a pre-existing on-prem assisted installer instance. [redhatci.ocp.apply_nmstate](https://github.com/redhatci/ansible-collection-redhatci-ocp/blob/main/roles/apply_nmstate/README.md) | Applies nmstate network configuration to a host. [redhatci.ocp.approve_csrs](https://github.com/redhatci/ansible-collection-redhatci-ocp/blob/main/roles/approve_csrs/README.md) |Checks for Cert Signing Requests in the pending state and approves them until nodes in the day2_workers group are present in the oc nodes output. @@ -110,6 +111,7 @@ Name | Description [redhatci.ocp.prune_catalog](https://github.com/redhatci/ansible-collection-redhatci-ocp/blob/main/roles/prune_catalog/README.md) | Create a pruned catalog, leaving only the specified operators. [redhatci.ocp.pyxis](https://github.com/redhatci/ansible-collection-redhatci-ocp/blob/main/roles/pyxis/README.md) | Interacts with Pyxis API to submit Preflight certification results [redhatci.ocp.redhat_tests](https://github.com/redhatci/ansible-collection-redhatci-ocp/blob/main/roles/redhat_tests/README.md) | [Openshift End to End tests](https://github.com/openshift/openshift-tests) +[redhatci.ocp.remove_ztp_gitops_resources](https://github.com/redhatci/ansible-collection-redhatci-ocp/blob/main/roles/remove_ztp_gitops_resources/README.md) | Remove all GitOps related resources for a given spoke cluster, excepting the cluster namespace, which is not deleted because this will imply the spoke cluster is detached from the hub cluster. [redhatci.ocp.resources_to_components](https://github.com/redhatci/ansible-collection-redhatci-ocp/blob/main/roles/resources_to_components/README.md) | Creates DCI components based on Kubernetes resources [redhatci.ocp.rhoai](roles/rhoai/README.md) | Install the Red Hat OpenShift AI operators [redhatci.ocp.setup_assisted_installer](https://github.com/redhatci/ansible-collection-redhatci-ocp/blob/main/roles/setup_assisted_installer/README.md) | Deploys an on-prem assisted installer diff --git a/roles/acm_spoke_mgmt/README.md b/roles/acm_spoke_mgmt/README.md new file mode 100644 index 000000000..21b8ccd1d --- /dev/null +++ b/roles/acm_spoke_mgmt/README.md @@ -0,0 +1,76 @@ +# ACM Spoke Management operations + +This role allows to perform multiple management operations on a given spoke cluster. + +Tasks must be executed on the hub cluster that is controlling/going to control the given spoke cluster. + +## Actions allowed + +The following variable controls the action that can be performed within this role: + +Name | Type | Required | Default | Description +---------------------------- | ------ | -------- | -------------------------------------------------- | ------------------------------------------------------------ +asm_action | string | yes | - | Action to be performed. Accepted values are `detach` and `attach`. + +## Detach a spoke cluster + +This action allows to detach a spoke cluster from a given hub cluster. + +This is based on the following [OCP documentation](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.5/html/clusters/managing-your-clusters#remove-a-cluster-by-using-the-cli). + +### Requirements + +* Spoke cluster running in a given hub cluster. + +### Role Variables + +Name | Type | Required | Default | Description +--------------------------- | ------ | -------- | -------------------------------------------------- | ------------------------------------------------------------- +asm_cluster_name | string | yes | - | Cluster name, used for identifying the ManagedCluster and the Namespace + +### Example + +```yaml +- name: Detach spoke cluster from hub cluster + ansible.builtin.include_role: + name: redhatci.ocp.acm_spoke_mgmt + vars: + asm_action: "detach" + asm_cluster_name: "mycluster" +``` + +## Attach a spoke cluster + +This action allows to attach a spoke cluster to a given hub cluster. + +This is based on the following [OCP documentation](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.5/html/clusters/managing-your-clusters#importing-a-target-managed-cluster-to-the-hub-cluster). + +Three resources are created in this role: + +- ManagedCluster resource (and related Namespace is automatically created after its creation). +- Autoimport Secret. +- KlusterletAddonConfig. + +### Requirements + +* Active spoke cluster already created, having access to its kubeconfig. + +### Role Variables + +Name | Type | Required | Default | Description +---------------------------- | ------ | -------- | -------------------------------------------------- | ------------------------------------------------------------ +asm_cluster_kubeconfig_path | string | yes | - | Path to spoke cluster's kubeconfig file +asm_cluster_name | string | yes | - | Cluster name, used for identifying the ManagedCluster and the Namespace + + +### Example + +```yaml +- name: Attach spoke cluster to hub cluster + ansible.builtin.include_role: + name: redhatci.ocp.acm_spoke_mgmt + vars: + asm_action: "attach" + asm_cluster_kubeconfig_path: "/path/to/spoke/kubeconfig" + asm_cluster_name: "mycluster" +``` diff --git a/roles/acm_spoke_mgmt/defaults/main.yaml b/roles/acm_spoke_mgmt/defaults/main.yaml new file mode 100644 index 000000000..ed97d539c --- /dev/null +++ b/roles/acm_spoke_mgmt/defaults/main.yaml @@ -0,0 +1 @@ +--- diff --git a/roles/acm_spoke_mgmt/tasks/attach.yaml b/roles/acm_spoke_mgmt/tasks/attach.yaml new file mode 100644 index 000000000..d0ea0e62b --- /dev/null +++ b/roles/acm_spoke_mgmt/tasks/attach.yaml @@ -0,0 +1,104 @@ +--- + +- name: Assert the required variables are defined + ansible.builtin.assert: + that: + - asm_cluster_kubeconfig_path is defined + - asm_cluster_kubeconfig_path | length > 0 + - asm_cluster_name is defined + - asm_cluster_name | length > 0 + +# This will automatically create the cluster namespace, called +# asm_cluster_name too. +- name: Create ManagedCluster + community.kubernetes.k8s: + definition: + apiVersion: cluster.open-cluster-management.io/v1 + kind: ManagedCluster + metadata: + name: "{{ asm_cluster_name }}" + labels: + name: "{{ asm_cluster_name }}" + cloud: auto-detect + vendor: auto-detect + annotations: {} + spec: + hubAcceptsClient: true + +- name: Ensure spoke cluster namespace has been created + community.kubernetes.k8s_info: + api: v1 + kind: Namespace + name: "{{ asm_cluster_name }}" + register: _asm_namespace_status + until: + - _asm_namespace_status.resources | length > 0 + retries: 60 + delay: 10 + +# To import the spoke cluster to the hub cluster, we need to create +# a secret that contains the spoke cluster's kubeconfig. +# This requires a special formatting, including double line breaks +# and a correct indentation. +- name: Create autoimport secret + block: + - name: Get kubeconfig content + ansible.builtin.slurp: + src: "{{ asm_cluster_kubeconfig_path }}" + register: _asm_kubeconfig_content + no_log: true + + - name: Save kubeconfig content in a variable + ansible.builtin.set_fact: + asm_kubeconfig: "{{ _asm_kubeconfig_content['content'] | b64decode }}" + no_log: true + + - name: Apply Secret to attach spoke cluster to hub cluster + community.kubernetes.k8s: + state: present + definition: "{{ lookup('ansible.builtin.template', 'autoimport_secret.yml.j2') | from_yaml }}" + no_log: true + +# To correctly join the hub cluster, ManagedClusterJoined condition must be True. +- name: Ensure ManagedCluster has joined the hub cluster + community.kubernetes.k8s_info: + api: cluster.open-cluster-management.io/v1 + kind: ManagedCluster + name: "{{ asm_cluster_name }}" + register: _asm_managedcluster_status + vars: + _asm_status_query: "resources[0].status.conditions[?type=='ManagedClusterJoined'].status" + _asm_update_status: "{{ _asm_managedcluster_status | json_query(_asm_status_query) | flatten | unique }}" # noqa: jinja[invalid] + until: + - _asm_managedcluster_status.resources is defined + - _asm_managedcluster_status.resources | length > 0 + - _asm_update_status == ['True'] + retries: 30 + delay: 10 + +- name: Create KlusterletAddonConfig + community.kubernetes.k8s: + definition: + apiVersion: agent.open-cluster-management.io/v1 + kind: KlusterletAddonConfig + metadata: + name: "{{ asm_cluster_name }}" + namespace: "{{ asm_cluster_name }}" + spec: + clusterName: "{{ asm_cluster_name }}" + clusterNamespace: "{{ asm_cluster_name }}" + clusterLabels: + cloud: auto-detect + vendor: auto-detect + applicationManager: + enabled: false + certPolicyController: + enabled: false + iamPolicyController: + enabled: false + policyController: + enabled: true + searchCollector: + enabled: false + +... diff --git a/roles/acm_spoke_mgmt/tasks/detach.yaml b/roles/acm_spoke_mgmt/tasks/detach.yaml new file mode 100644 index 000000000..04c9ba0a8 --- /dev/null +++ b/roles/acm_spoke_mgmt/tasks/detach.yaml @@ -0,0 +1,56 @@ +--- + +- name: Assert the required variables are defined + ansible.builtin.assert: + that: + - asm_cluster_name is defined + - asm_cluster_name | length > 0 + +- name: Delete spoke's ManagedCluster + community.kubernetes.k8s: + api_version: cluster.open-cluster-management.io/v1 + kind: ManagedCluster + name: "{{ asm_cluster_name }}" + state: absent + wait: true + wait_sleep: 5 + wait_timeout: 300 + +- name: Ensure ManagedCluster resource has been deleted + community.kubernetes.k8s_info: + api: cluster.open-cluster-management.io/v1 + kind: ManagedCluster + name: "{{ asm_cluster_name }}" + register: _asm_managed_cluster_status + until: + - _asm_managed_cluster_status.resources | length == 0 + retries: 18 + delay: 10 + +# Normally, the namespace is deleted after removing the ManagedCluster, excepting +# ZTP-based spoke clusters, since there are more resources living in the namespace. +# For this case, we need to explicitly remove the namespace. +# This may take some time. +- name: Delete spoke cluster namespace + community.kubernetes.k8s: + api_version: v1 + kind: Namespace + name: "{{ asm_cluster_name }}" + state: absent + wait: true + wait_sleep: 5 + wait_timeout: 600 + +# Same, this may take some time. +- name: Ensure spoke cluster namespace has been deleted + community.kubernetes.k8s_info: + api: v1 + kind: Namespace + name: "{{ asm_cluster_name }}" + register: _asm_namespace_status + until: + - _asm_namespace_status.resources | length == 0 + retries: 60 + delay: 10 + +... diff --git a/roles/acm_spoke_mgmt/tasks/main.yaml b/roles/acm_spoke_mgmt/tasks/main.yaml new file mode 100644 index 000000000..07146687f --- /dev/null +++ b/roles/acm_spoke_mgmt/tasks/main.yaml @@ -0,0 +1,23 @@ +--- + +- name: Check if the selected action is allowed + vars: + asm_actions: + - detach + - attach + ansible.builtin.assert: + that: + - asm_action | lower in asm_actions + fail_msg: "{{ asm_action }} is not a supported action" + +- name: Detach a spoke cluster + ansible.builtin.include_tasks: detach.yaml + when: + - asm_action == 'detach' + +- name: Attach a spoke cluster + ansible.builtin.include_tasks: attach.yaml + when: + - asm_action == 'attach' + +... diff --git a/roles/acm_spoke_mgmt/templates/autoimport_secret.yml.j2 b/roles/acm_spoke_mgmt/templates/autoimport_secret.yml.j2 new file mode 100644 index 000000000..a00196f8a --- /dev/null +++ b/roles/acm_spoke_mgmt/templates/autoimport_secret.yml.j2 @@ -0,0 +1,16 @@ +--- + +apiVersion: v1 +kind: Secret +metadata: + name: auto-import-secret + namespace: {{ asm_cluster_name }} +stringData: + autoImportRetry: "2" + kubeconfig: > + +{{ asm_kubeconfig | regex_replace('\n', '\n\n') | indent(4, True) }} + +type: Opaque + +... diff --git a/roles/gitops_configure_repo/README.md b/roles/gitops_configure_repo/README.md index 705190105..b29d66084 100644 --- a/roles/gitops_configure_repo/README.md +++ b/roles/gitops_configure_repo/README.md @@ -16,8 +16,10 @@ Role Variables Name | Type | Required | Default | Description --------------------------- | ------ | -------- | -------------------------------------------------- | ------------------------------------------------------------- gcr_ssh_key_path | string | yes | `{{ lookup('env', 'HOME') }}/.ssh/id_rsa` | Path to the SSH private key file used to log into the GitOps manifest repository. -gcr_ssh_known_hosts | string | no | - | Should be the ssh known hosts. It is required by ArgoCD when working with a SSH key. +gcr_ssh_known_hosts | string | no | - | Should be the SSH known hosts. It is required by ArgoCD when working with a SSH key. gcr_ztp_gitops_repo | string | yes | - | URL to the ZTP GitOps Git repository. +gcr_argo_cd_known_host_cm | string | no | argocd-ssh-known-hosts-cm | ConfigMap that will save the ArgoCD SSH known hosts. +gcr_private_repo_secret | string | no | private-repo | Secret that will hold the private repo credentials. Dependencies ------------ diff --git a/roles/gitops_configure_repo/defaults/main.yaml b/roles/gitops_configure_repo/defaults/main.yaml index 043595912..141b1971b 100644 --- a/roles/gitops_configure_repo/defaults/main.yaml +++ b/roles/gitops_configure_repo/defaults/main.yaml @@ -1,2 +1,4 @@ --- gcr_ssh_key_path: "{{ lookup('env', 'HOME') }}/.ssh/id_rsa" +gcr_argo_cd_known_host_cm: "argocd-ssh-known-hosts-cm" +gcr_private_repo_secret: "private-repo" diff --git a/roles/gitops_configure_repo/tasks/main.yaml b/roles/gitops_configure_repo/tasks/main.yaml index 4978d048c..0f3fcc6bb 100644 --- a/roles/gitops_configure_repo/tasks/main.yaml +++ b/roles/gitops_configure_repo/tasks/main.yaml @@ -12,7 +12,7 @@ community.kubernetes.k8s: api_version: v1 kind: ConfigMap - name: argocd-ssh-known-hosts-cm + name: "{{ gcr_argo_cd_known_host_cm }}" namespace: openshift-gitops resource_definition: labels: diff --git a/roles/gitops_configure_repo/templates/ssh_private_repo.yaml.j2 b/roles/gitops_configure_repo/templates/ssh_private_repo.yaml.j2 index c664cbac1..d8ab4ca8c 100644 --- a/roles/gitops_configure_repo/templates/ssh_private_repo.yaml.j2 +++ b/roles/gitops_configure_repo/templates/ssh_private_repo.yaml.j2 @@ -1,7 +1,7 @@ apiVersion: v1 kind: Secret metadata: - name: private-repo + name: {{ gcr_private_repo_secret }} namespace: openshift-gitops labels: argocd.argoproj.io/secret-type: repository diff --git a/roles/remove_ztp_gitops_resources/README.md b/roles/remove_ztp_gitops_resources/README.md new file mode 100644 index 000000000..db0661c82 --- /dev/null +++ b/roles/remove_ztp_gitops_resources/README.md @@ -0,0 +1,47 @@ +# Remove ZTP GitOps resources + +Remove all GitOps related resources for a given spoke cluster, excepting the cluster namespace, which is not deleted because this will imply the spoke cluster is detached from the hub cluster. + +For performing a cluster detachment, then use the [acm_detach_spoke_cluster](../acm_detach_spoke_cluster) role accordingly. + +## Requirements + +* Hub cluster configured for launching ZTP. +* ArgoCD configured in the hub cluster. +* Spoke cluster deploying thanks to ArgoCD applications already configured. + +## Role Variables + +Default values for role variables are based on default values used in [gitops_configure_repo](../gitops_configure_repo) and [configure_ztp_gitops_apps](../configure_ztp_gitops_apps) roles. Being more precise for the second case, it's using the default resource names applied by the ZTP Site Generator container. + +Please adapt the values of these variables for your use case. + +Name | Type | Required | Default | Description +--------------------------- | ------ | -------- | -------------------------------------------------- | ------------------------------------------------------------- +rzgr_gitops_applications | list | no | ["clusters", "policies"] | GitOps Applications related to SiteConfig and Policy resources. +rzgr_gitops_appprojects | list | no | ["ztp-app-project", "policy-app-project"] | GitOps AppProjects related to SiteConfig and Policy resources. +rzgr_policies_namespace | string | no | policies-sub | Namespace for the policy generator template resources. It can not be the sabe as the clusters namespace. +rzgr_extra_namespaces | list | no | ["ztp-common", "ztp-group", "ztp-site"] | Extra namespaces that are created for GitOps policy creation by default. +rzgr_cluster_role_bindings | list | no | ["gitops-policy", "gitops-cluster"] | ClusterRoleBindings created for the deployment of GitOps Policy and SiteConfig. +rzgr_private_repo_secret | string | no | private-repo | Secret that will hold the private repo credentials. +rzgr_argo_cd_known_host_cm | string | no | argocd-ssh-known-hosts-cm | ConfigMap that will save the ArgoCD SSH known hosts. + +## Example + +Following example is the most basic call to this role, using the default arguments for each role variable: + +``` +- name: Remove ZTP GitOps resources + ansible.builtin.include_role: + name: redhatci.ocp.remove_ztp_gitops_resources +``` + +If you want to override any default value of any role variable, then you need to provide it with `vars`, for example: + +``` +- name: Remove ZTP GitOps resources + ansible.builtin.include_role: + name: redhatci.ocp.remove_ztp_gitops_resources + vars: + rzgr_policies_namespace: "mycluster-policies" +``` diff --git a/roles/remove_ztp_gitops_resources/defaults/main.yaml b/roles/remove_ztp_gitops_resources/defaults/main.yaml new file mode 100644 index 000000000..6d4c8d02d --- /dev/null +++ b/roles/remove_ztp_gitops_resources/defaults/main.yaml @@ -0,0 +1,18 @@ +--- + +rzgr_gitops_applications: + - clusters + - policies +rzgr_gitops_appprojects: + - ztp-app-project + - policy-app-project +rzgr_policies_namespace: policies-sub +rzgr_extra_namespaces: + - ztp-common + - ztp-group + - ztp-site +rzgr_cluster_role_bindings: + - gitops-policy + - gitops-cluster +rzgr_private_repo_secret: "private-repo" +rzgr_argo_cd_known_host_cm: "argocd-ssh-known-hosts-cm" diff --git a/roles/remove_ztp_gitops_resources/tasks/main.yaml b/roles/remove_ztp_gitops_resources/tasks/main.yaml new file mode 100644 index 000000000..32ff8a712 --- /dev/null +++ b/roles/remove_ztp_gitops_resources/tasks/main.yaml @@ -0,0 +1,88 @@ +--- + +# This will prevent ArgoCD from recreating the spoke cluster +# resources if we decide to detach the spoke cluster from a +# given hub. +- name: Delete SiteConfig and Policy Applications + community.kubernetes.k8s: + api_version: argoproj.io/v1alpha1 + kind: Application + name: "{{ _rzgr_gitops_application }}" + namespace: openshift-gitops + state: absent + wait: true + wait_sleep: 5 + wait_timeout: 120 + loop: "{{ rzgr_gitops_applications }}" + loop_control: + loop_var: _rzgr_gitops_application + +- name: Delete SiteConfig and Policy AppProjects + community.kubernetes.k8s: + api_version: argoproj.io/v1alpha1 + kind: AppProject + name: "{{ _rzgr_gitops_appproject }}" + namespace: openshift-gitops + state: absent + wait: true + wait_sleep: 5 + wait_timeout: 120 + loop: "{{ rzgr_gitops_appprojects }}" + loop_control: + loop_var: _rzgr_gitops_appproject + +# SiteConfig namespace is not deleted here, else this +# will imply the spoke cluster is detached from the hub +# cluster. +# For detaching the spoke cluster, then run the +# acm_detach_spoke_cluster role. + +- name: Delete policies namespace + community.kubernetes.k8s: + api_version: v1 + kind: Namespace + name: "{{ rzgr_policies_namespace }}" + state: absent + wait: true + +- name: Delete extra namespaces created for GitOps Policy + community.kubernetes.k8s: + api_version: v1 + kind: Namespace + name: "{{ _rzgr_extra_namespace }}" + state: absent + wait: true + loop: "{{ rzgr_extra_namespaces }}" + loop_control: + loop_var: _rzgr_extra_namespace + +- name: Delete ClusterRoleBindings created for GitOps Policy and SiteConfig + community.kubernetes.k8s: + api_version: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + name: "{{ _rzgr_cluster_role_binding }}" + state: absent + wait: true + loop: "{{ rzgr_cluster_role_bindings }}" + loop_control: + loop_var: _rzgr_cluster_role_binding + +- name: Delete Secret from openshift-gitops repo + community.kubernetes.k8s: + api_version: v1 + kind: Secret + name: "{{ rzgr_private_repo_secret }}" + namespace: openshift-gitops + state: absent + wait: true + +- name: Delete ConfigMap from openshift-gitops repo + community.kubernetes.k8s: + api_version: v1 + kind: ConfigMap + name: "{{ rzgr_argo_cd_known_host_cm }}" + namespace: openshift-gitops + state: absent + wait: true + +...