From bba9dee7fcb009c6e285f1243d1f116ba967c374 Mon Sep 17 00:00:00 2001 From: Justin Cinkelj Date: Thu, 14 Aug 2025 11:13:29 +0200 Subject: [PATCH 1/2] pylint complained Signed-off-by: Justin Cinkelj --- plugins/modules/dns_config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/dns_config.py b/plugins/modules/dns_config.py index b9eebbdf..1ea28e3e 100644 --- a/plugins/modules/dns_config.py +++ b/plugins/modules/dns_config.py @@ -135,6 +135,7 @@ def build_entry_list( if module_entry_list is None: return api_entry_list, False + new_entry_list = [] # make pylint happy if module_entry_list is not None: if state == "set": new_entry_list = module_entry_list From 322462d31798a2794d815db24757075c9b12ef02 Mon Sep 17 00:00:00 2001 From: Justin Cinkelj Date: Mon, 22 Sep 2025 11:53:04 +0200 Subject: [PATCH 2/2] Support updating a HyperCore cluster to arbitrary version Fixes #361 Signed-off-by: Justin Cinkelj --- ...250922-update-hypercore-to-any-version.yml | 4 ++ examples/version_update_single_node.yml | 4 +- plugins/module_utils/errors.py | 6 +++ plugins/module_utils/hypercore_version.py | 10 +++- plugins/modules/version_update.py | 51 +++++++++++++++---- .../defaults/main.yml | 1 + .../meta/argument_specs.yml | 6 +++ .../version_update_single_node/tasks/main.yml | 5 +- .../module_utils/test_hypercore_version.py | 14 +---- 9 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 changelogs/fragments/20250922-update-hypercore-to-any-version.yml diff --git a/changelogs/fragments/20250922-update-hypercore-to-any-version.yml b/changelogs/fragments/20250922-update-hypercore-to-any-version.yml new file mode 100644 index 00000000..20b6aaec --- /dev/null +++ b/changelogs/fragments/20250922-update-hypercore-to-any-version.yml @@ -0,0 +1,4 @@ +--- +major_changes: + - Support HyperCore update to arbitrary version, even if it is not listed under available updates. + (https://github.com/ScaleComputing/HyperCoreAnsibleCollection/pull/364) diff --git a/examples/version_update_single_node.yml b/examples/version_update_single_node.yml index 8c127c34..30e51d54 100644 --- a/examples/version_update_single_node.yml +++ b/examples/version_update_single_node.yml @@ -7,7 +7,8 @@ check_mode: true vars: - desired_version: 9.2.22.212325 + desired_version: 9.5.5.219383 + force_update: false tasks: - name: Show desired_version @@ -36,3 +37,4 @@ name: scale_computing.hypercore.version_update_single_node vars: version_update_single_node_desired_version: "{{ desired_version }}" + version_update_single_node_force_update: "{{ force_update }}" diff --git a/plugins/module_utils/errors.py b/plugins/module_utils/errors.py index da731bdd..9b92c344 100644 --- a/plugins/module_utils/errors.py +++ b/plugins/module_utils/errors.py @@ -20,6 +20,12 @@ class AuthError(ScaleComputingError): pass +class InvalidModuleParam(ScaleComputingError): + def __init__(self, message: str): + self.message = message + super(InvalidModuleParam, self).__init__(self.message) + + class UnexpectedAPIResponse(ScaleComputingError): def __init__(self, response: Request): self.message = "Unexpected response - {0} {1}".format( diff --git a/plugins/module_utils/hypercore_version.py b/plugins/module_utils/hypercore_version.py index d6a39a4a..cd46fd23 100644 --- a/plugins/module_utils/hypercore_version.py +++ b/plugins/module_utils/hypercore_version.py @@ -245,9 +245,15 @@ def get( ) return cls.from_hypercore(update) - def apply(self, rest_client: RestClient, check_mode: bool = False) -> TypedTaskTag: + @classmethod + def apply_update( + cls: type["Update"], + rest_client: RestClient, + version: str, + check_mode: bool = False, + ) -> TypedTaskTag: return rest_client.create_record( - f"/rest/v1/Update/{self.uuid}/apply", payload=None, check_mode=check_mode + f"/rest/v1/Update/{version}/apply", payload=None, check_mode=check_mode ) diff --git a/plugins/modules/version_update.py b/plugins/modules/version_update.py index 9f44ed46..c76bf81b 100644 --- a/plugins/modules/version_update.py +++ b/plugins/modules/version_update.py @@ -30,6 +30,12 @@ - Hypercore version update to be installed on the cluster. type: str required: true + force_update: + description: + - Upgrade the HyperCore cluster to the requested version, + even if requested version is not listed under available versions of the HyperCore cluster. + type: bool + default: false notes: - C(check_mode) is not supported. """ @@ -51,15 +57,19 @@ type: dict contains: uuid: - description: Unique identifier in format major_version.minor_version.revision.build_id + description: Unique identifier in format major_version.minor_version.revision.build_id. type: str sample: 9.2.11.210763 description: - description: Human-readable name for the update + description: | + Human-readable name for the update. + Empty if I(force_update=true). type: str sample: 9.2.11 General Availability change_log: - description: Description of all changes that are in this update, in HTML format + description: | + Description of all changes that are in this update, in HTML format. + Empty if I(force_update=true). type: str sample: ...Please allow between 20-40 minutes per node for the update to complete... build_id: @@ -79,7 +89,9 @@ type: int sample: 11 timestamp: - description: Unix timestamp when the update was released + description: | + Unix timestamp when the update was released. + Value of 0 is returned if I(force_update=true). type: int sample: 0 """ @@ -99,7 +111,8 @@ def run( module: AnsibleModule, rest_client: RestClient ) -> Tuple[bool, Optional[TypedUpdateToAnsible], Dict[Any, Any]]: cluster = Cluster.get(rest_client) - if cluster.icos_version == module.params["icos_version"]: + new_icos_version = module.params["icos_version"] + if cluster.icos_version == new_icos_version: return ( False, None, @@ -108,14 +121,33 @@ def run( after=dict(icos_version=cluster.icos_version), ), ) - update = Update.get(rest_client, module.params["icos_version"], must_exist=True) - update.apply(rest_client) # type: ignore + update = None + if module.params["force_update"]: + if len(new_icos_version.split(".")) != 4: + msg = f"HyperCore version must be in format 'major_version.minor_version.revision.build_id' - value {new_icos_version} is not valid." + raise errors.InvalidModuleParam(msg) + update = Update( + uuid=new_icos_version, + description="", + change_log="", + build_id=new_icos_version.split(".")[3], + major_version=new_icos_version.split(".")[0], + minor_version=new_icos_version.split(".")[1], + revision=new_icos_version.split(".")[2], + timestamp=0, + ) + else: + # check the requested version is listed under available versions + update = Update.get(rest_client, new_icos_version, must_exist=True) + if not isinstance(update, Update): + raise AssertionError("update is not of type Update") # mypy helper + Update.apply_update(rest_client, new_icos_version) return ( True, - update.to_ansible(), # type: ignore + update.to_ansible(), dict( before=dict(icos_version=cluster.icos_version), - after=dict(icos_version=update.uuid), # type: ignore + after=dict(icos_version=new_icos_version), ), ) @@ -126,6 +158,7 @@ def main() -> None: argument_spec=dict( arguments.get_spec("cluster_instance"), icos_version=dict(type="str", required=True), + force_update=dict(type="bool", default=False), ), ) diff --git a/roles/version_update_single_node/defaults/main.yml b/roles/version_update_single_node/defaults/main.yml index 2230276e..db4f72ed 100644 --- a/roles/version_update_single_node/defaults/main.yml +++ b/roles/version_update_single_node/defaults/main.yml @@ -1,6 +1,7 @@ --- version_update_single_node_shutdown_wait_time: "{{ scale_computing_hypercore_shutdown_wait_time | default(300) }}" version_update_single_node_shutdown_tags: "{{ scale_computing_hypercore_shutdown_tags | default([]) }}" +version_update_single_node_force_update: false # renamed variables - if new name is not present, use deprecated name as default value version_update_single_node_shutdown_vms: "{{ scale_computing_hypercore_shutdown_vms | default(omit) }}" version_update_single_node_restart_vms: "{{ scale_computing_hypercore_restart_vms | default(omit) }}" diff --git a/roles/version_update_single_node/meta/argument_specs.yml b/roles/version_update_single_node/meta/argument_specs.yml index cef76220..3403358c 100644 --- a/roles/version_update_single_node/meta/argument_specs.yml +++ b/roles/version_update_single_node/meta/argument_specs.yml @@ -20,6 +20,12 @@ argument_specs: - After wait time expires a force shutdown is issued. Force shutdown can corrupt VM disk data. default: 300 type: int + version_update_single_node_force_update: + description: + - Upgrade the HyperCore cluster to the requested version, + even if requested version is not listed under available versions of the HyperCore cluster. + default: false + type: bool # ------------- # Renamed/deprecated vars scale_computing_hypercore_desired_version: diff --git a/roles/version_update_single_node/tasks/main.yml b/roles/version_update_single_node/tasks/main.yml index 0956d2b7..1dc770b8 100644 --- a/roles/version_update_single_node/tasks/main.yml +++ b/roles/version_update_single_node/tasks/main.yml @@ -52,7 +52,9 @@ msg: >- Requested update {{ version_update_single_node_desired_version }} is not in version_update_single_node_available_updates {{ version_update_single_node_available_updates.records | map(attribute='uuid') | list }} - when: not version_update_single_node_desired_version in (version_update_single_node_available_updates.records | map(attribute='uuid') | list) + when: + - not version_update_single_node_force_update + - not version_update_single_node_desired_version in (version_update_single_node_available_updates.records | map(attribute='uuid') | list) - name: Get all available running VMs scale_computing.hypercore.vm_info: @@ -69,6 +71,7 @@ - name: Update single-node system scale_computing.hypercore.version_update: icos_version: "{{ version_update_single_node_desired_version }}" + force_update: "{{ version_update_single_node_force_update }}" register: version_update_single_node_update_result - name: Check update status diff --git a/tests/unit/plugins/module_utils/test_hypercore_version.py b/tests/unit/plugins/module_utils/test_hypercore_version.py index 598ed39b..4a77b93a 100644 --- a/tests/unit/plugins/module_utils/test_hypercore_version.py +++ b/tests/unit/plugins/module_utils/test_hypercore_version.py @@ -278,18 +278,8 @@ def test_update_equal_false(self): assert update1 != update2 def test_apply_update(self, rest_client): - update = Update( - uuid="9.2.11.210763", - description="9.1.11 General Availability", - change_log="...Please allow between 20-40 minutes per node for the update to complete...", - build_id=210763, - major_version=9, - minor_version=2, - revision=11, - timestamp=0, - ) - - update.apply(rest_client) + new_icos_version = "9.2.11.210763" + Update.apply_update(rest_client, new_icos_version) rest_client.create_record.assert_called_with( "/rest/v1/Update/9.2.11.210763/apply", payload=None, check_mode=False