Skip to content

Commit

Permalink
Merge pull request #366 from redhatci/acm-detach-reattach
Browse files Browse the repository at this point in the history
New role for spoke cluster management operations (detach, attach and clean GitOps)
  • Loading branch information
ramperher authored Jul 10, 2024
2 parents 1a90989 + 3f27770 commit ba31e20
Show file tree
Hide file tree
Showing 15 changed files with 440 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
76 changes: 76 additions & 0 deletions roles/acm_spoke_mgmt/README.md
Original file line number Diff line number Diff line change
@@ -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"
```
1 change: 1 addition & 0 deletions roles/acm_spoke_mgmt/defaults/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
---
104 changes: 104 additions & 0 deletions roles/acm_spoke_mgmt/tasks/attach.yaml
Original file line number Diff line number Diff line change
@@ -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

...
56 changes: 56 additions & 0 deletions roles/acm_spoke_mgmt/tasks/detach.yaml
Original file line number Diff line number Diff line change
@@ -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

...
23 changes: 23 additions & 0 deletions roles/acm_spoke_mgmt/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -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'

...
16 changes: 16 additions & 0 deletions roles/acm_spoke_mgmt/templates/autoimport_secret.yml.j2
Original file line number Diff line number Diff line change
@@ -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

...
4 changes: 3 additions & 1 deletion roles/gitops_configure_repo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------
Expand Down
2 changes: 2 additions & 0 deletions roles/gitops_configure_repo/defaults/main.yaml
Original file line number Diff line number Diff line change
@@ -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"
2 changes: 1 addition & 1 deletion roles/gitops_configure_repo/tasks/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
47 changes: 47 additions & 0 deletions roles/remove_ztp_gitops_resources/README.md
Original file line number Diff line number Diff line change
@@ -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"
```
Loading

0 comments on commit ba31e20

Please sign in to comment.