From 9a19c01e1b1486a172bffa17c422d0a6a9dd0f4c Mon Sep 17 00:00:00 2001 From: Rabin Yasharzadehe Date: Mon, 20 May 2024 22:04:18 +0300 Subject: [PATCH] Add support for self-hosted AI server Signed-off-by: Rabin Yasharzadehe --- plugins/module_utils/api.py | 4 ++ plugins/module_utils/defaults.py | 2 + plugins/modules/create_cluster.py | 39 +++++++++++++---- plugins/modules/create_infra_env.py | 46 +++++++++++++++----- plugins/modules/download_credentials.py | 37 ++++++++++++---- plugins/modules/download_files.py | 2 +- plugins/modules/get_credentials.py | 38 ++++++++++++++--- plugins/modules/install_cluster.py | 57 ++++++++++++++++++------- plugins/modules/wait_for_hosts.py | 54 +++++++++++++++++------ 9 files changed, 216 insertions(+), 63 deletions(-) create mode 100644 plugins/module_utils/api.py create mode 100644 plugins/module_utils/defaults.py diff --git a/plugins/module_utils/api.py b/plugins/module_utils/api.py new file mode 100644 index 0000000..8577e10 --- /dev/null +++ b/plugins/module_utils/api.py @@ -0,0 +1,4 @@ +MANIFESTS = 'manifests' +ACTIONS_INSTALL = 'actions/install' +REGISTER_CLUSTER = 'clusters' +REGISTER_INFRASTRUCTURE = 'infra-envs' diff --git a/plugins/module_utils/defaults.py b/plugins/module_utils/defaults.py new file mode 100644 index 0000000..fcf45b6 --- /dev/null +++ b/plugins/module_utils/defaults.py @@ -0,0 +1,2 @@ +ai_api_endpoint = 'https://api.openshift.com/api/assisted-install/v2' +validate_certificate = True diff --git a/plugins/modules/create_cluster.py b/plugins/modules/create_cluster.py index 1391b00..893c9af 100644 --- a/plugins/modules/create_cluster.py +++ b/plugins/modules/create_cluster.py @@ -9,6 +9,8 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.rhpds.assisted_installer.plugins.module_utils import access_token +from ansible_collections.rhpds.assisted_installer.plugins.module_utils import api +from ansible_collections.rhpds.assisted_installer.plugins.module_utils import defaults DOCUMENTATION = r''' --- @@ -21,6 +23,14 @@ description: Creates a new OpenShift cluster definition using Assisted Installer options: + ai_api_endpoint: + description: The AI Endpoint + required: false + type: str + validate_certificate: + description: validate the API certificate + required: false + type: bool name: description: Name of the cluster required: true @@ -171,6 +181,8 @@ def run_module(): # define available arguments/parameters a user can pass to the module module_args = dict( + ai_api_endpoint=dict(type='str', required=False, default=defaults.ai_api_endpoint), + validate_certificate=dict(type='bool', required=False, default=defaults.validate_certificate), name=dict(type='str', required=True), offline_token=dict(type='str', required=True), openshift_version=dict(type='str', required=True), @@ -215,26 +227,37 @@ def run_module(): module = AnsibleModule( argument_spec=module_args, supports_check_mode=True, + ) - response = access_token._get_access_token(module.params['offline_token']) - if response.status_code != 200: - module.fail_json(msg='Error getting access token ', **response.json()) + + headers = { + "Content-Type": "application/json" + } + + if module.params['offline_token'] != 'None': + response = access_token._get_access_token(module.params['offline_token']) + if response.status_code != 200: + module.fail_json(msg='Error getting access token ', **response.json()) + headers.update({ + "Authorization": "Bearer " + response.json()["access_token"], + }) + # if the user is working with this module in only check mode we do not # want to make any changes to the environment, just return the current # state with no modifications if module.check_mode: module.exit_json(**result) - headers = { - "Authorization": "Bearer " + response.json()["access_token"], - "Content-Type": "application/json" - } + params = module.params.copy() params.pop("offline_token") + params.pop("ai_api_endpoint") + params.pop("validate_certificate") + if "cluster_id" in params: params.pop("cluster_id") params["pull_secret"] = json.loads(params["pull_secret"]) response = session.post( - "https://api.openshift.com/api/assisted-install/v2/clusters", + module.params['ai_api_endpoint'] + '/' + api.REGISTER_CLUSTER, headers=headers, json=params ) diff --git a/plugins/modules/create_infra_env.py b/plugins/modules/create_infra_env.py index bf7dad1..43ac678 100644 --- a/plugins/modules/create_infra_env.py +++ b/plugins/modules/create_infra_env.py @@ -9,6 +9,8 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.rhpds.assisted_installer.plugins.module_utils import access_token +from ansible_collections.rhpds.assisted_installer.plugins.module_utils import api +from ansible_collections.rhpds.assisted_installer.plugins.module_utils import defaults DOCUMENTATION = r''' @@ -22,12 +24,24 @@ description: Creates a new OpenShift Discovery ISO for Assisted Installer options: + ai_api_endpoint: + description: The AI Endpoint + required: false + type: str + validate_certificate: + description: validate the API certificate + required: false + type: bool + offline_token: + description: Offline token from console.redhat.com + required: true + type: str additional_ntp_sources: description: A comma-separated list of NTP sources (name or IP) going to be added to all the hosts. required: false type: str additional_trust_bundle: - description: PEM-encoded X.509 certificate bundle. Hosts discovered by this infra-env will trust the certificates in this bundle. Clusters formed from the hosts discovered by this infra-env will also trust the certificates in this bundle. + description: PEM-encoded X.509 certificate bundle. Hosts discovered by this infra-env will trust the certificates in this bundle. Clusters formed from the hosts discovered by this infra-env will also trust the certificates in this bundle. required: false type: str cluster_id: @@ -100,6 +114,8 @@ def run_module(): # define available arguments/parameters a user can pass to the module module_args = dict( + ai_api_endpoint=dict(type='str', required=False, default=defaults.ai_api_endpoint), + validate_certificate=dict(type='bool', required=False, default=defaults.validate_certificate), name=dict(type='str', required=True), offline_token=dict(type='str', required=True), cluster_id=dict(type='str', required=True), @@ -138,9 +154,20 @@ def run_module(): supports_check_mode=True ) - response = access_token._get_access_token(module.params['offline_token']) - if response.status_code != 200: - module.fail_json(msg='Error getting access token ', **response.json()) + headers = { + "Content-Type": "application/json" + } + + if module.params['offline_token'] != 'None' : + response = access_token._get_access_token(module.params['offline_token']) + if response.status_code != 200: + module.fail_json(msg='Error getting access token ', **response.json()) + + result['access_token'] = response.json()["access_token"] + headers.update({ + "Authorization": "Bearer " + response.json()["access_token"], + }) + # if the user is working with this module in only check mode we do not # want to make any changes to the environment, just return the current @@ -148,17 +175,14 @@ def run_module(): if module.check_mode: module.exit_json(**result) - result['access_token'] = response.json()["access_token"] - - headers = { - "Authorization": "Bearer " + response.json()["access_token"], - "Content-Type": "application/json" - } params = module.params.copy() params.pop("offline_token") + params.pop("ai_api_endpoint") + params.pop("validate_certificate") + params["pull_secret"] = json.loads(params["pull_secret"]) response = session.post( - "https://api.openshift.com/api/assisted-install/v2/infra-envs", + module.params['ai_api_endpoint'] + '/' + api.REGISTER_INFRASTRUCTURE, headers=headers, json=params ) diff --git a/plugins/modules/download_credentials.py b/plugins/modules/download_credentials.py index 2c8c28d..204c098 100644 --- a/plugins/modules/download_credentials.py +++ b/plugins/modules/download_credentials.py @@ -9,6 +9,8 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.rhpds.assisted_installer.plugins.module_utils import access_token +from ansible_collections.rhpds.assisted_installer.plugins.module_utils import api +from ansible_collections.rhpds.assisted_installer.plugins.module_utils import defaults DOCUMENTATION = r''' @@ -22,6 +24,14 @@ description: Downloads credentials relating to the installed/installing cluster. options: + ai_api_endpoint: + description: The AI Endpoint + required: false + type: str + validate_certificate: + description: validate the API certificate + required: false + type: bool cluster_id: description: ID of the cluster required: true @@ -61,6 +71,8 @@ def run_module(): # define available arguments/parameters a user can pass to the module module_args = dict( + ai_api_endpoint=dict(type='str', required=False, default=defaults.ai_api_endpoint), + validate_certificate=dict(type='bool', required=False, default=defaults.validate_certificate), cluster_id=dict(type='str', required=True), offline_token=dict(type='str', required=True), file_name=dict(type='str', required=True), @@ -88,22 +100,31 @@ def run_module(): argument_spec=module_args, supports_check_mode=True ) - response = access_token._get_access_token(module.params['offline_token']) - if response.status_code != 200: - module.fail_json(msg='Error getting access token ', **response.json()) - result['access_token'] = response.json()["access_token"] - params = module.params.copy() - params.pop("offline_token") headers = { - "Authorization": "Bearer " + response.json()["access_token"], "Content-Type": "application/json" } + + if module.params['offline_token'] != 'None': + response = access_token._get_access_token(module.params['offline_token']) + if response.status_code != 200: + module.fail_json(msg='Error getting access token ', **response.json()) + result['access_token'] = response.json()["access_token"] + headers.update({ + "Authorization": "Bearer " + response.json()["access_token"], + }) + + params = module.params.copy() + params.pop("offline_token") + params.pop("ai_api_endpoint") + params.pop("validate_certificate") + response = session.get( - "https://api.openshift.com/api/assisted-install/v2/clusters/" + module.params['cluster_id'] + "/downloads/credentials", + f"{module.params['ai_api_endpoint']}/{api.REGISTER_CLUSTER}/{module.params['cluster_id']}/downloads/credentials", headers=headers, params=params ) + if "code" in response: module.fail_json(msg='Request failed: ' + response) diff --git a/plugins/modules/download_files.py b/plugins/modules/download_files.py index 2046d39..fef2ed8 100644 --- a/plugins/modules/download_files.py +++ b/plugins/modules/download_files.py @@ -13,7 +13,7 @@ DOCUMENTATION = r''' --- -module: download_credentials +module: download_files short_description: Downloads files relating to the installed/installing cluster. diff --git a/plugins/modules/get_credentials.py b/plugins/modules/get_credentials.py index d90bde5..32e886a 100644 --- a/plugins/modules/get_credentials.py +++ b/plugins/modules/get_credentials.py @@ -8,7 +8,15 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.rhpds.assisted_installer.plugins.module_utils import access_token +from ansible_collections.rhpds.assisted_installer.plugins.module_utils import api +from ansible_collections.rhpds.assisted_installer.plugins.module_utils import defaults +import logging +import logging.handlers +my_logger = logging.getLogger('MyLogger') +my_logger.setLevel(logging.DEBUG) +handler = logging.handlers.SysLogHandler(address = '/dev/log') +my_logger.addHandler(handler) DOCUMENTATION = r''' --- @@ -21,11 +29,18 @@ description: Get the cluster admin credentials. options: + ai_api_endpoint: + description: The AI Endpoint + required: false + type: str + validate_certificate: + description: validate the API certificate + required: false + type: bool cluster_id: description: ID of the cluster required: true type: str - offline_token: description: Offline token from console.redhat.com required: true @@ -54,6 +69,8 @@ def run_module(): # define available arguments/parameters a user can pass to the module module_args = dict( + ai_api_endpoint=dict(type='str', required=False, default=defaults.ai_api_endpoint), + validate_certificate=dict(type='bool', required=False, default=defaults.validate_certificate), cluster_id=dict(type='str', required=True), offline_token=dict(type='str', required=True), ) @@ -79,20 +96,27 @@ def run_module(): argument_spec=module_args, supports_check_mode=True ) - response = access_token._get_access_token(module.params['offline_token']) - if response.status_code != 200: - module.fail_json(msg='Error getting access token ', **response.json()) - result['access_token'] = response.json()["access_token"] headers = { - "Authorization": "Bearer " + response.json()["access_token"], "Content-Type": "application/json" } + + if module.params['offline_token'] != 'None': + response = access_token._get_access_token(module.params['offline_token']) + if response.status_code != 200: + module.fail_json(msg='Error getting access token ', **response.json()) + result['access_token'] = response.json()["access_token"] + headers.update({ + "Authorization": "Bearer " + response.json()["access_token"], + }) + response = session.get( - "https://api.openshift.com/api/assisted-install/v2/clusters/" + module.params['cluster_id'] + "/credentials", + f"{module.params['ai_api_endpoint']}/{api.REGISTER_CLUSTER}/{module.params['cluster_id']}/credentials", headers=headers, ) + if "code" in response.json(): + my_logger.error(response.json()) module.fail_json(msg='Request failed: ' + response) else: result['result'] = response.json() diff --git a/plugins/modules/install_cluster.py b/plugins/modules/install_cluster.py index 00acd00..21e1865 100644 --- a/plugins/modules/install_cluster.py +++ b/plugins/modules/install_cluster.py @@ -9,7 +9,15 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.rhpds.assisted_installer.plugins.module_utils import access_token +from ansible_collections.rhpds.assisted_installer.plugins.module_utils import api +from ansible_collections.rhpds.assisted_installer.plugins.module_utils import defaults +import logging +import logging.handlers +my_logger = logging.getLogger('MyLogger') +my_logger.setLevel(logging.DEBUG) +handler = logging.handlers.SysLogHandler(address = '/dev/log') +my_logger.addHandler(handler) DOCUMENTATION = r''' --- @@ -22,6 +30,14 @@ description: creates a new cluster on console.redhat.com/openshift options: + ai_api_endpoint: + description: The AI Endpoint + required: false + type: str + validate_certificate: + description: validate the API certificate + required: false + type: bool cluster_id: description: ID of the cluster required: true @@ -66,6 +82,8 @@ def run_module(): # define available arguments/parameters a user can pass to the module module_args = dict( + ai_api_endpoint=dict(type='str', required=False, default=defaults.ai_api_endpoint), + validate_certificate=dict(type='bool', required=False, default=defaults.validate_certificate), cluster_id=dict(type='str', required=True), offline_token=dict(type='str', required=True), wait_timeout=dict(type='int', required=False, default=1800), @@ -93,15 +111,21 @@ def run_module(): argument_spec=module_args, supports_check_mode=True ) - response = access_token._get_access_token(module.params['offline_token']) - if response.status_code != 200: - module.fail_json(msg='Error getting access token ', **response.json()) headers = { - "Authorization": "Bearer " + response.json()["access_token"], "Content-Type": "application/json" } + if module.params['offline_token'] != 'None': + response = access_token._get_access_token(module.params['offline_token']) + if response.status_code != 200: + module.fail_json(msg='Error getting access token ', **response.json()) + headers.update({ + "Authorization": "Bearer " + response.json()["access_token"], + }) + result['access_token'] = response.json()["access_token"] + + ### TODO: Can we make idempotent? response = session.post( - "https://api.openshift.com/api/assisted-install/v2/clusters/" + module.params['cluster_id'] + "/actions/install", + f"{module.params['ai_api_endpoint']}/{api.REGISTER_CLUSTER}/{module.params['cluster_id']}/actions/install", headers=headers, ) if "code" in response.json(): @@ -112,10 +136,17 @@ def run_module(): max_retries = module.params['wait_timeout'] / module.params['delay'] while retries < max_retries and cluster_installed is False: - response = access_token._get_access_token(module.params['offline_token']) - if response.status_code != 200: - module.fail_json(msg='Error getting access token ', **response.json()) - + headers = { + "Content-Type": "application/json" + } + if module.params['offline_token'] != 'None': + response = access_token._get_access_token(module.params['offline_token']) + if response.status_code != 200: + module.fail_json(msg='Error getting access token ', **response.json()) + result['access_token'] = response.json()["access_token"] + headers.update({ + "Authorization": "Bearer " + response.json()["access_token"], + }) # if the user is working with this module in only check mode we do not # want to make any changes to the environment, just return the current # state with no modifications @@ -124,14 +155,8 @@ def run_module(): # manipulate or modify the state as needed (this is going to be the # part where your module will do what it needs to do) - result['access_token'] = response.json()["access_token"] - - headers = { - "Authorization": "Bearer " + response.json()["access_token"], - "Content-Type": "application/json" - } response = session.get( - "https://api.openshift.com/api/assisted-install/v2/clusters/" + module.params['cluster_id'], + f"{module.params['ai_api_endpoint']}/{api.REGISTER_CLUSTER}/{module.params['cluster_id']}", headers=headers, ) if "code" in response.json(): diff --git a/plugins/modules/wait_for_hosts.py b/plugins/modules/wait_for_hosts.py index 44d7a86..e78e7e3 100644 --- a/plugins/modules/wait_for_hosts.py +++ b/plugins/modules/wait_for_hosts.py @@ -9,7 +9,15 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.rhpds.assisted_installer.plugins.module_utils import access_token +from ansible_collections.rhpds.assisted_installer.plugins.module_utils import api +from ansible_collections.rhpds.assisted_installer.plugins.module_utils import defaults +import logging +import logging.handlers +my_logger = logging.getLogger('MyLogger') +my_logger.setLevel(logging.DEBUG) +handler = logging.handlers.SysLogHandler(address = '/dev/log') +my_logger.addHandler(handler) DOCUMENTATION = r''' --- @@ -22,6 +30,14 @@ description: Wait for the hosts to be ready and configure them. options: + ai_api_endpoint: + description: The AI Endpoint + required: false + type: str + validate_certificate: + description: validate the API certificate + required: false + type: bool cluster_id: description: ID of the cluster required: true @@ -69,6 +85,8 @@ def run_module(): # define available arguments/parameters a user can pass to the module module_args = dict( + ai_api_endpoint=dict(type='str', required=False, default=defaults.ai_api_endpoint), + validate_certificate=dict(type='bool', required=False, default=defaults.validate_certificate), cluster_id=dict(type='str', required=True), infra_env_id=dict(type='str', required=False), offline_token=dict(type='str', required=True), @@ -104,10 +122,20 @@ def run_module(): retries = 0 cluster_ready = False max_retries = module.params['wait_timeout'] / module.params['delay'] + headers = { + "Content-Type": "application/json" + } + while retries < max_retries and cluster_ready is False: - response = access_token._get_access_token(module.params['offline_token']) - if response.status_code != 200: - module.fail_json(msg='Error getting access token ', **response.json()) + + if module.params['offline_token'] != 'None': + response = access_token._get_access_token(module.params['offline_token']) + if response.status_code != 200: + module.fail_json(msg='Error getting access token ', **response.json()) + headers.update({ + "Authorization": "Bearer " + response.json()["access_token"], + }) + result['access_token'] = response.json()["access_token"] # if the user is working with this module in only check mode we do not # want to make any changes to the environment, just return the current @@ -117,29 +145,29 @@ def run_module(): # manipulate or modify the state as needed (this is going to be the # part where your module will do what it needs to do) - result['access_token'] = response.json()["access_token"] - - headers = { - "Authorization": "Bearer " + response.json()["access_token"], - "Content-Type": "application/json" - } response = session.get( - "https://api.openshift.com/api/assisted-install/v2/clusters/" + module.params['cluster_id'], + module.params['ai_api_endpoint'] + '/' + api.REGISTER_CLUSTER + "/" + module.params['cluster_id'], headers=headers, ) if "code" in response.json(): module.fail_json(msg='Request failed: ', **response.json()) + ready_hosts = 0 for host in response.json()['hosts']: + my_logger.debug(f"host: {host['requested_hostname']} status={host['status']}") if host['status'] == "known": ready_hosts = ready_hosts + 1 + my_logger.debug(f"Found host: {host['requested_hostname']} (total={ready_hosts})") if 'configure_hosts' in module.params and module.params['configure_hosts'] is not None: for configure_host in module.params['configure_hosts']: + my_logger.debug('testing config host for: ' + configure_host['hostname']) if host['requested_hostname'] == configure_host['hostname']: + # If the role of the host not match the requested role if host['role'] != configure_host['role']: + my_logger.debug(f"Set host {host['requested_hostname']} role to {configure_host['role']}") data = {"host_role": configure_host['role']} responsepatch = session.patch( - "https://api.openshift.com/api/assisted-install/v2/infra-envs/" + module.params['infra_env_id'] + "/hosts/" + host['id'], + module.params['ai_api_endpoint'] + '/' + api.REGISTER_INFRASTRUCTURE + "/" + module.params['infra_env_id'] + "/hosts/" + host['id'], headers=headers, json=data ) @@ -147,9 +175,10 @@ def run_module(): module.fail_json(msg='Request failed: ', **responsepatch.json()) if "installation_disk" in configure_host: if host['installation_disk_path'] != configure_host['installation_disk']: + my_logger.debug(f"Set host {host['requested_hostname']} install disk to {configure_host['installation_disk']} was: {host['installation_disk_path']}") data = {"disks_selected_config": [{"id": configure_host['installation_disk'], "role": "install"}]} responsepatch = session.patch( - "https://api.openshift.com/api/assisted-install/v2/infra-envs/" + module.params['infra_env_id'] + "/hosts/" + host['id'], + module.params['ai_api_endpoint'] + '/' + api.REGISTER_INFRASTRUCTURE + "/" + module.params['infra_env_id'] + "/hosts/" + host['id'], headers=headers, json=data ) @@ -160,6 +189,7 @@ def run_module(): cluster_ready = True result['result'] = response.json() else: + my_logger.debug(f"Expected {module.params['expected_hosts']} hosts, got {ready_hosts}") time.sleep(module.params['delay']) result['result'] = response.json()