From 1eb3ba883c13153757672a476fde7e3a283c93f7 Mon Sep 17 00:00:00 2001 From: Wolfgang Kulhanek Date: Sat, 23 May 2026 08:29:39 +0200 Subject: [PATCH] Add ROSA HCP as deployment method for openshift-cluster Add ROSA (Red Hat OpenShift Service on AWS) Hosted Control Planes as a new installation method (host_ocp4_deploy_installation_method: rosa) for the openshift-cluster config. New roles: - host_ocp4_rosa_install: Installs ROSA CLI, Terraform, creates VPC networking, deploys ROSA HCP cluster, sets up admin access and kubeconfig, queries standard OpenShift cluster facts - host_ocp4_rosa_destroy: Deletes ROSA cluster and Terraform VPC resources on bastion Modified files: - pre_infra.yml: Skip pull_secret check, add rosa_token assertion - post_software.yml: Skip credential cleanup, add API readiness check - destroy_env.yml: Add ROSA destroy play on bastion - lifecycle_hook_post_start.yml: Skip CSR approval for ROSA - default_vars.yml: Add ROSA configuration variables Co-Authored-By: Claude Opus 4.6 (1M context) --- .../openshift-cluster/aws/destroy_env.yml | 18 ++- .../openshift-cluster/default_vars.yml | 46 +++++++ .../lifecycle_hook_post_start.yml | 4 +- .../openshift-cluster/post_software.yml | 18 ++- .../configs/openshift-cluster/pre_infra.yml | 15 +- .../openshift-cluster-rosa-aws.yml | 81 +++++++++++ ansible/roles/host_ocp4_deploy/tasks/rosa.yml | 8 ++ .../host_ocp4_rosa_destroy/defaults/main.yml | 11 ++ .../tasks/destroy_cluster.yml | 36 +++++ .../tasks/destroy_terraform.yml | 42 ++++++ .../host_ocp4_rosa_destroy/tasks/main.yml | 21 +++ .../host_ocp4_rosa_install/defaults/main.yml | 55 ++++++++ .../tasks/install_aws_cli.yml | 62 +++++++++ .../tasks/install_ocp_cli.yml | 29 ++++ .../tasks/install_rosa_cli.yml | 28 ++++ .../tasks/install_terraform.yml | 13 ++ .../host_ocp4_rosa_install/tasks/main.yml | 128 ++++++++++++++++++ .../tasks/rosa_cluster_info.yml | 72 ++++++++++ .../tasks/rosa_cluster_login.yml | 47 +++++++ .../tasks/rosa_create_cluster.yml | 108 +++++++++++++++ .../tasks/rosa_create_cluster_wait.yml | 25 ++++ .../tasks/rosa_post_install.yml | 23 ++++ .../tasks/rosa_post_install_admin.yml | 29 ++++ .../tasks/rosa_pre_install.yml | 114 ++++++++++++++++ .../tasks/rosa_terraform.yml | 51 +++++++ 25 files changed, 1078 insertions(+), 6 deletions(-) create mode 100644 ansible/configs/openshift-cluster/sample_vars/openshift-cluster-rosa-aws.yml create mode 100644 ansible/roles/host_ocp4_deploy/tasks/rosa.yml create mode 100644 ansible/roles/host_ocp4_rosa_destroy/defaults/main.yml create mode 100644 ansible/roles/host_ocp4_rosa_destroy/tasks/destroy_cluster.yml create mode 100644 ansible/roles/host_ocp4_rosa_destroy/tasks/destroy_terraform.yml create mode 100644 ansible/roles/host_ocp4_rosa_destroy/tasks/main.yml create mode 100644 ansible/roles/host_ocp4_rosa_install/defaults/main.yml create mode 100644 ansible/roles/host_ocp4_rosa_install/tasks/install_aws_cli.yml create mode 100644 ansible/roles/host_ocp4_rosa_install/tasks/install_ocp_cli.yml create mode 100644 ansible/roles/host_ocp4_rosa_install/tasks/install_rosa_cli.yml create mode 100644 ansible/roles/host_ocp4_rosa_install/tasks/install_terraform.yml create mode 100644 ansible/roles/host_ocp4_rosa_install/tasks/main.yml create mode 100644 ansible/roles/host_ocp4_rosa_install/tasks/rosa_cluster_info.yml create mode 100644 ansible/roles/host_ocp4_rosa_install/tasks/rosa_cluster_login.yml create mode 100644 ansible/roles/host_ocp4_rosa_install/tasks/rosa_create_cluster.yml create mode 100644 ansible/roles/host_ocp4_rosa_install/tasks/rosa_create_cluster_wait.yml create mode 100644 ansible/roles/host_ocp4_rosa_install/tasks/rosa_post_install.yml create mode 100644 ansible/roles/host_ocp4_rosa_install/tasks/rosa_post_install_admin.yml create mode 100644 ansible/roles/host_ocp4_rosa_install/tasks/rosa_pre_install.yml create mode 100644 ansible/roles/host_ocp4_rosa_install/tasks/rosa_terraform.yml diff --git a/ansible/configs/openshift-cluster/aws/destroy_env.yml b/ansible/configs/openshift-cluster/aws/destroy_env.yml index 305f8888..e49cb686 100644 --- a/ansible/configs/openshift-cluster/aws/destroy_env.yml +++ b/ansible/configs/openshift-cluster/aws/destroy_env.yml @@ -128,9 +128,25 @@ aws_secret_access_key = {{ aws_secret_access_key }} - name: Call role to destroy the OpenShift cluster - when: bwait is successful + when: + - bwait is successful + - host_ocp4_deploy_installation_method != 'rosa' ansible.builtin.include_role: name: host_ocp4_destroy +- name: Destroy ROSA cluster + hosts: bastions + gather_facts: false + become: false + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key_id }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_access_key }}" + AWS_DEFAULT_REGION: "{{ aws_region_final | default(aws_region) }}" + tasks: + - name: Destroy ROSA cluster + when: host_ocp4_deploy_installation_method == 'rosa' + ansible.builtin.include_role: + name: host_ocp4_rosa_destroy + - name: Import default aws destroy playbook ansible.builtin.import_playbook: "../../../cloud_providers/aws/destroy_env.yml" diff --git a/ansible/configs/openshift-cluster/default_vars.yml b/ansible/configs/openshift-cluster/default_vars.yml index ec45fd86..16c3ef0b 100644 --- a/ansible/configs/openshift-cluster/default_vars.yml +++ b/ansible/configs/openshift-cluster/default_vars.yml @@ -119,6 +119,7 @@ cluster_dns_zone: "{{ guid }}.{{ base_domain }}" # - openshift_install # - openshift_assisted # - openshift_hcp_cnv +# - rosa host_ocp4_deploy_installation_method: openshift_install # Types of Nodes (workers / control_planes) are set in the @@ -170,3 +171,48 @@ remove_workloads: [] # Remove mint-mode cloud credentials at end of provision # https://docs.redhat.com/en/documentation/openshift_container_platform/4.21/html/authentication_and_authorization/managing-cloud-provider-credentials remove_mint_mode_cloud_provider_credentials: true + +# ------------------------------------------------- +# ROSA (Red Hat OpenShift Service on AWS) +# ------------------------------------------------- + +# Cluster name for ROSA +rosa_cluster_name: "rosa-{{ guid }}" + +# ROSA token - must come from secrets +# rosa_token: "" + +# Version of ROSA to deploy +# Options: default, latest, latest-upgrade, or specific (e.g. 4.20.3) +rosa_version: default + +# Base version for latest/latest-upgrade resolution +rosa_version_base: "openshift-v4.20" + +# ROSA CLI version and download URL +rosa_installer_version: latest +# rosa_installer_version: "1.2.53" +rosa_installer_url: >- + https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/rosa/{{ rosa_installer_version }}/rosa-linux.tar.gz + +# Set up cluster-admin user on the cluster +rosa_setup_cluster_admin: true + +# Log cluster-admin into OpenShift on bastion (creates .kube/config) +rosa_setup_cluster_admin_login: false + +# Terraform for ROSA networking +rosa_terraform_version: "1.15.3" +rosa_terraform_url: >- + https://releases.hashicorp.com/terraform/{{ rosa_terraform_version }}/terraform_{{ rosa_terraform_version }}_linux_amd64.zip +rosa_terraform_repo: https://github.com/openshift-cs/terraform-vpc-example +rosa_terraform_repo_branch: main + +# AWS billing account ID (required for ROSA, comes from sandbox) +# aws_billing_account_id: "" + +# Networking CIDRs (empty = use ROSA defaults) +rosa_machine_cidr: "" +rosa_service_cidr: "" +rosa_pod_cidr: "" +rosa_host_prefix: "" diff --git a/ansible/configs/openshift-cluster/lifecycle_hook_post_start.yml b/ansible/configs/openshift-cluster/lifecycle_hook_post_start.yml index ffcbc8b8..31853468 100644 --- a/ansible/configs/openshift-cluster/lifecycle_hook_post_start.yml +++ b/ansible/configs/openshift-cluster/lifecycle_hook_post_start.yml @@ -67,6 +67,8 @@ gather_facts: false tasks: - name: Approve CertificateSigningRequests - when: hostvars[groups['bastions'][0]]['r_wait'] is successful + when: + - hostvars[groups['bastions'][0]]['r_wait'] is successful + - host_ocp4_deploy_installation_method != 'rosa' ansible.builtin.include_role: name: ocp4_approve_certificate_signing_requests diff --git a/ansible/configs/openshift-cluster/post_software.yml b/ansible/configs/openshift-cluster/post_software.yml index b970c7b4..c4a65451 100644 --- a/ansible/configs/openshift-cluster/post_software.yml +++ b/ansible/configs/openshift-cluster/post_software.yml @@ -11,7 +11,9 @@ msg: "Post-Software Steps starting" - name: Remove AWS Credentials from bastion - when: cloud_provider == 'aws' + when: + - cloud_provider == 'aws' + - host_ocp4_deploy_installation_method != 'rosa' ansible.builtin.file: path: "/home/{{ ansible_user }}/.aws/credentials" state: absent @@ -48,6 +50,16 @@ environment: KUBECONFIG: "{{ hostvars.localhost.output_dir }}/{{ config }}_{{ guid }}_kubeconfig" tasks: + - name: Wait for OpenShift API to be reachable + kubernetes.core.k8s_info: + api_version: config.openshift.io/v1 + kind: Infrastructure + name: cluster + register: r_api_check + retries: 30 + delay: 10 + until: r_api_check is success + - name: Setup cluster-admin service account when: openshift_cluster_admin_service_account_enable | bool ansible.builtin.include_role: @@ -75,7 +87,9 @@ KUBECONFIG: "{{ hostvars.localhost.output_dir }}/{{ config }}_{{ guid }}_kubeconfig" tasks: - name: Remove mint-mode cloud provider credentials - when: remove_mint_mode_cloud_provider_credentials | bool + when: + - remove_mint_mode_cloud_provider_credentials | bool + - host_ocp4_deploy_installation_method != 'rosa' kubernetes.core.k8s: state: absent api_version: v1 diff --git a/ansible/configs/openshift-cluster/pre_infra.yml b/ansible/configs/openshift-cluster/pre_infra.yml index 5f976aab..1ccda6ac 100644 --- a/ansible/configs/openshift-cluster/pre_infra.yml +++ b/ansible/configs/openshift-cluster/pre_infra.yml @@ -9,18 +9,29 @@ ansible.builtin.debug: msg: "Step 000 Pre Infrastructure" - - name: Ensure variables are set + - name: Ensure pull secret is set + when: host_ocp4_deploy_installation_method != 'rosa' ansible.builtin.assert: that: ocp4_pull_secret | default("") | length > 0 fail_msg: ocp4_pull_secret variable must be defined success_msg: ocp4_pull_secret variable is defined quiet: true + - name: Ensure ROSA token is set + when: host_ocp4_deploy_installation_method == 'rosa' + ansible.builtin.assert: + that: rosa_token | default("") | length > 0 + fail_msg: rosa_token variable must be defined for ROSA deployments + success_msg: rosa_token variable is defined + quiet: true + - name: AWS Pre Infrastructure tasks when: cloud_provider == "aws" block: - name: Set availability zones for Cloudformation and install-config.yml - when: agnosticd_aws_capacity_reservation_results.reservations | default({}) | length > 0 + when: + - agnosticd_aws_capacity_reservation_results.reservations | default({}) | length > 0 + - host_ocp4_deploy_installation_method != 'rosa' block: - name: Set availability zone for bastion, control_plane, and worker nodes vars: diff --git a/ansible/configs/openshift-cluster/sample_vars/openshift-cluster-rosa-aws.yml b/ansible/configs/openshift-cluster/sample_vars/openshift-cluster-rosa-aws.yml new file mode 100644 index 00000000..3678ea58 --- /dev/null +++ b/ansible/configs/openshift-cluster/sample_vars/openshift-cluster-rosa-aws.yml @@ -0,0 +1,81 @@ +--- +# =================================================================== +# Mandatory Variables +# =================================================================== +cloud_provider: aws +config: openshift-cluster +host_ocp4_deploy_installation_method: rosa +# =================================================================== +# End Mandatory Variables +# =================================================================== + +# =================================================================== +# AWS Specific settings +# =================================================================== +aws_region: us-east-2 + +cloud_tags: +- owner: wkulhane@redhat.com +- Purpose: development +- config: "{{ config }}" +- guid: "{{ guid }}" + +# ------------------------------------------------------------------- +# Set authorized keys on all created instances +# ------------------------------------------------------------------- +host_ssh_authorized_keys: +- key: https://github.com/wkulhanek.keys + +# =================================================================== +# ROSA settings +# =================================================================== + +# ROSA token from cloud.redhat.com/openshift/token/rosa +# Must be provided via secrets +# rosa_token: "" + +# AWS billing account ID - required for ROSA +# Must be provided via secrets or sandbox +# aws_billing_account_id: "" + +# ROSA version: default, latest, latest-upgrade, or specific +rosa_version: default +rosa_version_base: "openshift-v4.20" + +# Set up cluster-admin and log in on bastion +rosa_setup_cluster_admin: true +rosa_setup_cluster_admin_login: true + +# ------------------------------------------------------------------- +# Bastion VM settings +# ------------------------------------------------------------------- +bastion_setup_student_user: true +bastion_student_user_name: rosa + +# ROSA only needs a bastion - no control plane or worker instances +agnosticd_aws_capacity_reservation_enable: false +control_plane_instance_count: 0 +worker_instance_count: 0 + +# Simplified instances list - bastion only +instances: +- name: bastion + count: 1 + unique: true + public_dns: true + image: "{{ bastion_instance_image }}" + flavor: + ec2: t3a.small + tags: + - key: AnsibleGroup + value: bastions + - key: Purpose + value: "{{ purpose }}" + rootfs_size: 30 + security_groups: + - BastionSG + +# ------------------------------------------------------------------- +# Workloads +# ------------------------------------------------------------------- +workloads: [] diff --git a/ansible/roles/host_ocp4_deploy/tasks/rosa.yml b/ansible/roles/host_ocp4_deploy/tasks/rosa.yml new file mode 100644 index 00000000..0c2198e9 --- /dev/null +++ b/ansible/roles/host_ocp4_deploy/tasks/rosa.yml @@ -0,0 +1,8 @@ +--- +- name: Install ROSA cluster on bastion + delegate_to: "{{ groups['bastions'][0] }}" + block: + - name: Call role to install ROSA cluster + when: host_ocp4_deploy_install_ocp4 | default(true) | bool + ansible.builtin.include_role: + name: host_ocp4_rosa_install diff --git a/ansible/roles/host_ocp4_rosa_destroy/defaults/main.yml b/ansible/roles/host_ocp4_rosa_destroy/defaults/main.yml new file mode 100644 index 00000000..dcef8f76 --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_destroy/defaults/main.yml @@ -0,0 +1,11 @@ +--- +rosa_cluster_name: "rosa-{{ guid }}" + +rosa_installer_version: "1.2.53" +rosa_installer_url: >- + https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/rosa/{{ rosa_installer_version }}/rosa-linux.tar.gz +rosa_binary_path: /usr/local/bin + +rosa_terraform_version: "1.11.2" +rosa_terraform_url: >- + https://releases.hashicorp.com/terraform/{{ rosa_terraform_version }}/terraform_{{ rosa_terraform_version }}_linux_amd64.zip diff --git a/ansible/roles/host_ocp4_rosa_destroy/tasks/destroy_cluster.yml b/ansible/roles/host_ocp4_rosa_destroy/tasks/destroy_cluster.yml new file mode 100644 index 00000000..7839c19e --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_destroy/tasks/destroy_cluster.yml @@ -0,0 +1,36 @@ +--- +- name: Delete ROSA cluster {{ item.name }} + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa delete cluster + --cluster {{ item.name }} + --yes + register: r_rosa_delete + failed_when: >- + r_rosa_delete.rc != 0 + and 'ERR: There is no cluster with identifier or name' not in r_rosa_delete.stderr + +- name: Wait for ROSA cluster {{ item.name }} deletion + ansible.builtin.shell: | + for i in $(seq 1 30); do + {{ rosa_binary_path }}/rosa describe cluster --cluster {{ item.name }} 2>&1 + if [ $? -ne 0 ]; then + echo "Cluster {{ item.name }} has been deleted" + exit 0 + fi + echo "Attempt $i: cluster still exists, waiting..." + sleep 60 + done + echo "Timed out waiting for cluster deletion" + exit 1 + register: r_rosa_cluster_status + changed_when: false + async: 2400 + poll: 30 + +- name: Force ROSA cluster {{ item.name }} removal + when: r_rosa_cluster_status.rc != 0 + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa delete cluster + --cluster {{ item.name }} + --yes + --best-effort=true diff --git a/ansible/roles/host_ocp4_rosa_destroy/tasks/destroy_terraform.yml b/ansible/roles/host_ocp4_rosa_destroy/tasks/destroy_terraform.yml new file mode 100644 index 00000000..6d60c8d2 --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_destroy/tasks/destroy_terraform.yml @@ -0,0 +1,42 @@ +--- +- name: Check if terraform archive exists in output dir + ansible.builtin.stat: + path: "{{ output_dir }}/terraform-vpc.tar.gz" + register: r_terraform_archive + +- name: Destroy Terraform VPC resources + when: r_terraform_archive.stat.exists + block: + - name: Install Terraform + ansible.builtin.unarchive: + src: "{{ rosa_terraform_url }}" + remote_src: true + dest: /tmp + mode: u=rwx,go=rx + + - name: Restore terraform-vpc directory from output dir + ansible.builtin.unarchive: + src: "{{ output_dir }}/terraform-vpc.tar.gz" + dest: /tmp/ + extra_opts: + - --no-same-owner + + - name: Run Terraform destroy + ansible.builtin.command: + cmd: >- + /tmp/terraform destroy + -var region={{ aws_region }} + -var cluster_name={{ rosa_cluster_name }} + -auto-approve + chdir: /tmp/terraform-vpc/ + ignore_errors: true + register: r_terraform_destroy + + - name: Print Terraform destroy output + ansible.builtin.debug: + var: r_terraform_destroy + +- name: Skip Terraform destroy (no archive found) + when: not r_terraform_archive.stat.exists + ansible.builtin.debug: + msg: "No terraform-vpc.tar.gz found in output_dir, skipping Terraform destroy" diff --git a/ansible/roles/host_ocp4_rosa_destroy/tasks/main.yml b/ansible/roles/host_ocp4_rosa_destroy/tasks/main.yml new file mode 100644 index 00000000..8c195eea --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_destroy/tasks/main.yml @@ -0,0 +1,21 @@ +--- +- name: Login to ROSA + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa login --token {{ rosa_token }} + +- name: Get list of ROSA clusters + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa list clusters --output json + register: r_rosa_list + changed_when: false + +- name: Print number of clusters to destroy + ansible.builtin.debug: + msg: "Found {{ r_rosa_list.stdout | from_json | length }} cluster(s) to destroy" + +- name: Destroy each ROSA cluster + ansible.builtin.include_tasks: destroy_cluster.yml + loop: "{{ r_rosa_list.stdout | from_json }}" + +- name: Destroy Terraform VPC resources + ansible.builtin.include_tasks: destroy_terraform.yml diff --git a/ansible/roles/host_ocp4_rosa_install/defaults/main.yml b/ansible/roles/host_ocp4_rosa_install/defaults/main.yml new file mode 100644 index 00000000..f7f83539 --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_install/defaults/main.yml @@ -0,0 +1,55 @@ +--- +# ROSA cluster name +rosa_cluster_name: "rosa-{{ guid }}" + +# ROSA token - must come from secrets +# rosa_token: "" + +# Version of ROSA to deploy +# Options: default, latest, latest-upgrade, or specific (e.g. 4.17.4) +rosa_version: default + +# Base version string for latest/latest-upgrade resolution +rosa_version_base: "openshift-v4.20" + +# ROSA CLI version and download URL +rosa_installer_version: latest +# rosa_installer_version: "1.2.53" +rosa_installer_url: >- + https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/rosa/{{ rosa_installer_version }}/rosa-linux.tar.gz +rosa_binary_path: /usr/local/bin + +# OpenShift client download root URL +ocp4_tools_root_url: >- + https://mirror.openshift.com/pub/openshift-v4/clients + +# Compute settings (undefined = use ROSA defaults) +# rosa_compute_machine_type: m5.xlarge +# rosa_compute_worker_disk_size: 200GiB +# rosa_compute_replicas: 2 + +# Networking CIDRs (empty = use ROSA defaults) +rosa_machine_cidr: "" +rosa_service_cidr: "" +rosa_pod_cidr: "" +rosa_host_prefix: "" + +# Set up cluster-admin user +rosa_setup_cluster_admin: true +rosa_setup_cluster_admin_login: false + +# Terraform for ROSA networking +rosa_terraform_version: "1.15.3" +rosa_terraform_url: >- + https://releases.hashicorp.com/terraform/{{ rosa_terraform_version }}/terraform_{{ rosa_terraform_version }}_linux_amd64.zip +rosa_terraform_repo: https://github.com/openshift-cs/terraform-vpc-example +rosa_terraform_repo_branch: main + +# AWS billing account ID (required for ROSA) +# aws_billing_account_id: "" + +# Internal state variables +_rosa_cluster_admin_password: "" +_rosa_supported_regions: "" +_rosa_version_to_install: "" +_rosa_ocp_cli_version: "" diff --git a/ansible/roles/host_ocp4_rosa_install/tasks/install_aws_cli.yml b/ansible/roles/host_ocp4_rosa_install/tasks/install_aws_cli.yml new file mode 100644 index 00000000..681c0fa0 --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_install/tasks/install_aws_cli.yml @@ -0,0 +1,62 @@ +--- +- name: Download AWS CLI v2 + ansible.builtin.get_url: + url: https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip + dest: /tmp/awscliv2.zip + +- name: Unzip AWS CLI + ansible.builtin.unarchive: + src: /tmp/awscliv2.zip + dest: /tmp/ + remote_src: true + +- name: Install AWS CLI + become: true + ansible.builtin.command: /tmp/aws/install -i /usr/local/aws -b /usr/local/bin + args: + creates: /usr/local/aws + +- name: Cleanup AWS CLI archive and tmp files + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: + - /tmp/aws + - /tmp/awscliv2.zip + +- name: Create .aws directory + ansible.builtin.file: + path: "~{{ ansible_user }}/.aws" + state: directory + mode: u=rwx,go= + +- name: Add AWS credentials for sandbox + when: + - hostvars.localhost.student_access_key_id | default("") | length > 0 + - hostvars.localhost.student_secret_access_key | default("") | length > 0 + ansible.builtin.blockinfile: + path: "~{{ ansible_user }}/.aws/credentials" + create: true + mode: u=rw + marker: "# {mark} ANSIBLE MANAGED BLOCK Student Credentials" + block: |- + [default] + aws_access_key_id={{ hostvars.localhost.student_access_key_id }} + aws_secret_access_key={{ hostvars.localhost.student_secret_access_key }} + region={{ aws_region }} + +- name: Add AWS credentials for shared account + when: + - hostvars.localhost.student_access_key_id | default("") | length == 0 + - aws_access_key_id | default("") | length > 0 + - aws_secret_access_key | default("") | length > 0 + ansible.builtin.blockinfile: + path: "~{{ ansible_user }}/.aws/credentials" + create: true + mode: u=rw + marker: "# {mark} ANSIBLE MANAGED BLOCK Default Credentials" + block: |- + [default] + aws_access_key_id={{ aws_access_key_id }} + aws_secret_access_key={{ aws_secret_access_key }} + region={{ aws_region }} diff --git a/ansible/roles/host_ocp4_rosa_install/tasks/install_ocp_cli.yml b/ansible/roles/host_ocp4_rosa_install/tasks/install_ocp_cli.yml new file mode 100644 index 00000000..3b59091c --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_install/tasks/install_ocp_cli.yml @@ -0,0 +1,29 @@ +--- +- name: Download OpenShift client + ansible.builtin.get_url: + url: >- + {{ ocp4_tools_root_url }}/ocp/stable-{{ _rosa_ocp_cli_version + | regex_replace('^(\d+\.\d+).*$', '\1') }}/openshift-client-linux.tar.gz + dest: /tmp/openshift-client-linux.tar.gz + +- name: Install OpenShift client + become: true + ansible.builtin.unarchive: + src: /tmp/openshift-client-linux.tar.gz + dest: /usr/local/bin + remote_src: true + owner: root + mode: u=rwx,go=rx + extra_opts: + - --no-same-owner + +- name: Cleanup OpenShift client archive + ansible.builtin.file: + path: /tmp/openshift-client-linux.tar.gz + state: absent + +- name: Create oc bash completion + become: true + ansible.builtin.shell: + cmd: /usr/local/bin/oc completion bash >/etc/bash_completion.d/oc + changed_when: true diff --git a/ansible/roles/host_ocp4_rosa_install/tasks/install_rosa_cli.yml b/ansible/roles/host_ocp4_rosa_install/tasks/install_rosa_cli.yml new file mode 100644 index 00000000..42a76a4a --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_install/tasks/install_rosa_cli.yml @@ -0,0 +1,28 @@ +--- +- name: Download ROSA CLI + ansible.builtin.get_url: + url: "{{ rosa_installer_url }}" + dest: /tmp/rosa-linux.tar.gz + +- name: Install ROSA CLI + become: true + ansible.builtin.unarchive: + src: /tmp/rosa-linux.tar.gz + dest: "{{ rosa_binary_path }}" + remote_src: true + owner: root + mode: u=rwx,go=rx + extra_opts: + - --no-same-owner + +- name: Cleanup ROSA CLI archive + ansible.builtin.file: + path: /tmp/rosa-linux.tar.gz + state: absent + +- name: Create ROSA bash completion + become: true + ansible.builtin.shell: + cmd: "{{ rosa_binary_path }}/rosa completion bash >/etc/bash_completion.d/rosa" + changed_when: true + failed_when: false diff --git a/ansible/roles/host_ocp4_rosa_install/tasks/install_terraform.yml b/ansible/roles/host_ocp4_rosa_install/tasks/install_terraform.yml new file mode 100644 index 00000000..7da8a89c --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_install/tasks/install_terraform.yml @@ -0,0 +1,13 @@ +--- +- name: Install Terraform + become: true + ansible.builtin.unarchive: + src: "{{ rosa_terraform_url }}" + remote_src: true + dest: /usr/local/bin + mode: ug=rwx,o=rx + owner: root + retries: 10 + register: r_terraform + until: r_terraform is success + delay: 30 diff --git a/ansible/roles/host_ocp4_rosa_install/tasks/main.yml b/ansible/roles/host_ocp4_rosa_install/tasks/main.yml new file mode 100644 index 00000000..dd4ec543 --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_install/tasks/main.yml @@ -0,0 +1,128 @@ +--- +- name: Install AWS CLI + ansible.builtin.include_tasks: install_aws_cli.yml + +- name: Install Terraform + ansible.builtin.include_tasks: install_terraform.yml + +- name: Install ROSA CLI + ansible.builtin.include_tasks: install_rosa_cli.yml + +- name: Login to ROSA + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa login --token {{ rosa_token }} + +- name: List ROSA clusters in organization + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa list clusters --output json + register: r_rosa_list_clusters + changed_when: false + +- name: Check if ROSA cluster already exists + vars: + _matching: >- + {{ r_rosa_list_clusters.stdout | from_json + | selectattr('name', 'equalto', rosa_cluster_name) | list }} + _cluster_state: >- + {{ _matching[0].status.state | default('') if _matching | length > 0 else '' }} + ansible.builtin.set_fact: + _rosa_cluster_exists: "{{ _matching | length > 0 }}" + _rosa_cluster_ready: "{{ _cluster_state == 'ready' }}" + _rosa_cluster_state: "{{ _cluster_state }}" + +- name: Print cluster status + ansible.builtin.debug: + msg: "ROSA cluster {{ rosa_cluster_name }}: exists={{ _rosa_cluster_exists }}, state={{ _rosa_cluster_state | default('not found') }}" + +- name: Create ROSA cluster + when: not _rosa_cluster_exists | bool + block: + - name: Run ROSA pre-install tasks + ansible.builtin.include_tasks: rosa_pre_install.yml + + - name: Create ROSA networking with Terraform + ansible.builtin.include_tasks: rosa_terraform.yml + + - name: Create ROSA cluster + ansible.builtin.include_tasks: rosa_create_cluster.yml + + - name: Run ROSA post-install tasks + ansible.builtin.include_tasks: rosa_post_install.yml + +- name: Resume existing ROSA cluster install + when: + - _rosa_cluster_exists | bool + - not _rosa_cluster_ready | bool + block: + - name: Run ROSA pre-install tasks for version resolution + ansible.builtin.include_tasks: rosa_pre_install.yml + + - name: Wait for ROSA cluster to be ready + ansible.builtin.include_tasks: rosa_create_cluster_wait.yml + + - name: Run ROSA post-install tasks + ansible.builtin.include_tasks: rosa_post_install.yml + +- name: Reconnect to existing ready cluster + when: + - _rosa_cluster_exists | bool + - _rosa_cluster_ready | bool + block: + - name: Run ROSA pre-install tasks for version resolution + ansible.builtin.include_tasks: rosa_pre_install.yml + + - name: Run ROSA post-install tasks for URL extraction + ansible.builtin.include_tasks: rosa_post_install.yml + +- name: Install OpenShift CLI + ansible.builtin.include_tasks: install_ocp_cli.yml + +- name: Create ROSA admin user for cluster access + ansible.builtin.include_tasks: rosa_post_install_admin.yml + +- name: Log cluster admin into OpenShift and fetch kubeconfig + ansible.builtin.include_tasks: rosa_cluster_login.yml + +- name: Get OpenShift cluster information + ansible.builtin.include_tasks: rosa_cluster_info.yml + +- name: Save ROSA install log + block: + - name: Get ROSA install log + ansible.builtin.shell: + cmd: >- + {{ rosa_binary_path }}/rosa logs install + --cluster {{ rosa_cluster_name }} > /home/{{ ansible_user }}/rosa_install.log + changed_when: false + + - name: Archive ROSA install log + ansible.builtin.archive: + path: /home/{{ ansible_user }}/rosa_install.log + dest: /home/{{ ansible_user }}/rosa_install.log.gz + format: gz + + - name: Transfer install log to output dir + ansible.builtin.fetch: + src: /home/{{ ansible_user }}/rosa_install.log.gz + dest: "{{ output_dir }}/{{ config }}_{{ guid }}_log/" + flat: true + ignore_errors: true + +- name: Save ROSA user data + delegate_to: localhost + agnosticd.core.agnosticd_user_info: + data: + rosa_openshift_console_url: "{{ rosa_openshift_console_url }}" + rosa_openshift_api_url: "{{ rosa_openshift_api_url }}" + rosa_version: "{{ _rosa_version_to_install }}" + rosa_cluster_name: "{{ rosa_cluster_name }}" + +- name: Save ROSA admin credentials + when: + - rosa_setup_cluster_admin | bool + - _rosa_cluster_admin_password | length > 0 + delegate_to: localhost + agnosticd.core.agnosticd_user_info: + data: + rosa_openshift_admin_user: cluster-admin + rosa_openshift_admin_password: "{{ _rosa_cluster_admin_password }}" diff --git a/ansible/roles/host_ocp4_rosa_install/tasks/rosa_cluster_info.yml b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_cluster_info.yml new file mode 100644 index 00000000..65faacb3 --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_cluster_info.yml @@ -0,0 +1,72 @@ +--- +- name: Determine Web console URL + delegate_to: localhost + kubernetes.core.k8s_info: + api_version: config.openshift.io/v1 + kind: Console + name: cluster + kubeconfig: "{{ output_dir }}/{{ config }}_{{ guid }}_kubeconfig" + register: r_console + retries: 30 + delay: 5 + until: + - r_console.resources | length > 0 + - r_console.resources[0].status.consoleURL | default('') | length > 0 + +- name: Determine API server URL + delegate_to: localhost + kubernetes.core.k8s_info: + api_version: config.openshift.io/v1 + kind: Infrastructure + name: cluster + kubeconfig: "{{ output_dir }}/{{ config }}_{{ guid }}_kubeconfig" + register: r_api + retries: 30 + delay: 5 + until: + - r_api.resources | length > 0 + - r_api.resources[0].status.apiServerURL | default('') | length > 0 + +- name: Determine OpenShift Ingress Domain + delegate_to: localhost + kubernetes.core.k8s_info: + api_version: config.openshift.io/v1 + kind: Ingress + name: cluster + kubeconfig: "{{ output_dir }}/{{ config }}_{{ guid }}_kubeconfig" + register: r_ingress + retries: 30 + delay: 5 + until: + - r_ingress.resources | length > 0 + - r_ingress.resources[0].spec.domain | default('') | length > 0 + +- name: Set standard OpenShift cluster facts + ansible.builtin.set_fact: + openshift_api_url: "{{ r_api.resources[0].status.apiServerURL | trim }}" + openshift_console_url: "{{ r_console.resources[0].status.consoleURL | trim }}" + openshift_cluster_ingress_domain: "{{ r_ingress.resources[0].spec.domain | trim }}" + +- name: Print OpenShift cluster information + ansible.builtin.debug: + msg: "{{ item }}" + loop: + - "OpenShift API: {{ openshift_api_url }}" + - "OpenShift Console: {{ openshift_console_url }}" + - "OpenShift Ingress Domain: {{ openshift_cluster_ingress_domain }}" + +- name: Set user data for OpenShift access + delegate_to: localhost + agnosticd.core.agnosticd_user_info: + data: + openshift_api_url: "{{ openshift_api_url }}" + openshift_console_url: "{{ openshift_console_url }}" + openshift_cluster_ingress_domain: "{{ openshift_cluster_ingress_domain }}" + +- name: Show user messages for OpenShift access + when: openshift_cluster_show_access_user_info | default(true) | bool + delegate_to: localhost + agnosticd.core.agnosticd_user_info: + msg: |- + OpenShift Console: {{ openshift_console_url }} + OpenShift API for command line 'oc' client: {{ openshift_api_url }} diff --git a/ansible/roles/host_ocp4_rosa_install/tasks/rosa_cluster_login.yml b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_cluster_login.yml new file mode 100644 index 00000000..a3ea7986 --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_cluster_login.yml @@ -0,0 +1,47 @@ +--- +- name: Wait for cluster-admin login to succeed + ansible.builtin.command: >- + /usr/local/bin/oc login + -u cluster-admin + -p {{ _rosa_cluster_admin_password }} + {{ rosa_openshift_api_url }} + --insecure-skip-tls-verify=true + register: r_oc_login + retries: 20 + delay: 30 + until: r_oc_login.rc == 0 + changed_when: false + +- name: Create .kube directory + ansible.builtin.file: + path: "~{{ ansible_user }}/.kube" + owner: "{{ ansible_user }}" + state: directory + mode: u=rwx,og=rx + +- name: Set kubeconfig ownership and permissions + ansible.builtin.file: + path: "~{{ ansible_user }}/.kube/config" + owner: "{{ ansible_user }}" + mode: u=rw + +- name: Create cluster auth directory for compatibility + ansible.builtin.file: + path: "/home/{{ ansible_user }}/{{ cluster_name }}/auth" + owner: "{{ ansible_user }}" + state: directory + mode: u=rwx,og=rx + +- name: Copy kubeconfig to cluster auth directory + ansible.builtin.copy: + src: "~{{ ansible_user }}/.kube/config" + dest: "/home/{{ ansible_user }}/{{ cluster_name }}/auth/kubeconfig" + remote_src: true + owner: "{{ ansible_user }}" + mode: u=rw + +- name: Fetch kubeconfig to output dir + ansible.builtin.fetch: + src: "~{{ ansible_user }}/.kube/config" + dest: "{{ output_dir }}/{{ config }}_{{ guid }}_kubeconfig" + flat: true diff --git a/ansible/roles/host_ocp4_rosa_install/tasks/rosa_create_cluster.yml b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_create_cluster.yml new file mode 100644 index 00000000..e7dfd4b6 --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_create_cluster.yml @@ -0,0 +1,108 @@ +--- +- name: Create ROSA account roles + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa create account-roles + --hosted-cp + --region {{ aws_region }} + --force-policy-creation + --mode auto + --yes + +- name: Create ROSA OIDC config + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa create oidc-config + --region {{ aws_region }} + --mode auto + --yes + --output json + register: r_oidc_config + +- name: Save OIDC config ID + ansible.builtin.set_fact: + _rosa_oidc_id: "{{ (r_oidc_config.stdout | from_json).id }}" + +- name: Clean up stale operator roles for {{ rosa_cluster_name }} + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa delete operator-roles + --prefix {{ rosa_cluster_name }} + --mode auto + --yes + register: r_delete_operator_roles + changed_when: r_delete_operator_roles.rc == 0 + failed_when: false + +- name: Find stale operator IAM roles for {{ rosa_cluster_name }} + ansible.builtin.command: >- + aws iam list-roles + --query "Roles[?starts_with(RoleName, '{{ rosa_cluster_name }}-')].RoleName" + --output json + register: r_stale_iam_roles + changed_when: false + failed_when: false + +- name: Remove stale operator IAM roles for {{ rosa_cluster_name }} + when: r_stale_iam_roles.rc == 0 and (r_stale_iam_roles.stdout | from_json | length > 0) + ansible.builtin.shell: | + role="{{ item }}" + for policy in $(aws iam list-role-policies --role-name "$role" --output text --query 'PolicyNames'); do + aws iam delete-role-policy --role-name "$role" --policy-name "$policy" + done + for arn in $(aws iam list-attached-role-policies --role-name "$role" --output text --query 'AttachedPolicies[].PolicyArn'); do + aws iam detach-role-policy --role-name "$role" --policy-arn "$arn" + done + aws iam delete-role --role-name "$role" + loop: "{{ r_stale_iam_roles.stdout | from_json }}" + failed_when: false + +- name: Create ROSA operator roles + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa create operator-roles + --hosted-cp + --mode auto + --yes + --prefix {{ rosa_cluster_name }} + --oidc-config-id {{ _rosa_oidc_id }} + --installer-role-arn arn:aws:iam::{{ aws_billing_account_id }}:role/ManagedOpenShift-HCP-ROSA-Installer-Role + +- name: Print cluster creation parameters + ansible.builtin.debug: + msg: >- + Creating ROSA cluster '{{ rosa_cluster_name }}' + in region {{ aws_region }} + with subnets {{ _rosa_subnets }} + and OIDC config {{ _rosa_oidc_id }} + +- name: Create ROSA cluster + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa create cluster + --cluster-name {{ rosa_cluster_name }} + --billing-account {{ aws_billing_account_id }} + --sts + --mode auto + --yes + --hosted-cp + --region {{ aws_region }} + --operator-roles-prefix {{ rosa_cluster_name }} + --oidc-config-id {{ _rosa_oidc_id }} + --subnet-ids {{ _rosa_subnets }} + --tags "{{ cloud_tags_list }}" + {% if _rosa_version_to_install | default("") | length > 0 %}--version {{ _rosa_version_to_install }}{% endif %} + {% if rosa_compute_machine_type is defined %}--compute-machine-type {{ rosa_compute_machine_type }}{% endif %} + {% if rosa_compute_worker_disk_size is defined %}--worker-disk-size {{ rosa_compute_worker_disk_size }}{% endif %} + {% if rosa_compute_replicas is defined %}--replicas {{ rosa_compute_replicas | int }}{% endif %} + {% if rosa_machine_cidr | default("") | length > 0 %}--machine-cidr {{ rosa_machine_cidr }}{% endif %} + {% if rosa_service_cidr | default("") | length > 0 %}--service-cidr {{ rosa_service_cidr }}{% endif %} + {% if rosa_pod_cidr | default("") | length > 0 %}--pod-cidr {{ rosa_pod_cidr }}{% endif %} + {% if rosa_host_prefix | default("") | length > 0 %}--host-prefix {{ rosa_host_prefix | int }}{% endif %} + register: r_rosa_create_status + until: r_rosa_create_status.rc == 0 or 'Duplicate cluster name' in r_rosa_create_status.stderr | default('') + retries: 3 + delay: 10 + +- name: Cluster already exists in organization + when: r_rosa_create_status.rc != 0 and 'Duplicate cluster name' in r_rosa_create_status.stderr | default('') + ansible.builtin.debug: + msg: "Cluster '{{ rosa_cluster_name }}' already exists in organization, waiting for it to become ready" + +- name: Wait for ROSA cluster to become ready + ansible.builtin.include_tasks: rosa_create_cluster_wait.yml diff --git a/ansible/roles/host_ocp4_rosa_install/tasks/rosa_create_cluster_wait.yml b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_create_cluster_wait.yml new file mode 100644 index 00000000..733687fc --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_create_cluster_wait.yml @@ -0,0 +1,25 @@ +--- +- name: Wait for ROSA cluster to be ready with console URL + ansible.builtin.shell: | + for i in $(seq 1 120); do + output=$({{ rosa_binary_path }}/rosa describe cluster --cluster {{ rosa_cluster_name }} --output json 2>/dev/null) + if [ $? -eq 0 ]; then + state=$(echo "$output" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status',{}).get('state',''))" 2>/dev/null) + api=$(echo "$output" | python3 -c "import sys,json; print(json.load(sys.stdin).get('api',{}).get('url',''))" 2>/dev/null) + console=$(echo "$output" | python3 -c "import sys,json; print(json.load(sys.stdin).get('console',{}).get('url',''))" 2>/dev/null) + echo "Attempt $i: state=$state api=$api console=$console" + if [ "$state" = "ready" ] && [ -n "$api" ] && [ -n "$console" ]; then + echo "$output" + exit 0 + fi + else + echo "Attempt $i: rosa describe failed, retrying..." + fi + sleep 60 + done + echo "Timed out waiting for ROSA cluster to be ready" + exit 1 + register: r_rosa_wait + changed_when: false + async: 10800 + poll: 30 diff --git a/ansible/roles/host_ocp4_rosa_install/tasks/rosa_post_install.yml b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_post_install.yml new file mode 100644 index 00000000..377bad2f --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_post_install.yml @@ -0,0 +1,23 @@ +--- +- name: Get ROSA cluster details + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa describe cluster + --cluster {{ rosa_cluster_name }} + --output json + register: r_rosa_cluster_info + changed_when: false + +- name: Set ROSA cluster URLs + vars: + _cluster: "{{ r_rosa_cluster_info.stdout | from_json }}" + ansible.builtin.set_fact: + rosa_openshift_api_url: "{{ _cluster.api.url }}" + rosa_openshift_console_url: "{{ _cluster.console.url }}" + +- name: Print cluster URLs + ansible.builtin.debug: + msg: "{{ item }}" + loop: + - "API URL: {{ rosa_openshift_api_url }}" + - "Console URL: {{ rosa_openshift_console_url }}" + diff --git a/ansible/roles/host_ocp4_rosa_install/tasks/rosa_post_install_admin.yml b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_post_install_admin.yml new file mode 100644 index 00000000..c075a738 --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_post_install_admin.yml @@ -0,0 +1,29 @@ +--- +- name: Delete existing ROSA admin user if present + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa delete admin + --cluster {{ rosa_cluster_name }} + --yes + failed_when: false + +- name: Create ROSA admin user + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa create admin + --cluster {{ rosa_cluster_name }} + --yes + register: r_rosa_admin_result + +- name: Extract cluster-admin password from output + vars: + _all_output: "{{ r_rosa_admin_result.stdout ~ '\n' ~ r_rosa_admin_result.stderr }}" + _password_match: "{{ _all_output | regex_search('--password\\s+(\\S+)', '\\1') }}" + ansible.builtin.set_fact: + _rosa_cluster_admin_password: "{{ _password_match | first }}" + +- name: Fail if password could not be extracted + when: _rosa_cluster_admin_password | length == 0 + ansible.builtin.fail: + msg: >- + Failed to extract cluster-admin password from rosa create admin output. + stdout: {{ r_rosa_admin_result.stdout }} + stderr: {{ r_rosa_admin_result.stderr }} diff --git a/ansible/roles/host_ocp4_rosa_install/tasks/rosa_pre_install.yml b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_pre_install.yml new file mode 100644 index 00000000..974953a3 --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_pre_install.yml @@ -0,0 +1,114 @@ +--- +- name: Init AWS account for ROSA + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa init + changed_when: true + +- name: Get available ROSA regions + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa list regions --output json + register: r_rosa_regions + changed_when: false + +- name: Filter regions that support ROSA + ansible.builtin.set_fact: + _rosa_supported_regions: >- + {{ r_rosa_regions.stdout | from_json | json_query(_query) }} + vars: + _query: "[?supports_hypershift == `true`].id" + +- name: Print supported ROSA regions + ansible.builtin.debug: + msg: "Supported regions: {{ _rosa_supported_regions }}" + +- name: Verify requested region supports ROSA + when: aws_region not in _rosa_supported_regions + ansible.builtin.fail: + msg: >- + Region '{{ aws_region }}' does not support ROSA. + Supported regions: {{ _rosa_supported_regions | join(', ') }} + +- name: Get available ROSA versions + ansible.builtin.command: >- + {{ rosa_binary_path }}/rosa list versions --output json --hosted-cp + register: r_rosa_versions + changed_when: false + +- name: Set default ROSA version for {{ rosa_version_base }} + when: rosa_version == "default" + vars: + _query: >- + [?starts_with(id, '{{ rosa_version_base }}') + && (hosted_control_plane_default==`true` || default==`true`)] | [0] + _default_version: >- + {{ r_rosa_versions.stdout | from_json | community.general.json_query(_query) }} + _fallback_query: "[?starts_with(id, '{{ rosa_version_base }}')] | [0]" + _fallback_version: >- + {{ r_rosa_versions.stdout | from_json | community.general.json_query(_fallback_query) }} + _resolved: "{{ _default_version if _default_version else _fallback_version }}" + ansible.builtin.set_fact: + _rosa_version_to_install: "{{ _resolved.raw_id }}" + _rosa_ocp_cli_version: "{{ _resolved.raw_id }}" + +- name: Set specific ROSA version + when: rosa_version not in ("default", "latest", "latest-upgrade") + ansible.builtin.set_fact: + _rosa_version_to_install: "{{ rosa_version }}" + _rosa_ocp_cli_version: "{{ rosa_version }}" + +- name: Set latest ROSA version for {{ rosa_version_base }} + when: rosa_version == "latest" + vars: + _query: "[?starts_with(id, '{{ rosa_version_base }}')] | [0]" + _latest_version: >- + {{ r_rosa_versions.stdout | from_json | community.general.json_query(_query) }} + ansible.builtin.set_fact: + _rosa_version_to_install: "{{ _latest_version.raw_id }}" + _rosa_ocp_cli_version: "{{ _latest_version.raw_id }}" + +- name: Set latest upgradable ROSA version for {{ rosa_version_base }} + when: rosa_version == "latest-upgrade" + vars: + _query: "[?starts_with(id, '{{ rosa_version_base }}') && available_upgrades != `null`] | [0]" + _latest_upgrade_version: >- + {{ r_rosa_versions.stdout | from_json | community.general.json_query(_query) }} + block: + - name: Fail if no upgradable version found for {{ rosa_version_base }} + when: _latest_upgrade_version | default("") == "" + ansible.builtin.fail: + msg: "No upgradable version found for {{ rosa_version_base }}" + + - name: Set upgradable version facts + ansible.builtin.set_fact: + _rosa_version_to_install: "{{ _latest_upgrade_version.raw_id }}" + _rosa_ocp_cli_version: "{{ _latest_upgrade_version.raw_id }}" + _rosa_version_next: "{{ _latest_upgrade_version.available_upgrades[0] }}" + + rescue: + - name: Fallback to latest ROSA version for {{ rosa_version_base }} + vars: + _query: "[?starts_with(id, '{{ rosa_version_base }}')] | [0]" + _latest_version: >- + {{ r_rosa_versions.stdout | from_json | community.general.json_query(_query) }} + ansible.builtin.set_fact: + _rosa_version_to_install: "{{ _latest_version.raw_id }}" + _rosa_ocp_cli_version: "{{ _latest_version.raw_id }}" + +- name: Set cloud_tags_final if not already set + when: cloud_tags_final is not defined + ansible.builtin.include_role: + name: infra_cloud_tags + +- name: Convert cloud tags to comma-separated list for ROSA CLI + ansible.builtin.set_fact: + cloud_tags_list: >- + {% for k, v in cloud_tags_final.items() %}{{ k }} {{ v | replace(' ', '-') }}{% if not loop.last %}, {% endif %}{% endfor %} + +- name: Print ROSA version information + ansible.builtin.debug: + msg: "{{ item }}" + loop: + - "ROSA version to install: {{ _rosa_version_to_install | default('default') }}" + - "ROSA upgrade target: {{ _rosa_version_next | default('not set') }}" + - "OCP CLI version: {{ _rosa_ocp_cli_version }}" + - "Cloud tags: {{ cloud_tags_list }}" diff --git a/ansible/roles/host_ocp4_rosa_install/tasks/rosa_terraform.yml b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_terraform.yml new file mode 100644 index 00000000..9b52ce66 --- /dev/null +++ b/ansible/roles/host_ocp4_rosa_install/tasks/rosa_terraform.yml @@ -0,0 +1,51 @@ +--- +- name: Clone ROSA terraform VPC example + ansible.builtin.git: + accept_hostkey: true + repo: "{{ rosa_terraform_repo }}" + dest: "~{{ ansible_user }}/terraform-vpc" + version: "{{ rosa_terraform_repo_branch }}" + +- name: Run terraform init + ansible.builtin.command: + cmd: /usr/local/bin/terraform init + chdir: "~{{ ansible_user }}/terraform-vpc" + +- name: Run terraform plan + ansible.builtin.command: + cmd: >- + /usr/local/bin/terraform plan + -out rosa.tfplan + -var region={{ aws_region }} + -var cluster_name={{ rosa_cluster_name }} + {% if rosa_machine_cidr | default("") | length > 0 %}-var vpc_cidr={{ rosa_machine_cidr }}{% endif %} + {% if rosa_host_prefix | default("") | length > 0 %}-var subnet_cidr_prefix={{ rosa_host_prefix | int }}{% endif %} + chdir: "~{{ ansible_user }}/terraform-vpc" + +- name: Run terraform apply + ansible.builtin.command: + cmd: /usr/local/bin/terraform apply rosa.tfplan + chdir: "~{{ ansible_user }}/terraform-vpc" + +- name: Archive terraform state directory + ansible.builtin.archive: + path: "~{{ ansible_user }}/terraform-vpc" + dest: "~{{ ansible_user }}/terraform-vpc.tar.gz" + format: gz + +- name: Transfer terraform archive to output dir + ansible.builtin.fetch: + src: "~{{ ansible_user }}/terraform-vpc.tar.gz" + dest: "{{ output_dir }}/" + flat: true + +- name: Get created subnets + ansible.builtin.command: + cmd: /usr/local/bin/terraform output -raw cluster-subnets-string + chdir: "~{{ ansible_user }}/terraform-vpc" + register: r_subnets + changed_when: false + +- name: Save subnet IDs + ansible.builtin.set_fact: + _rosa_subnets: "{{ r_subnets.stdout }}"