From 0f370f9a209826d08750dd7b05d91bad65ee5a88 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Thu, 15 Dec 2022 08:29:59 -0500 Subject: [PATCH 01/21] Content library module --- docs/ref/modules/all.rst | 1 + ...saltext.vmware.modules.content_library.rst | 6 + src/saltext/vmware/modules/content_library.py | 127 ++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 docs/ref/modules/saltext.vmware.modules.content_library.rst create mode 100644 src/saltext/vmware/modules/content_library.py diff --git a/docs/ref/modules/all.rst b/docs/ref/modules/all.rst index 0aea788b..6441af8f 100644 --- a/docs/ref/modules/all.rst +++ b/docs/ref/modules/all.rst @@ -11,6 +11,7 @@ Execution Modules saltext.vmware.modules.cluster saltext.vmware.modules.cluster_drs saltext.vmware.modules.cluster_ha + saltext.vmware.modules.content_library saltext.vmware.modules.datacenter saltext.vmware.modules.datastore saltext.vmware.modules.dvportgroup diff --git a/docs/ref/modules/saltext.vmware.modules.content_library.rst b/docs/ref/modules/saltext.vmware.modules.content_library.rst new file mode 100644 index 00000000..f51fa219 --- /dev/null +++ b/docs/ref/modules/saltext.vmware.modules.content_library.rst @@ -0,0 +1,6 @@ + +saltext.vmware.modules.content_library +====================================== + +.. automodule:: saltext.vmware.modules.content_library + :members: diff --git a/src/saltext/vmware/modules/content_library.py b/src/saltext/vmware/modules/content_library.py new file mode 100644 index 00000000..15ade3bb --- /dev/null +++ b/src/saltext/vmware/modules/content_library.py @@ -0,0 +1,127 @@ +# Copyright 2021 VMware, Inc. +# SPDX-License: Apache-2.0 +import logging + +import saltext.vmware.utils.connect as connect + +log = logging.getLogger(__name__) + +__virtualname__ = "vsphere_content_library" + + +def __virtual__(): + return __virtualname__ + + +def list(): + """ + Lists IDs for all the content libraries on a given vCenter. + """ + response = connect.request( + "/api/content/local-library", "GET", opts=__opts__, pillar=__pillar__ + ) + response = response["response"].json() + return response["value"] + + +def get(id): + """ + Returns info on given content library. + + id + (string) Content Library ID. + """ + url = f"/api/content/local-library/{id}" + response = connect.request(url, "GET", opts=__opts__, pillar=__pillar__) + return response["response"].json() + + +def create(name, description, published, authentication, datastore): + """ + Creates a new content library. + + name + Name of the content library. + + datastore + Datastore ID where library will store its contents. + + description + (optional) Description for the content library being created. + + published + (optional) Whether the content library should be published or not. + + authentication + (optional) The authentication method when the content library is published. + """ + + publish_info = {"published": published, "authentication_method": authentication} + storage_backings = {"datastore_id": datastore, "type": "DATASTORE"} + + data = { + "name": name, + "publish_info": publish_info, + "storage_backings": storage_backings, + "type": "LOCAL", + } + if description is not None: + data["description"] = description + + response = connect.request( + "/api/content/local-library", "POST", body=data, opts=__opts__, pillar=__pillar__ + ) + return response["response"].json() + + +def update(id, name, description, published, authentication, datastore): + """ + Updates content library with given id. + + id + (string) Content library ID . + + name + (optional) Name of the content library. + + description + (optional) Description for the content library being updated. + + published + (optional) Whether the content library should be published or not. + + authentication + (optional) The authentication method when the content library is published. + + datastore + (optional) Datastore ID where library will store its contents. + """ + + publish_info = {"published": published, "authentication_method": authentication} + storage_backings = {"datastore_id": datastore, "type": "DATASTORE"} + + data = {} + if name is not None: + data["name"] = name + if description is not None: + data["name"] = description + if published is not None: + data["publish_info"] = publish_info + if datastore is not None: + data["storage_backings"] = storage_backings + + url = f"/api/content/local-library/{id}" + response = connect.request(url, "PATCH", body=data, opts=__opts__, pillar=__pillar__) + return response["response"].json() + + +def delete(id): + """ + Delete content library having corresponding id. + + id + (string) Content library ID to delete. + """ + url = f"/api/content/local-library/{id}" + response = connect.request(url, "DELETE", opts=__opts__, pillar=__pillar__) + return response["response"].json() From 528c3381240d75d3acf56687fc602bfd04f097d7 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Mon, 2 Jan 2023 01:05:50 -0500 Subject: [PATCH 02/21] Create subscribed library module based on content library one --- docs/ref/modules/all.rst | 1 + ...text.vmware.modules.subscribed_library.rst | 6 + .../vmware/modules/subscribed_library.py | 127 ++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 docs/ref/modules/saltext.vmware.modules.subscribed_library.rst create mode 100644 src/saltext/vmware/modules/subscribed_library.py diff --git a/docs/ref/modules/all.rst b/docs/ref/modules/all.rst index 6441af8f..6a49f21f 100644 --- a/docs/ref/modules/all.rst +++ b/docs/ref/modules/all.rst @@ -33,6 +33,7 @@ Execution Modules saltext.vmware.modules.nsxt_uplink_profiles saltext.vmware.modules.ssl_adapter saltext.vmware.modules.storage_policies + saltext.vmware.modules.subscribed_library saltext.vmware.modules.tag saltext.vmware.modules.vm saltext.vmware.modules.vmc_dhcp_profiles diff --git a/docs/ref/modules/saltext.vmware.modules.subscribed_library.rst b/docs/ref/modules/saltext.vmware.modules.subscribed_library.rst new file mode 100644 index 00000000..6b38e2c9 --- /dev/null +++ b/docs/ref/modules/saltext.vmware.modules.subscribed_library.rst @@ -0,0 +1,6 @@ + +saltext.vmware.modules.subscribed_library +========================================= + +.. automodule:: saltext.vmware.modules.subscribed_library + :members: diff --git a/src/saltext/vmware/modules/subscribed_library.py b/src/saltext/vmware/modules/subscribed_library.py new file mode 100644 index 00000000..dcac03dd --- /dev/null +++ b/src/saltext/vmware/modules/subscribed_library.py @@ -0,0 +1,127 @@ +# Copyright 2021 VMware, Inc. +# SPDX-License: Apache-2.0 +import logging + +import saltext.vmware.utils.connect as connect + +log = logging.getLogger(__name__) + +__virtualname__ = "vsphere_subscribed_library" + + +def __virtual__(): + return __virtualname__ + + +def list(): + """ + Lists IDs for all the subscribed libraries on a given vCenter. + """ + response = connect.request( + "/api/content/subscribed-library", "GET", opts=__opts__, pillar=__pillar__ + ) + response = response["response"].json() + return response["value"] + + +def get(id): + """ + Returns info on given subscribed library. + + id + (string) Subscribed Library ID. + """ + url = f"/api/content/subscribed-library/{id}" + response = connect.request(url, "GET", opts=__opts__, pillar=__pillar__) + return response["response"].json() + + +def create(name, description, published, authentication, datastore): + """ + Creates a new subscribed library. + + name + Name of the subscribed library. + + datastore + Datastore ID where library will store its contents. + + description + (optional) Description for the subscribed library being created. + + published + (optional) Whether the subscribed library should be published or not. + + authentication + (optional) The authentication method for the subscribed library being published. + """ + + publish_info = {"published": published, "authentication_method": authentication} + storage_backings = {"datastore_id": datastore, "type": "DATASTORE"} + + data = { + "name": name, + "publish_info": publish_info, + "storage_backings": storage_backings, + "type": "SUBSCRIBED", + } + if description is not None: + data["description"] = description + + response = connect.request( + "/api/content/subscribed-library", "POST", body=data, opts=__opts__, pillar=__pillar__ + ) + return response["response"].json() + + +def update(id, name, description, published, authentication, datastore): + """ + Updates subscribed library with given id. + + id + (string) Subscribed library ID . + + name + (optional) Name of the subscribed library. + + description + (optional) Description for the subscribed library being updated. + + published + (optional) Whether the subscribed library should be published or not. + + authentication + (optional) The authentication method for the subscribed library being published. + + datastore + (optional) Datastore ID where library will store its contents. + """ + + publish_info = {"published": published, "authentication_method": authentication} + storage_backings = {"datastore_id": datastore, "type": "DATASTORE"} + + data = {} + if name is not None: + data["name"] = name + if description is not None: + data["name"] = description + if published is not None: + data["publish_info"] = publish_info + if datastore is not None: + data["storage_backings"] = storage_backings + + url = f"/api/content/subscribed-library/{id}" + response = connect.request(url, "PATCH", body=data, opts=__opts__, pillar=__pillar__) + return response["response"].json() + + +def delete(id): + """ + Delete subscribed library having corresponding id. + + id + (string) Subscribed library ID to delete. + """ + url = f"/api/content/subscribed-library/{id}" + response = connect.request(url, "DELETE", opts=__opts__, pillar=__pillar__) + return response["response"].json() From 1b87b62463d45aad7bf6a548b873b4d74e58ee35 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Tue, 3 Jan 2023 06:41:39 -0500 Subject: [PATCH 03/21] Added posible content lib module unit test --- tests/unit/modules/test_content_library.py | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/unit/modules/test_content_library.py diff --git a/tests/unit/modules/test_content_library.py b/tests/unit/modules/test_content_library.py new file mode 100644 index 00000000..23923b8c --- /dev/null +++ b/tests/unit/modules/test_content_library.py @@ -0,0 +1,30 @@ +""" + Unit Tests for content library module +""" +from unittest.mock import MagicMock +from unittest.mock import Mock +from unittest.mock import patch + +import pytest +from saltext.vmware.modules import content_library + +dummy_list = {"b87d887b-dfe0-4133-a8c0-a4dbf2977245"} + + +@pytest.fixture +def configure_loader_modules(): + return {content_library: {}} + + +def get_mock_success_response(body): + response = Mock() + response.raise_for_status = Mock() + response.status_code = 200 + response["response"].json = MagicMock(return_value=body) + return response + + +def xtest_list_content_libraries(): + content_library.connect.request = MagicMock(return_value=get_mock_success_response(dummy_list)) + result = content_library.list() + assert result == dummy_list From f6540dc40654cfda8054fea4a63edf15f12a20c1 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Tue, 3 Jan 2023 07:07:45 -0500 Subject: [PATCH 04/21] Content library state initial setup --- docs/ref/states/all.rst | 1 + .../saltext.vmware.states.content_library.rst | 6 +++ src/saltext/vmware/modules/content_library.py | 13 ++++++ src/saltext/vmware/states/content_library.py | 43 +++++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 docs/ref/states/saltext.vmware.states.content_library.rst create mode 100644 src/saltext/vmware/states/content_library.py diff --git a/docs/ref/states/all.rst b/docs/ref/states/all.rst index c6ee2f5f..79244b0d 100644 --- a/docs/ref/states/all.rst +++ b/docs/ref/states/all.rst @@ -8,6 +8,7 @@ State Modules .. autosummary:: :toctree: + saltext.vmware.states.content_library saltext.vmware.states.datacenter saltext.vmware.states.datastore saltext.vmware.states.esxi diff --git a/docs/ref/states/saltext.vmware.states.content_library.rst b/docs/ref/states/saltext.vmware.states.content_library.rst new file mode 100644 index 00000000..aa6195a6 --- /dev/null +++ b/docs/ref/states/saltext.vmware.states.content_library.rst @@ -0,0 +1,6 @@ + +saltext.vmware.states.content_library +===================================== + +.. automodule:: saltext.vmware.states.content_library + :members: diff --git a/src/saltext/vmware/modules/content_library.py b/src/saltext/vmware/modules/content_library.py index 15ade3bb..5a5a703a 100644 --- a/src/saltext/vmware/modules/content_library.py +++ b/src/saltext/vmware/modules/content_library.py @@ -24,6 +24,19 @@ def list(): return response["value"] +def list_detailed(): + """ + Lists all the content libraries on a given vCenter with all their details. + """ + result = {} + library_ids = list() + for library_id in library_ids: + response = get(library_id) + name = response["name"] + result[name] = response + return response + + def get(id): """ Returns info on given content library. diff --git a/src/saltext/vmware/states/content_library.py b/src/saltext/vmware/states/content_library.py new file mode 100644 index 00000000..d13899d3 --- /dev/null +++ b/src/saltext/vmware/states/content_library.py @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: Apache-2.0 +import json +import logging + +import saltext.vmware.utils.common as utils_common +import saltext.vmware.utils.connect as connect + +log = logging.getLogger(__name__) + +__virtualname__ = "content_library" + + +def __virtual__(): + return __virtualname__ + + +def local(name, config): + """ + Set local content libraries based on configuration. + + name + Name of configuration. (required). + + libraries + List of libraries with configuration values. (required). + + Example: + + content_library_example: + content_library.local: + name: local_example + config: + - name: publish + published: true + authentication: NONE + datastore: datastore-00001 + - name: local + datastore: datastore-00001 + """ + + current_state = __salt__["content_library.list_detailed"]() + changes = {} + return {"name": name, "result": True, "comment": "", "changes": changes} From 4cb6c7d94fd527c1ea9ab9acb5bd223bf5e6a6d0 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Tue, 3 Jan 2023 07:37:26 -0500 Subject: [PATCH 05/21] Rename content_library to vsphere_content_library --- src/saltext/vmware/states/content_library.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/saltext/vmware/states/content_library.py b/src/saltext/vmware/states/content_library.py index d13899d3..fbdd4817 100644 --- a/src/saltext/vmware/states/content_library.py +++ b/src/saltext/vmware/states/content_library.py @@ -7,7 +7,7 @@ log = logging.getLogger(__name__) -__virtualname__ = "content_library" +__virtualname__ = "vsphere_content_library" def __virtual__(): @@ -27,7 +27,7 @@ def local(name, config): Example: content_library_example: - content_library.local: + vsphere_content_library.local: name: local_example config: - name: publish @@ -38,6 +38,6 @@ def local(name, config): datastore: datastore-00001 """ - current_state = __salt__["content_library.list_detailed"]() + current_state = __salt__["vsphere_content_library.list_detailed"]() changes = {} return {"name": name, "result": True, "comment": "", "changes": changes} From ed11429226817365ec339bc22cd26073bca8d79f Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Tue, 3 Jan 2023 09:12:52 -0500 Subject: [PATCH 06/21] Content library state initial changes --- src/saltext/vmware/modules/content_library.py | 5 ++- src/saltext/vmware/states/content_library.py | 35 +++++++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/saltext/vmware/modules/content_library.py b/src/saltext/vmware/modules/content_library.py index 5a5a703a..202d3b4f 100644 --- a/src/saltext/vmware/modules/content_library.py +++ b/src/saltext/vmware/modules/content_library.py @@ -20,8 +20,7 @@ def list(): response = connect.request( "/api/content/local-library", "GET", opts=__opts__, pillar=__pillar__ ) - response = response["response"].json() - return response["value"] + return response["response"].json() def list_detailed(): @@ -34,7 +33,7 @@ def list_detailed(): response = get(library_id) name = response["name"] result[name] = response - return response + return result def get(id): diff --git a/src/saltext/vmware/states/content_library.py b/src/saltext/vmware/states/content_library.py index fbdd4817..09a2b54c 100644 --- a/src/saltext/vmware/states/content_library.py +++ b/src/saltext/vmware/states/content_library.py @@ -2,6 +2,7 @@ import json import logging +import salt.utils.data import saltext.vmware.utils.common as utils_common import saltext.vmware.utils.connect as connect @@ -14,6 +15,34 @@ def __virtual__(): return __virtualname__ +def _transform_libraries_to_state(libraries): + result = {} + for name, library in libraries.items(): + library_state = {} + library_state["description"] = library.description + library_state["published"] = library.publish_info.published + library_state["authentication"] = library.publish_info.authentication_method + library_state["datastore"] = library.storage_backings.datastore_id + result[name] = library_state + return result + + +def _transform_config_to_state(config): + result = {} + for library in config: + library_state = {} + if config.description is not None: + library_state["description"] = config.description + if config.description is not None: + library_state["published"] = config.published + if config.description is not None: + library_state["authentication"] = config.authentication + if config.description is not None: + library_state["datastore"] = config.datastore + result[library] = library_state + return result + + def local(name, config): """ Set local content libraries based on configuration. @@ -38,6 +67,8 @@ def local(name, config): datastore: datastore-00001 """ - current_state = __salt__["vsphere_content_library.list_detailed"]() - changes = {} + current_libraries = __salt__["vsphere_content_library.list_detailed"]() + old_state = _transform_libraries_to_state(current_libraries) + new_state = _transform_config_to_state(config) + changes = salt.utils.data.recursive_diff(old_state, new_state) return {"name": name, "result": True, "comment": "", "changes": changes} From fb03fdb5fc9ccf3b00a8e8f94431687cbbfefd44 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Tue, 3 Jan 2023 09:23:32 -0500 Subject: [PATCH 07/21] Fixed content library transformation --- src/saltext/vmware/states/content_library.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/saltext/vmware/states/content_library.py b/src/saltext/vmware/states/content_library.py index 09a2b54c..3efb3a05 100644 --- a/src/saltext/vmware/states/content_library.py +++ b/src/saltext/vmware/states/content_library.py @@ -19,10 +19,10 @@ def _transform_libraries_to_state(libraries): result = {} for name, library in libraries.items(): library_state = {} - library_state["description"] = library.description - library_state["published"] = library.publish_info.published - library_state["authentication"] = library.publish_info.authentication_method - library_state["datastore"] = library.storage_backings.datastore_id + library_state["description"] = library["description"] + library_state["published"] = library["publish_info"]["published"] + library_state["authentication"] = library["publish_info"]["authentication_method"] + library_state["datastore"] = library["storage_backings"][0]["datastore_id"] result[name] = library_state return result From c531fb69569689d5aaff852d64d7aaa069acd4be Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Tue, 3 Jan 2023 09:25:33 -0500 Subject: [PATCH 08/21] Fixed content library config transformation --- src/saltext/vmware/states/content_library.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/saltext/vmware/states/content_library.py b/src/saltext/vmware/states/content_library.py index 3efb3a05..03835303 100644 --- a/src/saltext/vmware/states/content_library.py +++ b/src/saltext/vmware/states/content_library.py @@ -31,13 +31,13 @@ def _transform_config_to_state(config): result = {} for library in config: library_state = {} - if config.description is not None: + if library.description is not None: library_state["description"] = config.description - if config.description is not None: + if library.published is not None: library_state["published"] = config.published - if config.description is not None: + if library.authentication is not None: library_state["authentication"] = config.authentication - if config.description is not None: + if library.datastore is not None: library_state["datastore"] = config.datastore result[library] = library_state return result From 477bd88daac146b97183a391e731b25327438f52 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Tue, 3 Jan 2023 09:27:31 -0500 Subject: [PATCH 09/21] Using content library config directly --- src/saltext/vmware/states/content_library.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/saltext/vmware/states/content_library.py b/src/saltext/vmware/states/content_library.py index 03835303..32b767e2 100644 --- a/src/saltext/vmware/states/content_library.py +++ b/src/saltext/vmware/states/content_library.py @@ -69,6 +69,6 @@ def local(name, config): current_libraries = __salt__["vsphere_content_library.list_detailed"]() old_state = _transform_libraries_to_state(current_libraries) - new_state = _transform_config_to_state(config) - changes = salt.utils.data.recursive_diff(old_state, new_state) + # new_state = _transform_config_to_state(config) + changes = salt.utils.data.recursive_diff(old_state, config) return {"name": name, "result": True, "comment": "", "changes": changes} From fbb6944a5f1d86b31297c533678005c07f2cdfbf Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Thu, 5 Jan 2023 07:24:04 -0500 Subject: [PATCH 10/21] Content library config state fix --- src/saltext/vmware/states/content_library.py | 25 ++++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/saltext/vmware/states/content_library.py b/src/saltext/vmware/states/content_library.py index 32b767e2..46dd6d0c 100644 --- a/src/saltext/vmware/states/content_library.py +++ b/src/saltext/vmware/states/content_library.py @@ -31,14 +31,14 @@ def _transform_config_to_state(config): result = {} for library in config: library_state = {} - if library.description is not None: - library_state["description"] = config.description - if library.published is not None: - library_state["published"] = config.published - if library.authentication is not None: - library_state["authentication"] = config.authentication - if library.datastore is not None: - library_state["datastore"] = config.datastore + if library["description"] is not None: + library_state["description"] = config["description"] + if library["published"] is not None: + library_state["published"] = config["published"] + if library["authentication"] is not None: + library_state["authentication"] = config["authentication"] + if library["datastore"] is not None: + library_state["datastore"] = config["datastore"] result[library] = library_state return result @@ -69,6 +69,11 @@ def local(name, config): current_libraries = __salt__["vsphere_content_library.list_detailed"]() old_state = _transform_libraries_to_state(current_libraries) - # new_state = _transform_config_to_state(config) - changes = salt.utils.data.recursive_diff(old_state, config) + new_state = _transform_config_to_state(config) + changes = salt.utils.data.recursive_diff(old_state, new_state) + if not __opts__["test"] and changes: + for change in changes: + update = change["new"] + # __salt__["vsphere_content_library.update"]() + return {"name": name, "result": True, "comment": "", "changes": changes} From 1d8e4d5514e3cb201cd25dc9b76718bc1926de70 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Fri, 6 Jan 2023 07:21:45 -0500 Subject: [PATCH 11/21] Test content library list --- tests/unit/modules/test_content_library.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/unit/modules/test_content_library.py b/tests/unit/modules/test_content_library.py index 23923b8c..ad987053 100644 --- a/tests/unit/modules/test_content_library.py +++ b/tests/unit/modules/test_content_library.py @@ -1,7 +1,6 @@ """ Unit Tests for content library module """ -from unittest.mock import MagicMock from unittest.mock import Mock from unittest.mock import patch @@ -18,13 +17,14 @@ def configure_loader_modules(): def get_mock_success_response(body): response = Mock() - response.raise_for_status = Mock() response.status_code = 200 - response["response"].json = MagicMock(return_value=body) + response.json = Mock(return_value=body) return response -def xtest_list_content_libraries(): - content_library.connect.request = MagicMock(return_value=get_mock_success_response(dummy_list)) +@patch.object(content_library.connect, "request") +def test_list_content_libraries(mock_request): + response = get_mock_success_response(dummy_list) + mock_request.return_value = {"response": response, "token": ""} result = content_library.list() assert result == dummy_list From a63329b2ce365a48bfea88ff8f8c8fc1b9132818 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Fri, 6 Jan 2023 07:23:44 -0500 Subject: [PATCH 12/21] Fixed content library transform config --- src/saltext/vmware/states/content_library.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/saltext/vmware/states/content_library.py b/src/saltext/vmware/states/content_library.py index 46dd6d0c..e71e3e5b 100644 --- a/src/saltext/vmware/states/content_library.py +++ b/src/saltext/vmware/states/content_library.py @@ -31,13 +31,13 @@ def _transform_config_to_state(config): result = {} for library in config: library_state = {} - if library["description"] is not None: + if "description" in library: library_state["description"] = config["description"] - if library["published"] is not None: + if "published" in library: library_state["published"] = config["published"] - if library["authentication"] is not None: + if "authentication" in library: library_state["authentication"] = config["authentication"] - if library["datastore"] is not None: + if "datastore" in library: library_state["datastore"] = config["datastore"] result[library] = library_state return result From 9fba205f57c8de07c471d1edd3149c050b17db11 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Fri, 6 Jan 2023 07:35:15 -0500 Subject: [PATCH 13/21] Validations on content library state file --- src/saltext/vmware/states/content_library.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/saltext/vmware/states/content_library.py b/src/saltext/vmware/states/content_library.py index e71e3e5b..26027bf2 100644 --- a/src/saltext/vmware/states/content_library.py +++ b/src/saltext/vmware/states/content_library.py @@ -30,16 +30,18 @@ def _transform_libraries_to_state(libraries): def _transform_config_to_state(config): result = {} for library in config: + if "name" not in library: + raise ValueError("Every library configuration should have a name") library_state = {} if "description" in library: - library_state["description"] = config["description"] + library_state["description"] = library["description"] if "published" in library: - library_state["published"] = config["published"] + library_state["published"] = library["published"] if "authentication" in library: - library_state["authentication"] = config["authentication"] + library_state["authentication"] = library["authentication"] if "datastore" in library: - library_state["datastore"] = config["datastore"] - result[library] = library_state + library_state["datastore"] = library["datastore"] + result[library["name"]] = library_state return result From 71e006f2b822fc320150debf9a7895bf1cd946a3 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Fri, 6 Jan 2023 08:05:03 -0500 Subject: [PATCH 14/21] Content library remediation --- src/saltext/vmware/modules/content_library.py | 63 +++++++++++-------- src/saltext/vmware/states/content_library.py | 20 ++++-- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/saltext/vmware/modules/content_library.py b/src/saltext/vmware/modules/content_library.py index 202d3b4f..016e0404 100644 --- a/src/saltext/vmware/modules/content_library.py +++ b/src/saltext/vmware/modules/content_library.py @@ -48,37 +48,43 @@ def get(id): return response["response"].json() -def create(name, description, published, authentication, datastore): +def create(library): """ Creates a new content library. - name - Name of the content library. + library + Dictionary having values for library, as following: - datastore - Datastore ID where library will store its contents. + name + Name of the content library. - description - (optional) Description for the content library being created. + description + (optional) Description for the content library being created. - published - (optional) Whether the content library should be published or not. + datastore + Datastore ID where library will store its contents. - authentication - (optional) The authentication method when the content library is published. + published + (optional) Whether the content library should be published or not. + + authentication + (optional) The authentication method when the content library is published. """ - publish_info = {"published": published, "authentication_method": authentication} - storage_backings = {"datastore_id": datastore, "type": "DATASTORE"} + publish_info = { + "published": library["published"], + "authentication_method": library["authentication"], + } + storage_backings = {"datastore_id": library["datastore"], "type": "DATASTORE"} data = { - "name": name, + "name": library["name"], "publish_info": publish_info, "storage_backings": storage_backings, "type": "LOCAL", } - if description is not None: - data["description"] = description + if "description" in library: + data["description"] = library["description"] response = connect.request( "/api/content/local-library", "POST", body=data, opts=__opts__, pillar=__pillar__ @@ -86,27 +92,30 @@ def create(name, description, published, authentication, datastore): return response["response"].json() -def update(id, name, description, published, authentication, datastore): +def update(id, library): """ Updates content library with given id. id (string) Content library ID . - name - (optional) Name of the content library. + library + Dictionary having values for library, as following: + + name + (optional) Name of the content library. - description - (optional) Description for the content library being updated. + description + (optional) Description for the content library being updated. - published - (optional) Whether the content library should be published or not. + published + (optional) Whether the content library should be published or not. - authentication - (optional) The authentication method when the content library is published. + authentication + (optional) The authentication method when the content library is published. - datastore - (optional) Datastore ID where library will store its contents. + datastore + (optional) Datastore ID where library will store its contents. """ publish_info = {"published": published, "authentication_method": authentication} diff --git a/src/saltext/vmware/states/content_library.py b/src/saltext/vmware/states/content_library.py index 26027bf2..b90c51d4 100644 --- a/src/saltext/vmware/states/content_library.py +++ b/src/saltext/vmware/states/content_library.py @@ -74,8 +74,20 @@ def local(name, config): new_state = _transform_config_to_state(config) changes = salt.utils.data.recursive_diff(old_state, new_state) if not __opts__["test"] and changes: - for change in changes: - update = change["new"] - # __salt__["vsphere_content_library.update"]() - + for name, library in changes["new"].items(): + if name in changes["old"]: + library_id = current_libraries[name]["id"] + __salt__["vsphere_content_library.update"](library_id, library) + else: + if "datastore" not in library: + raise ValueError( + "Non existing libraries must be provided with a datastore ID for creation" + ) + new_library = { + "name": name, + "datastore": library["datastore"], + "published": library.get("published") or False, + "published": library.get("authentication") or "NONE", + } + __salt__["vsphere_content_library.create"](new_library) return {"name": name, "result": True, "comment": "", "changes": changes} From 784a950464bea59c09c02a7fed6392c265b3d585 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Fri, 6 Jan 2023 08:07:06 -0500 Subject: [PATCH 15/21] Fix new library auth key --- src/saltext/vmware/states/content_library.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/saltext/vmware/states/content_library.py b/src/saltext/vmware/states/content_library.py index b90c51d4..0f012ac7 100644 --- a/src/saltext/vmware/states/content_library.py +++ b/src/saltext/vmware/states/content_library.py @@ -87,7 +87,7 @@ def local(name, config): "name": name, "datastore": library["datastore"], "published": library.get("published") or False, - "published": library.get("authentication") or "NONE", + "authentication": library.get("authentication") or "NONE", } __salt__["vsphere_content_library.create"](new_library) return {"name": name, "result": True, "comment": "", "changes": changes} From 4952d7dcdc839ad4cd0485b08eabac5b997f4eec Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Fri, 6 Jan 2023 08:34:23 -0500 Subject: [PATCH 16/21] Content library fixes for remediation --- src/saltext/vmware/modules/content_library.py | 19 +++++++++---------- src/saltext/vmware/states/content_library.py | 6 ++---- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/saltext/vmware/modules/content_library.py b/src/saltext/vmware/modules/content_library.py index 016e0404..590ac917 100644 --- a/src/saltext/vmware/modules/content_library.py +++ b/src/saltext/vmware/modules/content_library.py @@ -75,7 +75,7 @@ def create(library): "published": library["published"], "authentication_method": library["authentication"], } - storage_backings = {"datastore_id": library["datastore"], "type": "DATASTORE"} + storage_backings = [{"datastore_id": library["datastore"], "type": "DATASTORE"}] data = { "name": library["name"], @@ -118,18 +118,17 @@ def update(id, library): (optional) Datastore ID where library will store its contents. """ - publish_info = {"published": published, "authentication_method": authentication} - storage_backings = {"datastore_id": datastore, "type": "DATASTORE"} + publish_info = {} + if "published" in library: + publish_info["published"] = library["published"] + if "authentication" in library: + publish_info["authentication_method"] = library["authentication"] data = {} - if name is not None: - data["name"] = name - if description is not None: - data["name"] = description - if published is not None: + if publish_info: data["publish_info"] = publish_info - if datastore is not None: - data["storage_backings"] = storage_backings + if "datastore" in library: + data["storage_backings"] = [{"datastore_id": library["datastore"], "type": "DATASTORE"}] url = f"/api/content/local-library/{id}" response = connect.request(url, "PATCH", body=data, opts=__opts__, pillar=__pillar__) diff --git a/src/saltext/vmware/states/content_library.py b/src/saltext/vmware/states/content_library.py index 0f012ac7..30812ce3 100644 --- a/src/saltext/vmware/states/content_library.py +++ b/src/saltext/vmware/states/content_library.py @@ -1,10 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 -import json import logging import salt.utils.data -import saltext.vmware.utils.common as utils_common -import saltext.vmware.utils.connect as connect log = logging.getLogger(__name__) @@ -73,7 +70,8 @@ def local(name, config): old_state = _transform_libraries_to_state(current_libraries) new_state = _transform_config_to_state(config) changes = salt.utils.data.recursive_diff(old_state, new_state) - if not __opts__["test"] and changes: + changes_required = any(changes.values()) + if not __opts__["test"] and changes_required: for name, library in changes["new"].items(): if name in changes["old"]: library_id = current_libraries[name]["id"] From a04b38be7402232dadac5a7d03a177d139198e19 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Fri, 6 Jan 2023 08:45:22 -0500 Subject: [PATCH 17/21] Fix changes_required for content library state --- src/saltext/vmware/states/content_library.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/saltext/vmware/states/content_library.py b/src/saltext/vmware/states/content_library.py index 30812ce3..eeb6c613 100644 --- a/src/saltext/vmware/states/content_library.py +++ b/src/saltext/vmware/states/content_library.py @@ -70,7 +70,7 @@ def local(name, config): old_state = _transform_libraries_to_state(current_libraries) new_state = _transform_config_to_state(config) changes = salt.utils.data.recursive_diff(old_state, new_state) - changes_required = any(changes.values()) + changes_required = any(changes["new"].values()) if not __opts__["test"] and changes_required: for name, library in changes["new"].items(): if name in changes["old"]: From 02ecb0566def65e96ec464f623f651010ea67f05 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Fri, 6 Jan 2023 10:59:54 -0500 Subject: [PATCH 18/21] Content library test detailed list --- tests/unit/modules/test_content_library.py | 30 ++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/tests/unit/modules/test_content_library.py b/tests/unit/modules/test_content_library.py index ad987053..002b127f 100644 --- a/tests/unit/modules/test_content_library.py +++ b/tests/unit/modules/test_content_library.py @@ -7,7 +7,24 @@ import pytest from saltext.vmware.modules import content_library -dummy_list = {"b87d887b-dfe0-4133-a8c0-a4dbf2977245"} +dummy_list = ["d9e26762-493c-4722-856d-ca00097269b5"] +dummy_get = { + "creation_time": "2022-12-14T14:46:13.538Z", + "storage_backings": [{"datastore_id": "datastore-35032", "type": "DATASTORE"}], + "last_modified_time": "2022-12-14T14:47:42.785Z", + "server_guid": "cc6c3419-d4e3-4253-864d-7150e1be1430", + "description": "Modified 1", + "type": "LOCAL", + "version": "3", + "name": "API_changed", + "publish_info": { + "authentication_method": "NONE", + "published": True, + "publish_url": "https://vc-l-01a.corp.local:443/cls/vcsp/lib/d9e26762-493c-4722-856d-ca00097269b5/lib.json", + "persist_json_enabled": False, + }, + "id": "d9e26762-493c-4722-856d-ca00097269b5", +} @pytest.fixture @@ -23,8 +40,17 @@ def get_mock_success_response(body): @patch.object(content_library.connect, "request") -def test_list_content_libraries(mock_request): +def test_content_libraries_list(mock_request): response = get_mock_success_response(dummy_list) mock_request.return_value = {"response": response, "token": ""} result = content_library.list() assert result == dummy_list + + +@patch.object(content_library, "get") +@patch.object(content_library, "list") +def test_content_libraries_detailed_list(mock_list, mock_get): + mock_list.return_value = dummy_list + mock_get.return_value = dummy_get + result = content_library.list_detailed() + assert result From 6efb5d15d2b449491db1384fd85ceb95db068ea9 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Fri, 6 Jan 2023 16:18:08 -0500 Subject: [PATCH 19/21] Content library module full success unit test --- src/saltext/vmware/modules/content_library.py | 4 ++ tests/unit/modules/test_content_library.py | 62 ++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/saltext/vmware/modules/content_library.py b/src/saltext/vmware/modules/content_library.py index 590ac917..e1a17bfd 100644 --- a/src/saltext/vmware/modules/content_library.py +++ b/src/saltext/vmware/modules/content_library.py @@ -125,6 +125,10 @@ def update(id, library): publish_info["authentication_method"] = library["authentication"] data = {} + if "name" in library: + data["name"] = library["name"] + if "description" in library: + publish_info["description"] = library["description"] if publish_info: data["publish_info"] = publish_info if "datastore" in library: diff --git a/tests/unit/modules/test_content_library.py b/tests/unit/modules/test_content_library.py index 002b127f..6b35309b 100644 --- a/tests/unit/modules/test_content_library.py +++ b/tests/unit/modules/test_content_library.py @@ -7,7 +7,8 @@ import pytest from saltext.vmware.modules import content_library -dummy_list = ["d9e26762-493c-4722-856d-ca00097269b5"] +dummy_library_id = "d9e26762-493c-4722-856d-ca00097269b5" +dummy_list = [dummy_library_id] dummy_get = { "creation_time": "2022-12-14T14:46:13.538Z", "storage_backings": [{"datastore_id": "datastore-35032", "type": "DATASTORE"}], @@ -25,6 +26,25 @@ }, "id": "d9e26762-493c-4722-856d-ca00097269b5", } +dummy_create = { + "name": "Newest", + "description": "Test", + "published": True, + "authentication": "NONE", + "datastore": "datastore-35032", +} + +dummy_update_full = { + "name": "Newest", + "description": "Test", + "published": True, + "authentication": "NONE", + "datastore": "datastore-35032", +} + +dummy_update_partial = { + "description": "Test", +} @pytest.fixture @@ -54,3 +74,43 @@ def test_content_libraries_detailed_list(mock_list, mock_get): mock_get.return_value = dummy_get result = content_library.list_detailed() assert result + + +@patch.object(content_library.connect, "request") +def test_content_libraries_get(mock_request): + response = get_mock_success_response(dummy_get) + mock_request.return_value = {"response": response, "token": ""} + result = content_library.get(dummy_library_id) + assert result == dummy_get + + +@patch.object(content_library.connect, "request") +def test_content_libraries_create(mock_request): + response = get_mock_success_response(None) + mock_request.return_value = {"response": response, "token": ""} + result = content_library.create(dummy_create) + assert result is None + + +@patch.object(content_library.connect, "request") +def test_content_libraries_update_full(mock_request): + response = get_mock_success_response(None) + mock_request.return_value = {"response": response, "token": ""} + result = content_library.update(dummy_library_id, dummy_update_full) + assert result is None + + +@patch.object(content_library.connect, "request") +def test_content_libraries_update_partial(mock_request): + response = get_mock_success_response(None) + mock_request.return_value = {"response": response, "token": ""} + result = content_library.update(dummy_library_id, dummy_update_partial) + assert result is None + + +@patch.object(content_library.connect, "request") +def test_content_libraries_delete(mock_request): + response = get_mock_success_response(None) + mock_request.return_value = {"response": response, "token": ""} + result = content_library.delete(dummy_library_id) + assert result is None From eff7f65281ce1bfd110c41c9f9c178e5e68b0938 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Tue, 10 Jan 2023 09:24:50 -0500 Subject: [PATCH 20/21] Tests for content library state --- tests/unit/states/test_content_library.py | 155 ++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 tests/unit/states/test_content_library.py diff --git a/tests/unit/states/test_content_library.py b/tests/unit/states/test_content_library.py new file mode 100644 index 00000000..015886e9 --- /dev/null +++ b/tests/unit/states/test_content_library.py @@ -0,0 +1,155 @@ +""" + Unit Tests for content library state +""" +from unittest.mock import MagicMock +from unittest.mock import Mock +from unittest.mock import patch + +import pytest +from saltext.vmware.states import content_library + +dummy_list_detailed = { + "existing": { + "creation_time": "2022-12-14T14:46:13.538Z", + "storage_backings": [{"datastore_id": "datastore-35032", "type": "DATASTORE"}], + "last_modified_time": "2022-12-14T14:47:42.785Z", + "server_guid": "cc6c3419-d4e3-4253-864d-7150e1be1430", + "description": "Modified 1", + "type": "LOCAL", + "version": "3", + "name": "existing", + "publish_info": { + "authentication_method": "NONE", + "published": True, + "publish_url": "https://vc-l-01a.corp.local:443/cls/vcsp/lib/d9e26762-493c-4722-856d-ca00097269b5/lib.json", + "persist_json_enabled": False, + }, + "id": "d9e26762-493c-4722-856d-ca00097269b5", + } +} + +dummy_config_unchanged = [ + { + "name": "existing", + "published": True, + } +] + +dummy_config_existing = [ + { + "name": "existing", + "published": False, + } +] + +dummy_config_new = [{"name": "new", "published": False, "datastore": "datastore-35032"}] + +dummy_expected_changes_existing = {"existing": {"published": False}} + +dummy_expected_changes_new = {"new": {"published": False, "datastore": "datastore-35032"}} + + +@pytest.fixture +def configure_loader_modules(): + return {content_library: {}} + + +def test_content_library_local_state_test_existing(): + content_library.connect = Mock() + mock_list = Mock(return_value=dummy_list_detailed) + + with patch.dict( + content_library.__salt__, + { + "vsphere_content_library.list_detailed": mock_list, + }, + ): + with patch.dict(content_library.__opts__, {"test": True}): + result = content_library.local("", dummy_config_existing) + + assert result is not None + assert result["result"] + assert result["changes"]["new"] == dummy_expected_changes_existing + mock_list.assert_called() + + +def test_content_library_local_state_existing(): + content_library.connect = Mock() + mock_list = Mock(return_value=dummy_list_detailed) + mock_create = Mock() + + with patch.dict( + content_library.__salt__, + { + "vsphere_content_library.list_detailed": mock_list, + "vsphere_content_library.update": mock_create, + }, + ): + with patch.dict(content_library.__opts__, {"test": False}): + result = content_library.local("", dummy_config_existing) + + assert result is not None + assert result["result"] + assert result["changes"]["new"] == dummy_expected_changes_existing + mock_list.assert_called() + mock_create.assert_called() + + +def test_content_library_local_state_test_new(): + content_library.connect = Mock() + mock_list = Mock(return_value=dummy_list_detailed) + + with patch.dict( + content_library.__salt__, + { + "vsphere_content_library.list_detailed": mock_list, + }, + ): + with patch.dict(content_library.__opts__, {"test": True}): + result = content_library.local("", dummy_config_new) + + assert result is not None + assert result["result"] + assert result["changes"]["new"] == dummy_expected_changes_new + mock_list.assert_called() + + +def test_content_library_local_state_new(): + content_library.connect = Mock() + mock_list = Mock(return_value=dummy_list_detailed) + mock_create = Mock() + + with patch.dict( + content_library.__salt__, + { + "vsphere_content_library.list_detailed": mock_list, + "vsphere_content_library.create": mock_create, + }, + ): + with patch.dict(content_library.__opts__, {"test": False}): + result = content_library.local("", dummy_config_new) + + assert result is not None + assert result["result"] + assert result["changes"]["new"] == dummy_expected_changes_new + mock_list.assert_called() + mock_create.assert_called() + + +def test_get_advanced_config_unchanged(): + content_library.connect = MagicMock() + mock_list = Mock(return_value=dummy_list_detailed) + + with patch.dict( + content_library.__salt__, + { + "vsphere_content_library.list_detailed": mock_list, + }, + ): + with patch.dict(content_library.__opts__, {"test": False}): + result = content_library.local("", dummy_config_unchanged) + + assert result is not None + assert result["changes"]["new"]["existing"] == {} + assert result["result"] + mock_list.assert_called() From 3f5224cb92ad195ebbab45f7beca6cc520527131 Mon Sep 17 00:00:00 2001 From: Jesus Angel Date: Tue, 17 Jan 2023 11:13:40 -0500 Subject: [PATCH 21/21] Copyright libraries --- src/saltext/vmware/modules/content_library.py | 2 +- src/saltext/vmware/modules/subscribed_library.py | 2 +- src/saltext/vmware/states/content_library.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/saltext/vmware/modules/content_library.py b/src/saltext/vmware/modules/content_library.py index e1a17bfd..02e03166 100644 --- a/src/saltext/vmware/modules/content_library.py +++ b/src/saltext/vmware/modules/content_library.py @@ -1,4 +1,4 @@ -# Copyright 2021 VMware, Inc. +# Copyright 2021-2023 VMware, Inc. # SPDX-License: Apache-2.0 import logging diff --git a/src/saltext/vmware/modules/subscribed_library.py b/src/saltext/vmware/modules/subscribed_library.py index dcac03dd..3b124ed9 100644 --- a/src/saltext/vmware/modules/subscribed_library.py +++ b/src/saltext/vmware/modules/subscribed_library.py @@ -1,4 +1,4 @@ -# Copyright 2021 VMware, Inc. +# Copyright 2021-2023 VMware, Inc. # SPDX-License: Apache-2.0 import logging diff --git a/src/saltext/vmware/states/content_library.py b/src/saltext/vmware/states/content_library.py index eeb6c613..022f3225 100644 --- a/src/saltext/vmware/states/content_library.py +++ b/src/saltext/vmware/states/content_library.py @@ -1,3 +1,4 @@ +# Copyright 2021-2023 VMware, Inc. # SPDX-License-Identifier: Apache-2.0 import logging