diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd940825..80a0ca05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,21 @@ jobs: python -m pip install --upgrade pip chmod +x ci_deploy.sh ./ci_deploy.sh - - name: Test with unittest + - name: Test unit tests with unittest run: | python -m unittest + - name: Test integration tests for package/tests/integration_tests + env: + GH_TOKEN_DEC: ${{ secrets.GH_TOKEN_DEC }} + CURRENT_BRANCH: ${{ env.BRANCH_NAME }} + AZURE_APPLICATION_KEY_DEC: ${{ secrets.AZURE_APPLICATION_KEY_DEC }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_APPLICATION_ID: ${{ secrets.AZURE_APPLICATION_ID }} + + run: | + pwd + echo "1: ${{ env.BRANCH_NAME }}" + + python -m unittest discover -p "package/tests/integration_tests/mock_api_based_tests/int_test_mock*.py" diff --git a/.gitignore b/.gitignore index 156ac2f9..d1b49dbb 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ __pycache__/ .idea/ venv/ package/tests/.env +package/tests/integration_tests/.env env/ build/ develop-eggs/ @@ -64,4 +65,5 @@ cloudshell_config.yml # env files *.env -!*.template.env \ No newline at end of file +!*.template.env +!package/tests/integration_tests/int_tests.env \ No newline at end of file diff --git a/package/cloudshell/iac/terraform/services/clp_envvar_handler.py b/package/cloudshell/iac/terraform/services/clp_envvar_handler.py index 23859355..bbd6f065 100644 --- a/package/cloudshell/iac/terraform/services/clp_envvar_handler.py +++ b/package/cloudshell/iac/terraform/services/clp_envvar_handler.py @@ -13,7 +13,7 @@ def set_env_vars_based_on_clp(self): @staticmethod def does_attribute_match(clp_res_model, clp_attribute, attr_name_to_check) -> bool: - if f"{clp_res_model}.{clp_attribute.Name}" == attr_name_to_check or clp_attribute.Name == attr_name_to_check: + if f"{clp_res_model}.{attr_name_to_check}" == clp_attribute.Name or attr_name_to_check == clp_attribute.Name: return True return False @@ -57,11 +57,11 @@ def __init__(self, clp_res_model, clp_resource_attributes, shell_helper): def set_env_vars_based_on_clp(self): for attr in self._clp_resource_attributes: - if self.does_attribute_match(self._clp_res_model, attr, self._shell_helper, "Azure Subscription ID"): + if self.does_attribute_match(self._clp_res_model, attr, "Azure Subscription ID"): os.environ["ARM_SUBSCRIPTION_ID"] = attr.Value - if self.does_attribute_match(self._clp_res_model, attr, self._shell_helper, "Azure Tenant ID"): - os.environ["Azure Tenant ID"] = attr.Value - if self.does_attribute_match(self._clp_res_model, attr, self._shell_helper, "Azure Application ID"): + if self.does_attribute_match(self._clp_res_model, attr, "Azure Tenant ID"): + os.environ["ARM_TENANT_ID"] = attr.Value + if self.does_attribute_match(self._clp_res_model, attr, "Azure Application ID"): os.environ["ARM_CLIENT_ID"] = attr.Value - if self.does_attribute_match(self._clp_res_model, attr, self._shell_helper, "Azure Application Key", True): + if self.does_attribute_match(self._clp_res_model, attr, "Azure Application Key"): os.environ["ARM_CLIENT_SECRET"] = self._shell_helper.api.DecryptPassword(attr.Value).Value diff --git a/package/cloudshell/iac/terraform/terraform_shell.py b/package/cloudshell/iac/terraform/terraform_shell.py index bbf7b36f..a71141b3 100644 --- a/package/cloudshell/iac/terraform/terraform_shell.py +++ b/package/cloudshell/iac/terraform/terraform_shell.py @@ -85,6 +85,9 @@ def _validate_remote_backend_or_existing_working_dir(self, sandbox_data_handler, not LocalDir.does_working_dir_exists(sandbox_data_handler.get_tf_working_dir()): self._handle_error_output(shell_helper, f"Missing local folder {sandbox_data_handler.get_tf_working_dir()}") + def get_tf_service(self): + return self._tf_service + @staticmethod def _destroy_passed(sandbox_data_handler): return sandbox_data_handler.get_status(DESTROY_STATUS) == DESTROY_PASSED diff --git a/package/requirements.txt b/package/requirements.txt index de572b4c..ac9aa898 100644 --- a/package/requirements.txt +++ b/package/requirements.txt @@ -1,5 +1,5 @@ cloudshell-shell-core==5.0.5 requests==2.25.1 -cloudshell-automation-api==2021.1.0.181140 +cloudshell-automation-api==2021.2.0.182230 retry==0.9.2 python-hcl2==2.0.1 diff --git a/package/test_requirements.txt b/package/test_requirements.txt index 4f700872..6d1b1bd7 100644 --- a/package/test_requirements.txt +++ b/package/test_requirements.txt @@ -1,2 +1,6 @@ mock==4.0.3 python-dotenv==0.18.0 +pathlib==1.0.1 + +#azure-common==1.1.27 +#azure-mgmt==5.0.0 \ No newline at end of file diff --git a/package/tests/.env.template.env b/package/tests/.env.template similarity index 100% rename from package/tests/.env.template.env rename to package/tests/.env.template diff --git a/package/tests/constants.py b/package/tests/constants.py index 410c0529..f41e9e21 100644 --- a/package/tests/constants.py +++ b/package/tests/constants.py @@ -1,5 +1,5 @@ -GITHUB_TF_PUBLIC_HELLO_URL_FILE = "https://github.com/QualiSystemsLab/CloudShell-Terraform-Shell/blob/master/package/tests/tests_helper_files/tf_modules/hello/hello.tf" -GITHUB_TF_PUBLIC_HELLO_URL_FOLDER = "https://github.com/QualiSystemsLab/CloudShell-Terraform-Shell/tree/master/package/tests/tests_helper_files/tf_modules/hello" +GH_TF_PUBLIC_HELLO_URL_FILE = "https://github.com/QualiSystemsLab/CloudShell-Terraform-Shell/blob/master/package/tests/tests_helper_files/tf_modules/hello/hello.tf" +GH_TF_PUBLIC_HELLO_URL_FOLDER = "https://github.com/QualiSystemsLab/CloudShell-Terraform-Shell/tree/master/package/tests/tests_helper_files/tf_modules/hello" TF_STATE_FILE_STRING = 'tf_state_file_string' @@ -8,3 +8,6 @@ TERRAFORM_EXEC_FILE = "terraform.exe" TF_HELLO_FILE = "hello.tf" SHELL_NAME = "Generic Terraform Service" + +MOCK_ALIAS1 = "MOCK_ALIAS1" +MOCK_ALIAS2 = "MOCK_ALIAS2" diff --git a/package/tests/integration_tests/INT-TEST-README.md b/package/tests/integration_tests/INT-TEST-README.md new file mode 100644 index 00000000..7253aa17 --- /dev/null +++ b/package/tests/integration_tests/INT-TEST-README.md @@ -0,0 +1,91 @@ +# Integration Tests for CloudShell-Terraform-Shell + +The purpose of this documentation section is to provide explanations regarding different aspects of the integrations tests usage and concepts used the making. + +## Content + +1) Structure +2) Helper Objects/Services +3) How to: + 1) Run + 2) Extend + +### Structure + +The Integration tests are devided into two types: +1) Mocked (mock_api_based_tests) +2) Real (real_api_based_tests) + +The Mocked (*1) tests will be using the Mock API (multiple patched functions of the API) during tests +for that it shall also use the MockAPIIntegrationData Object that inherits from BaseIntegrationData part of helper_objects\integration_context.py + +he Real (*2) tests will be using the Real API (Communicate with an actual live CloudShell server) during tests +* for that one would need to create a int_tests_secrets.env file based on the values mentioned in int_tests_secrets.template.env + in order for the test to work. +* In addition, one must also create a blueprint (and a derived sandbox) with two services (which the tests will use for running the tests) +for that it shall also use the RealAPIIntegrationData Object that inherits from BaseIntegrationData part of helper_objects\integration_context.py + +### Helper Objects/Services + +1) Helper Object (helper_objects) + 1) Environment Variables (helper_objects\env_vars.py) + * Used to hold generatl information regarding the Real reservation testing against + 2) Integration Context Data (helper_objects\integration_context.py) + * Used both by "Real" and "Mocked" in order to construct the data for the tests wether real or mocked +2) Helper Services (helper_services) + 1) Serice Attributes Factory (helper_services\service_attributes_factory.py) + * Used by "Mocked" tests to create empty attributes + +### How To + +#### Run +prerequisite: have a a unit tests plugin installed (e.g. unittests) + + 1) As IDEs have sometimes issues with importing packages, best way is to open the project from it`s "Package" as root dir + 2) For running real tests: + 1) Instantiate a CloudShell server + 2) Create a blueprint with two TF Services + 3) create a int_tests_secrets.env file and fill it with the variables (and their values) from int_tests_secrets.template.env + 3) For mock tests: + 1) either from IDE or CLI : python -m unittest discover -p "int_test_mock*.py" + +#### Extend + +Test cases part of int_test_mock_tf_execute_destroy.py have several sections: + +1) Patching (bypassing the use of real api / server requirements): + + For example: + + @patch('cloudshell.iac.terraform.services.tf_proc_exec.TfProcExec.can_destroy_run') + @patch('cloudshell.iac.terraform.terraform_shell.SandboxDataHandler') + @patch('cloudshell.iac.terraform.services.object_factory.CloudShellSessionContext') + +2) Making sure the patched functions/objects are initialized with the right values: + + For example: + + def test_execute_and_destroy_azure_vault(self, patch_api, patched_sbdata_handler, can_destroy_run): + + can_destroy_run.return_value = True + + patch_api.return_value.get_api.return_value = self.mock_api + + mock_sbdata_handler = Mock() + mock_sbdata_handler.get_tf_working_dir = self._get_mocked_tf_working_dir + mock_sbdata_handler.set_tf_working_dir = self._set_mocked_tf_working_dir + patched_sbdata_handler.return_value = mock_sbdata_handler + +3) the run_execute_and_destroy function that holds 3 parameters + what function needs to run before executing + * create such a function and modify the attributes values there, so it will match the test case requirements here + what function needs to run before destroying + The data used for the test (self.integration_data1) + + For example: + +self.run_execute_and_destroy( + pre_exec_function=self.pre_exec_azure_vault, + pre_destroy_function=self.pre_destroy, + integration_data=self.integration_data1 + ) \ No newline at end of file diff --git a/shells/generic_terraform_service/tests/integration_tests/constants.py b/package/tests/integration_tests/constants.py similarity index 65% rename from shells/generic_terraform_service/tests/integration_tests/constants.py rename to package/tests/integration_tests/constants.py index 36e27876..c7fd8d74 100644 --- a/shells/generic_terraform_service/tests/integration_tests/constants.py +++ b/package/tests/integration_tests/constants.py @@ -1,5 +1,7 @@ SHELL_NAME = "Generic Terraform Service" -UUID_ATTRIBUTE = f"{SHELL_NAME}.UUID" + +INT_TEST_TF_VER = "0.15.1" +INT_TEST_CLP_RESOURSE = "real_azure" class ATTRIBUTE_NAMES: @@ -17,3 +19,8 @@ class ATTRIBUTE_NAMES: BRANCH = "Branch" CLOUD_PROVIDER = "Cloud Provider" UUID = "UUID" + + +EXPECTED_VAULT_TF_OUTPUTS = "BLA1=bla1,BLA2=bla2" +EXPECTED_VAULT_TF_SENSETIVE_OUTPUTS_DEC = "SECRET_VALUE=test,SECRET_VALUE_2=my_secret" +EXPECTED_VAULT_TF_SENSETIVE_OUTPUTS_ENC = 'nwH0Qhxnn7ntDpWxsvol+89EPFFSvYE16gHjACzsQRDzvXXnNFc4WfYpgq5w1kw8' diff --git a/package/tests/integration_tests/helper_objects/integration_context.py b/package/tests/integration_tests/helper_objects/integration_context.py index 7cf6e317..01513859 100644 --- a/package/tests/integration_tests/helper_objects/integration_context.py +++ b/package/tests/integration_tests/helper_objects/integration_context.py @@ -1,51 +1,88 @@ from unittest import mock +from unittest.mock import Mock from cloudshell.api.cloudshell_api import CloudShellAPISession +from cloudshell.iac.terraform import TerraformShell, TerraformShellConfig from cloudshell.logging.qs_logger import get_qs_logger from cloudshell.shell.core.driver_context import ResourceCommandContext - +# from tests.integration_tests.helper_objects.env_vars import EnvVars from package.tests.integration_tests.helper_objects.env_vars import EnvVars +# from tests.integration_tests.helper_services.service_attributes_factory import ServiceAttributesMockBuilder +from package.tests.integration_tests.helper_services.service_attributes_factory import ServiceAttributesMockBuilder + +from abc import ABCMeta, abstractmethod + + +class BaseIntegrationData(metaclass=ABCMeta): + def __init__(self, service_name: str): + self._sb_service_alias = service_name + self._logger = get_qs_logger(log_group=service_name) + + self._build_context() + self._set_general_context_resource_data() + self._set_context() + self.create_tf_shell() + + @abstractmethod + def _set_context(self): + raise NotImplementedError() + + def _build_context(self): + self.context = mock.create_autospec(ResourceCommandContext) + self.context.connectivity = Mock() + + def _set_general_context_resource_data(self): + self.context.resource = Mock() + self.context.resource.attributes = dict() + self.context.resource.name = self._sb_service_alias + self.context.resource.model = 'Generic Terraform Service' + def create_tf_shell(self): + _config = TerraformShellConfig(write_sandbox_messages=True, update_live_status=True) + self.tf_shell = TerraformShell(self.context, self._logger, _config) -class IntegrationData(object): - def __init__(self): + +class RealAPIIntegrationData(BaseIntegrationData): + def __init__(self, service_name: str): self._env_vars = EnvVars() - self.real_api = CloudShellAPISession( + self.api = CloudShellAPISession( self._env_vars.cs_server, self._env_vars.cs_user, self._env_vars.cs_pass, self._env_vars.cs_domain ) - self._set_context() - self._logger = get_qs_logger(log_group=self.context.resource.name) - - # self._create_driver() + BaseIntegrationData.__init__(self, service_name) def _set_context(self): - self.context = mock.create_autospec(ResourceCommandContext) - self.context.connectivity = mock.MagicMock() self.context.connectivity.server_address = self._env_vars.cs_server - self.context.connectivity.admin_auth_token = self.real_api.authentication.xmlrpc_token - - self.context.resource = mock.MagicMock() - self.context.resource.attributes = dict() - self.context.resource.name = self._env_vars.sb_service_alias - self.context.resource.model = 'Generic Terraform Service' - self.set_context_resource_attributes() - - self.context.reservation = mock.MagicMock() + self.context.connectivity.admin_auth_token = self.api.authentication.xmlrpc_token + self.set_context_resource_attributes_from_cs() + self.context.reservation = Mock() self.context.reservation.reservation_id = self._env_vars.cs_res_id self.context.reservation.domain = self._env_vars.cs_domain - def set_context_resource_attributes(self): - services = self.real_api.GetReservationDetails(self._env_vars.cs_res_id).ReservationDescription.Services + def set_context_resource_attributes_from_cs(self, the_only_attribute_to_update: str = ""): + services = self.api.GetReservationDetails(self._env_vars.cs_res_id, disableCache=True) \ + .ReservationDescription.Services for service in services: - if service.Alias == self._env_vars.sb_service_alias: + if service.Alias == self._sb_service_alias: for attribute in service.Attributes: - self.context.resource.attributes[attribute.Name] = attribute.Value - ''' - def _create_driver(self) : - self.driver = GenericTerraformServiceDriver() - self.driver.initialize(self.context) - ''' + if the_only_attribute_to_update and attribute.Name == the_only_attribute_to_update: + self.context.resource.attributes[attribute.Name] = attribute.Value + return + elif not the_only_attribute_to_update: + self.context.resource.attributes[attribute.Name] = attribute.Value + + +class MockAPIIntegrationData(BaseIntegrationData): + def __init__(self, service_name: str, mock_api: Mock): + self.api = mock_api + self.api.authentication.xmlrpc_token = Mock() + + BaseIntegrationData.__init__(self, service_name) + + def _set_context(self): + self.context.resource.attributes = ServiceAttributesMockBuilder.create_empty_attributes() + self.context.reservation = Mock() + self.context.reservation.reservation_id = "mock_reservation_id" diff --git a/package/tests/integration_tests/helper_objects/mock_tests_data.py b/package/tests/integration_tests/helper_objects/mock_tests_data.py new file mode 100644 index 00000000..48a63123 --- /dev/null +++ b/package/tests/integration_tests/helper_objects/mock_tests_data.py @@ -0,0 +1,63 @@ +import os +from unittest.mock import Mock + +from package.tests.integration_tests.helper_objects.integration_context import MockAPIIntegrationData +from package.tests.integration_tests.helper_services.service_attributes_factory import ServiceAttributesMockBuilder + +from cloudshell.api.cloudshell_api import NameValuePair + + +class MockTestsData(object): + def __init__(self): + self.mock_api = Mock() + self.service1 = Mock() + self.service2 = Mock() + self._mocked_tf_working_dir = '' + self.service1.Alias = '' + self.service1.Attributes = None + self.service2.Alias = '' + self.service2.Attributes = None + + self._prepare_mock_services() + self._prepare_mock_api() + + def prepare_integration_data(self): + self.integration_data1 = MockAPIIntegrationData(self.service1.Alias, self.mock_api) + self.integration_data2 = MockAPIIntegrationData(self.service2.Alias, self.mock_api) + + def _prepare_mock_services(self): + self.service1 = Mock() + self.service1.Alias = "MOCK_ALIAS1" + self.service1.Attributes = ServiceAttributesMockBuilder.create_empty_attributes() + self.service2 = Mock() + self.service2.Alias = "MOCK_ALIAS2" + self.service2.Attributes = ServiceAttributesMockBuilder.create_empty_attributes() + + def _prepare_mock_api(self): + self.mock_api = Mock() + self.mock_api.DecryptPassword = _decrypt_password + self.mock_api.GetReservationDetails.return_value.ReservationDescription.Services = [self.service1, + self.service2] + + # self.mock_api.SetServiceAttributesValues = self._set_service_attributes_values + + self.mock_api.GetResourceDetails.return_value.ResourceFamilyName = 'Cloud Provider' + self.mock_api.GetResourceDetails.return_value.ResourceModelName = 'Microsoft Azure' + self.mock_api.GetResourceDetails.return_value.ResourceAttributes = [ + NameValuePair(Name="Azure Subscription ID", Value=os.environ.get("AZURE_SUBSCRIPTION_ID")), + NameValuePair(Name="Azure Tenant ID", Value=os.environ.get("AZURE_TENANT_ID")), + NameValuePair(Name="Azure Application ID", Value=os.environ.get("AZURE_APPLICATION_ID")), + NameValuePair(Name="Azure Application Key", Value=os.environ.get("AZURE_APPLICATION_KEY_DEC")) + ] + + def _get_mocked_tf_working_dir(self): + return self._mocked_tf_working_dir + + def _set_mocked_tf_working_dir(self, tf_working_dir: str): + self._mocked_tf_working_dir = tf_working_dir + + +def _decrypt_password(x): + result = Mock() + result.Value = x + return result diff --git a/package/tests/integration_tests/helper_objects/real_tests_data.py b/package/tests/integration_tests/helper_objects/real_tests_data.py new file mode 100644 index 00000000..b5c678e5 --- /dev/null +++ b/package/tests/integration_tests/helper_objects/real_tests_data.py @@ -0,0 +1,12 @@ +import os + +from package.tests.integration_tests.helper_objects.integration_context import RealAPIIntegrationData + + +class RealTestsData(object): + def __init__(self): + self.integration_data1 = RealAPIIntegrationData(os.environ.get("SB_SERVICE_ALIAS1")) + self.integration_data2 = RealAPIIntegrationData(os.environ.get("SB_SERVICE_ALIAS2")) + + def clear_sb_data(self): + self.integration_data1.api.ClearSandboxData(self.integration_data1.context.reservation.reservation_id) diff --git a/shells/backends/azure_tf_backend/tests/integration_tests/__init__.py b/package/tests/integration_tests/helper_services/__init__.py similarity index 100% rename from shells/backends/azure_tf_backend/tests/integration_tests/__init__.py rename to package/tests/integration_tests/helper_services/__init__.py diff --git a/package/tests/integration_tests/helper_services/azure_validator.py b/package/tests/integration_tests/helper_services/azure_validator.py new file mode 100644 index 00000000..269caee5 --- /dev/null +++ b/package/tests/integration_tests/helper_services/azure_validator.py @@ -0,0 +1,22 @@ +''' +import os +from azure.common.credentials import ServicePrincipalCredentials +from azure.mgmt.resource import ResourceManagementClient + + +class AzureValidator(object): + def __init__(self): + self._subscription_id = os.environ['AZURE_SUBSCRIPTION_ID'] + self._client_id = os.environ['AZURE_APPLICATION_ID'] + self._secret = os.environ['AZURE_APPLICATION_KEY_DEC'] + self._tenant = os.environ['AZURE_TENANT_ID'] + self._credentials = ServicePrincipalCredentials( + client_id=self._client_id, + secret=self._secret, + tenant=self._tenant + ) + self._client = ResourceManagementClient(self._credentials, self._subscription_id) + + for item in self._client.resource_groups.list(): + print(item) +''' diff --git a/package/tests/integration_tests/helper_services/mock_tests_prepper.py b/package/tests/integration_tests/helper_services/mock_tests_prepper.py new file mode 100644 index 00000000..2cdbebc5 --- /dev/null +++ b/package/tests/integration_tests/helper_services/mock_tests_prepper.py @@ -0,0 +1,128 @@ +import os +from unittest.mock import Mock + +from package.tests.integration_tests.helper_objects.integration_context import MockAPIIntegrationData +from package.tests.integration_tests.constants import SHELL_NAME, ATTRIBUTE_NAMES, INT_TEST_TF_VER, \ + INT_TEST_CLP_RESOURSE +from package.tests.integration_tests.helper_objects.mock_tests_data import MockTestsData + + +def pre_exec_azure_vault(mock_tests_data: MockTestsData, azure_vault_url: str): + set_attribute_on_mock_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.TF_INPUTS}", + os.environ.get("AZUREAPP_TF_INPUTS"), + mock_tests_data.integration_data1 + ) + set_attribute_on_mock_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.GITHUB_TERRAFORM_MODULE_URL}", + azure_vault_url, + mock_tests_data.integration_data1 + ) + set_attribute_on_mock_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.GITHUB_TOKEN}", + os.environ.get("GH_TOKEN_DEC"), + mock_tests_data.integration_data1 + ) + set_attribute_on_mock_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.TERRAFORM_VERSION}", + INT_TEST_TF_VER, + mock_tests_data.integration_data1 + ) + set_attribute_on_mock_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.CLOUD_PROVIDER}", + INT_TEST_CLP_RESOURSE, + mock_tests_data.integration_data1 + ) + prepare_service1_before_execute(mock_tests_data, mock_tests_data.integration_data1) + + +def pre_exec_azure_mssql(mock_tests_data: MockTestsData, + integration_data: MockAPIIntegrationData): + set_attribute_on_mock_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.TF_INPUTS}", + os.environ.get("AZUREMSSQL_TF_INPUTS"), + integration_data + ) + set_attribute_on_mock_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.GITHUB_TERRAFORM_MODULE_URL}", + os.environ.get("GH_TF_PRIVATE_AZUREMSSQL_URL"), + integration_data + ) + set_attribute_on_mock_service( + f"{SHELL_NAME}.UUID", + os.environ.get(""), + integration_data + ) + prepare_service1_before_execute(mock_tests_data, mock_tests_data.integration_data1) + + +def pre_exec_azure_vault_with_remote_access_key_based(mock_tests_data: MockTestsData, + integration_data: MockAPIIntegrationData): + pre_exec_azure_vault(mock_tests_data) + set_attribute_on_mock_service( + f"{SHELL_NAME}.Remote State Provider", + os.environ.get("REMOTE_STATE_PROVIDER_ACCESS_KEY"), + integration_data + ) + + +def pre_exec_azure_vault_with_remote_cloud_cred_based(mock_tests_data: MockTestsData, + integration_data: MockAPIIntegrationData): + pre_exec_azure_vault(mock_tests_data) + set_attribute_on_mock_service( + f"{SHELL_NAME}.Remote State Provider", + os.environ.get("REMOTE_STATE_PROVIDER_CLOUD_CRED"), + integration_data + ) + + +def pre_exec_azure_vault_with_remote_invalid_nonexistent(mock_tests_data: MockTestsData, + integration_data: MockAPIIntegrationData): + pre_exec_azure_vault(mock_tests_data) + set_attribute_on_mock_service( + f"{SHELL_NAME}.Remote State Provider", + os.environ.get("REMOTE_STATE_PROVIDER_INVALID_NO_RESOURCE"), + integration_data + ) + + +def pre_exec_azure_vault_with_remote_invalid_wrong(mock_tests_data: MockTestsData, + integration_data: MockAPIIntegrationData): + pre_exec_azure_vault(mock_tests_data) + set_attribute_on_mock_service( + f"{SHELL_NAME}.Remote State Provider", + os.environ.get("REMOTE_STATE_PROVIDER_INVALID_WRONG_RESOURCE"), + integration_data + ) + + +def pre_exec_azure_vault_without_remote(mock_tests_data: MockTestsData, + integration_data: MockAPIIntegrationData): + pre_exec_azure_vault(mock_tests_data) + set_attribute_on_mock_service( + f"{SHELL_NAME}.Remote State Provider", + os.environ.get(""), + integration_data + ) + + +def pre_destroy(integration_data: MockAPIIntegrationData): + # As UUID has been created and SB data now contains UUID and Status we must update context so destroy can run + for attribute in integration_data.context.resource.attributes: + if attribute.Name == f"{SHELL_NAME}.{ATTRIBUTE_NAMES.UUID}": + attribute.Value = integration_data.tf_shell._tf_service.attributes[f"{SHELL_NAME}.{ATTRIBUTE_NAMES.UUID}"] + + +def prepare_service1_before_execute(mock_tests_data: MockTestsData, integration_data): + service1 = Mock() + service1.Alias = integration_data.context.resource.name + service1.Attributes = integration_data.context.resource.attributes + mock_tests_data.mock_api.GetReservationDetails.return_value.ReservationDescription.Services = [service1] + integration_data.create_tf_shell() + + +def set_attribute_on_mock_service(attr_name: str, attr_value: str, integration_data: MockAPIIntegrationData): + for attribute in integration_data.context.resource.attributes: + if attribute.Name == attr_name: + attribute.Value = attr_value + return diff --git a/package/tests/integration_tests/helper_services/real_tests_prepper.py b/package/tests/integration_tests/helper_services/real_tests_prepper.py new file mode 100644 index 00000000..205c1acf --- /dev/null +++ b/package/tests/integration_tests/helper_services/real_tests_prepper.py @@ -0,0 +1,100 @@ +import os + +from cloudshell.api.cloudshell_api import AttributeNameValue + +from package.tests.integration_tests.helper_objects.integration_context import RealAPIIntegrationData +from package.tests.integration_tests.constants import SHELL_NAME, ATTRIBUTE_NAMES, INT_TEST_TF_VER + + +def pre_exec_azure_vault(integration_data: RealAPIIntegrationData): + set_attribute_on_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.TF_INPUTS}", + os.environ.get("AZUREAPP_TF_INPUTS"), + integration_data + ) + set_attribute_on_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.GITHUB_TERRAFORM_MODULE_URL}", + os.environ.get("GITHUB_TF_PRIVATE_AZUREAPP_URL"), + integration_data + ) + set_attribute_on_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.TERRAFORM_VERSION}", + INT_TEST_TF_VER, + integration_data + ) + set_attribute_on_service( + f"{SHELL_NAME}.UUID", + os.environ.get(""), + integration_data + ) + + +def pre_exec_azure_vault_with_remote_access_key_based(integration_data: RealAPIIntegrationData): + pre_exec_azure_vault(integration_data) + set_attribute_on_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.REMOTE_STATE_PROVIDER}", + os.environ.get("REMOTE_STATE_PROVIDER_ACCESS_KEY"), + integration_data + ) + + +def pre_exec_azure_vault_with_remote_cloud_cred_based(integration_data: RealAPIIntegrationData): + pre_exec_azure_vault(integration_data) + set_attribute_on_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.REMOTE_STATE_PROVIDER}", + os.environ.get("REMOTE_STATE_PROVIDER_CLOUD_CRED"), + integration_data + ) + + +def pre_exec_azure_vault_with_remote_invalid_wrong(integration_data: RealAPIIntegrationData): + pre_exec_azure_vault(integration_data) + set_attribute_on_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.REMOTE_STATE_PROVIDER}", + os.environ.get("REMOTE_STATE_PROVIDER_INVALID_WRONG_RESOURCE"), + integration_data + ) + + +def pre_exec_azure_vault_with_remote_invalid_nonexistent(integration_data: RealAPIIntegrationData): + pre_exec_azure_vault(integration_data) + set_attribute_on_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.REMOTE_STATE_PROVIDER}", + os.environ.get("REMOTE_STATE_PROVIDER_INVALID_NO_RESOURCE"), + integration_data + ) + + +def pre_exec_azure_mssql(integration_data: RealAPIIntegrationData): + set_attribute_on_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.TF_INPUTS}", + os.environ.get("AZUREMSSQL_TF_INPUTS"), + integration_data + ) + set_attribute_on_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.GITHUB_TERRAFORM_MODULE_URL}", + os.environ.get("GITHUB_TF_PRIVATE_AZUREMSSQL_URL"), + integration_data + ) + set_attribute_on_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.UUID}", + os.environ.get(""), + integration_data + ) + + +def post_vault_cleanup(integration_data: RealAPIIntegrationData): + set_attribute_on_service( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.REMOTE_STATE_PROVIDER}", + "", + integration_data + ) + + +def set_attribute_on_service(attr_name: str, attr_value: str, integration_data: RealAPIIntegrationData): + attr_req = [AttributeNameValue(attr_name, attr_value)] + integration_data.api.SetServiceAttributesValues( + integration_data.context.reservation.reservation_id, + integration_data.context.resource.name, + attr_req + ) diff --git a/shells/generic_terraform_service/tests/integration_tests/helper_services/service_attributes_factory.py b/package/tests/integration_tests/helper_services/service_attributes_factory.py similarity index 82% rename from shells/generic_terraform_service/tests/integration_tests/helper_services/service_attributes_factory.py rename to package/tests/integration_tests/helper_services/service_attributes_factory.py index 1803bc72..7ce57dca 100644 --- a/shells/generic_terraform_service/tests/integration_tests/helper_services/service_attributes_factory.py +++ b/package/tests/integration_tests/helper_services/service_attributes_factory.py @@ -1,11 +1,14 @@ +from typing import List + from cloudshell.api.cloudshell_api import NameValuePair -from tests.integration_tests.constants import SHELL_NAME, ATTRIBUTE_NAMES +# from tests.integration_tests.constants import SHELL_NAME, ATTRIBUTE_NAMES +from package.tests.integration_tests.constants import SHELL_NAME, ATTRIBUTE_NAMES -class ServiceAttributesFactory: +class ServiceAttributesMockBuilder: @staticmethod - def create_empty_attributes() -> list[dict]: + def create_empty_attributes() -> List[dict]: attributes = [ NameValuePair(Name=f"{SHELL_NAME}.{ATTRIBUTE_NAMES.REMOTE_STATE_PROVIDER}", Value=""), NameValuePair(Name=f"{SHELL_NAME}.{ATTRIBUTE_NAMES.BRANCH}", Value=""), diff --git a/package/tests/integration_tests/int_tests.env b/package/tests/integration_tests/int_tests.env new file mode 100644 index 00000000..ab085a06 --- /dev/null +++ b/package/tests/integration_tests/int_tests.env @@ -0,0 +1,20 @@ +# The following values are used both for local runs and Github CI + +AZUREAPP_TF_INPUTS = "KEYVAULT_NAME=tf-tests-keyvault,KEYVAULT_RG=cs-tf-tests-rg,SECRET_NAME=test" +GITHUB_TF_PRIVATE_AZUREAPP_URL = "https://github.com/alexazarh/Colony-experiments/tree/master/terraform/azure-vault" + +GITHUB_TF_PUBLIC_AZUREAPP_URL_PRE = "https://github.com/QualiSystemsLab/CloudShell-Terraform-Shell/tree/" +GITHUB_TF_PUBLIC_AZUREAPP_URL_POST = "/package/tests/tests_helper_files/tf_modules/azure-vault" + + +REMOTE_STATE_PROVIDER_ACCESS_KEY = "azure_tf_backend1" +REMOTE_STATE_PROVIDER_CLOUD_CRED = "azure_tf_backend2" +REMOTE_STATE_PROVIDER_INVALID_NO_RESOURCE = "azure_tf_invalid" +REMOTE_STATE_PROVIDER_INVALID_WRONG_RESOURCE = "mock_azure" + +# CurrentBranch id enviornment variable checkin Github documentaion to implement below +# if enviornment varialbe doesnt exist use a default (do this when loading the .env) +#GITHUB_TF_PRIVATE_AZUREAPP_URL = "https://github.com/alexazarh/Colony-experiments/tree/{CURRENTBRANCH2}/terraform/azure-vault" + + +# GH_TF_PRIVATE_AZUREAPP_URL = "https://github.com/QualiSystemsLab/CloudShell-Terraform-Shell/tree/master/package/tests/integration_tests/terraform_test_modules/azure-vault" \ No newline at end of file diff --git a/package/tests/integration_tests/int_tests_secrets.template.env b/package/tests/integration_tests/int_tests_secrets.template.env new file mode 100644 index 00000000..c8e3533b --- /dev/null +++ b/package/tests/integration_tests/int_tests_secrets.template.env @@ -0,0 +1,13 @@ +# The following values are used only for local runs and need to be filled to run the integrations tests +# "_DEC" is a short for DECrypted +SB_SERVICE_ALIAS1 = "" +SB_SERVICE_ALIAS2 = "" + +AZURE_APPLICATION_KEY_DEC = "" +GH_TOKEN_DEC = "" + +AZURE_SUBSCRIPTION_ID = "" +AZURE_TENANT_ID = "" +AZURE_APPLICATION_ID = "" + +CURRENT_BRANCH = "" \ No newline at end of file diff --git a/shells/backends/azure_tf_backend/tests/integration_tests/helper_objects/__init__.py b/package/tests/integration_tests/mock_api_based_tests/__init__.py similarity index 100% rename from shells/backends/azure_tf_backend/tests/integration_tests/helper_objects/__init__.py rename to package/tests/integration_tests/mock_api_based_tests/__init__.py diff --git a/package/tests/integration_tests/mock_api_based_tests/int_test_mock_downloader.py b/package/tests/integration_tests/mock_api_based_tests/int_test_mock_downloader.py new file mode 100644 index 00000000..f4427974 --- /dev/null +++ b/package/tests/integration_tests/mock_api_based_tests/int_test_mock_downloader.py @@ -0,0 +1,111 @@ +import os +from unittest import TestCase +from pathlib import Path + +from dotenv import load_dotenv + +from unittest.mock import patch, Mock + +from cloudshell.iac.terraform.downloaders.downloader import Downloader +from cloudshell.iac.terraform.services.live_status_updater import LiveStatusUpdater +from cloudshell.iac.terraform.services.sandbox_messages import SandboxMessagesService +from shells.generic_terraform_service.src.data_model import GenericTerraformService +from cloudshell.iac.terraform.models.shell_helper import ShellHelperObject +from package.tests.constants import GH_TF_PUBLIC_HELLO_URL_FILE, GH_TF_PUBLIC_HELLO_URL_FOLDER, TERRAFORM_EXEC_FILE, \ + SHELL_NAME, TF_HELLO_FILE, MOCK_ALIAS2, MOCK_ALIAS1 + +from package.tests.integration_tests.helper_objects.integration_context import MockAPIIntegrationData + +from cloudshell.iac.terraform.tagging.tags import TagsManager +from cloudshell.iac.terraform.services.svc_attribute_handler import ServiceAttrHandler +from package.tests.integration_tests.helper_services.service_attributes_factory import ServiceAttributesMockBuilder + + +class TestTerraformDownloader(TestCase): + @classmethod + def setUpClass(self): + load_dotenv(Path('../int_tests.env')) + if os.path.isfile(Path('../int_tests_secrets.env')): + load_dotenv(Path('../int_tests_secrets.env')) + + @patch('cloudshell.iac.terraform.services.object_factory.CloudShellSessionContext') + def setUp(self, patched_api) -> None: + self._prepare_mock_api() + + self._mocked_tf_working_dir = '' + self._prepare_mock_services() + self.mock_api.GetReservationDetails.return_value.ReservationDescription.Services = [self._service1, + self._service2] + self._prepare_integration_data() + + service_resource = GenericTerraformService.create_from_context(self.integration_data1.context) + + sandbox_messages = SandboxMessagesService( + self.integration_data1.api, + self.integration_data1.context.reservation.reservation_id, + self.integration_data1.context.resource.name, + False + ) + + live_status_updater = LiveStatusUpdater( + self.integration_data1.api, + self.integration_data1.context.reservation.reservation_id, + False + ) + + default_tags = TagsManager(self.integration_data1.context.reservation) + + attr_handler = ServiceAttrHandler(service_resource) + + self._driver_helper = ShellHelperObject( + self.integration_data1.api, + self.integration_data1.context.reservation.reservation_id, + service_resource, + self.integration_data1._logger, + sandbox_messages, + live_status_updater, + attr_handler, + default_tags + ) + + def _prepare_integration_data(self): + self.integration_data1 = MockAPIIntegrationData(self._service1.Alias, self.mock_api) + self.integration_data2 = MockAPIIntegrationData(self._service2.Alias, self.mock_api) + + def _prepare_mock_api(self): + self.mock_api = Mock() + + def _prepare_mock_services(self): + self._service1 = Mock() + self._service1.Alias = MOCK_ALIAS1 + self._service1.Attributes = ServiceAttributesMockBuilder.create_empty_attributes() + self._service2 = Mock() + self._service2.Alias = MOCK_ALIAS2 + self._service2.Attributes = ServiceAttributesMockBuilder.create_empty_attributes() + + def _test_download_terraform_module(self, url: str, branch: str): + self.integration_data1.context.resource.attributes[ + f"{SHELL_NAME}.Github Terraform Module URL"] = url + self._driver_helper.tf_service.attributes[ + f"{SHELL_NAME}.Github Terraform Module URL"] = url + self.integration_data1.context.resource.attributes[ + f"{SHELL_NAME}.Branch"] = branch + self._driver_helper.tf_service.attributes[ + f"{SHELL_NAME}.Branch"] = branch + + downloader = Downloader(self._driver_helper) + tf_workingdir = downloader.download_terraform_module() + self.assertTrue(os.path.exists(os.path.join(tf_workingdir, TF_HELLO_FILE))) + + def test_public_and_private_hello_dl(self): + self._test_download_terraform_module(GH_TF_PUBLIC_HELLO_URL_FILE, "") + self._test_download_terraform_module(os.environ.get("GITHUB_TF_PRIVATE_HELLO_URL"), "") + self._test_download_terraform_module(GH_TF_PUBLIC_HELLO_URL_FOLDER, "") + + def test_download_terraform_executable(self): + downloader = Downloader(self._driver_helper) + tf_workingdir = downloader.download_terraform_module() + downloader.download_terraform_executable(tf_workingdir) + + self.assertTrue(os.path.exists(os.path.join(tf_workingdir, TERRAFORM_EXEC_FILE))) + self.assertTrue(os.access(os.path.join(tf_workingdir, TERRAFORM_EXEC_FILE), os.X_OK)) diff --git a/package/tests/integration_tests/mock_api_based_tests/int_test_mock_tf_execute_destroy.py b/package/tests/integration_tests/mock_api_based_tests/int_test_mock_tf_execute_destroy.py new file mode 100644 index 00000000..632d9c52 --- /dev/null +++ b/package/tests/integration_tests/mock_api_based_tests/int_test_mock_tf_execute_destroy.py @@ -0,0 +1,76 @@ +from unittest.mock import patch, Mock +from dotenv import load_dotenv + +import os +from unittest import TestCase +from pathlib import Path + +from package.tests.integration_tests.helper_services.mock_tests_prepper import pre_exec_azure_vault, pre_destroy +from package.tests.integration_tests.helper_objects.mock_tests_data import MockTestsData + +from package.tests.constants import MOCK_ALIAS1 + +from package.tests.integration_tests.constants import SHELL_NAME, ATTRIBUTE_NAMES, EXPECTED_VAULT_TF_OUTPUTS, \ + EXPECTED_VAULT_TF_SENSETIVE_OUTPUTS_DEC + + +class TestMockTerraformExecuteDestroy(TestCase): + # region Test Setup + + @classmethod + def setUpClass(self): + print(os.getcwd()) + load_dotenv(Path('../int_tests.env')) + if os.path.isfile(Path('../int_tests_secrets.env')): + load_dotenv(Path('../int_tests_secrets.env')) + + @patch('cloudshell.iac.terraform.services.object_factory.CloudShellSessionContext') + def setUp(self, patched_api) -> None: + + self.test_data_object = MockTestsData() + patched_api.return_value.get_api.return_value = self.test_data_object.mock_api + self.test_data_object.prepare_integration_data() + + # endregion + + # region Test Cases + + @patch('cloudshell.iac.terraform.services.tf_proc_exec.TfProcExec.can_destroy_run') + @patch('cloudshell.iac.terraform.terraform_shell.SandboxDataHandler') + @patch('cloudshell.iac.terraform.services.object_factory.CloudShellSessionContext') + def test_execute_and_destroy_azure_vault(self, patch_api, patched_sbdata_handler, can_destroy_run): + # arrange + can_destroy_run.return_value = True + patch_api.return_value.get_api.return_value = self.test_data_object.mock_api + mock_sbdata_handler = Mock() + mock_sbdata_handler.get_tf_working_dir = self.test_data_object._get_mocked_tf_working_dir + mock_sbdata_handler.set_tf_working_dir = self.test_data_object._set_mocked_tf_working_dir + patched_sbdata_handler.return_value = mock_sbdata_handler + + azure_vault_private_url = os.environ.get("GITHUB_TF_PRIVATE_AZUREAPP_URL") + azure_vault_public_url = f'{os.environ.get("GITHUB_TF_PUBLIC_AZUREAPP_URL_PRE")}' \ + f'{os.environ.get("CURRENT_BRANCH")}' \ + f'{os.environ.get("GITHUB_TF_PUBLIC_AZUREAPP_URL_POST")}' + for url in [azure_vault_private_url, azure_vault_public_url]: + pre_exec_azure_vault(self.test_data_object, url) + self.test_data_object.integration_data1.tf_shell.execute_terraform() + self.assertTrue(self.are_vault_tf_outputs_correct()) + pre_destroy(self.test_data_object.integration_data1) + self.test_data_object.integration_data1.tf_shell.destroy_terraform() + + def are_vault_tf_outputs_correct(self): + tf_outputs_correct = False + tf_sensitive_outputs_correct = False + try: + for call in self.test_data_object.mock_api.SetServiceAttributesValues.call_args_list: + if call.args[1] == MOCK_ALIAS1: + for attribute_name_value in call.args[2]: + if attribute_name_value.Name == f"{SHELL_NAME}.{ATTRIBUTE_NAMES.TF_OUTPUTS}" and \ + attribute_name_value.Value == EXPECTED_VAULT_TF_OUTPUTS: + tf_outputs_correct = True + if attribute_name_value.Name == f"{SHELL_NAME}.{ATTRIBUTE_NAMES.TF_SENSIITVE_OUTPUTS}" and \ + attribute_name_value.Value == EXPECTED_VAULT_TF_SENSETIVE_OUTPUTS_DEC: + tf_sensitive_outputs_correct = True + except Exception as e: + raise + return tf_outputs_correct and tf_sensitive_outputs_correct diff --git a/shells/generic_terraform_service/tests/integration_tests/__init__.py b/package/tests/integration_tests/real_api_based_tests/__init__.py similarity index 100% rename from shells/generic_terraform_service/tests/integration_tests/__init__.py rename to package/tests/integration_tests/real_api_based_tests/__init__.py diff --git a/package/tests/integration_tests/int_test_downloader.py b/package/tests/integration_tests/real_api_based_tests/int_test_real_downloader.py similarity index 59% rename from package/tests/integration_tests/int_test_downloader.py rename to package/tests/integration_tests/real_api_based_tests/int_test_real_downloader.py index 7fe995fa..bbff9956 100644 --- a/package/tests/integration_tests/int_test_downloader.py +++ b/package/tests/integration_tests/real_api_based_tests/int_test_real_downloader.py @@ -1,46 +1,58 @@ import os from unittest import TestCase +from pathlib import Path + +from dotenv import load_dotenv from cloudshell.iac.terraform.downloaders.downloader import Downloader from cloudshell.iac.terraform.services.live_status_updater import LiveStatusUpdater from cloudshell.iac.terraform.services.sandbox_messages import SandboxMessagesService from shells.generic_terraform_service.src.data_model import GenericTerraformService from cloudshell.iac.terraform.models.shell_helper import ShellHelperObject -from tests.constants import GITHUB_TF_PUBLIC_HELLO_URL_FILE, GITHUB_TF_PUBLIC_HELLO_URL_FOLDER, TERRAFORM_EXEC_FILE, \ +from package.tests.constants import GH_TF_PUBLIC_HELLO_URL_FILE, GH_TF_PUBLIC_HELLO_URL_FOLDER, TERRAFORM_EXEC_FILE, \ SHELL_NAME, TF_HELLO_FILE -from tests.integration_tests.helper_objects.integration_context import IntegrationData +# from package.tests.integration_tests.helper_objects.downloader_integration_context import IntegrationData +from package.tests.integration_tests.helper_objects.integration_context import RealAPIIntegrationData + from cloudshell.iac.terraform.tagging.tags import TagsManager from cloudshell.iac.terraform.services.svc_attribute_handler import ServiceAttrHandler class TestTerraformDownloader(TestCase): + @classmethod + def setUpClass(self): + load_dotenv(Path('../int_tests.env')) + if os.path.isfile(Path('../int_tests_secrets.env')): + load_dotenv(Path('../int_tests_secrets.env')) + def setUp(self) -> None: - self.integration_data = IntegrationData() + self.integration_data1 = RealAPIIntegrationData(os.environ.get("SB_SERVICE_ALIAS1")) + self.integration_data2 = RealAPIIntegrationData(os.environ.get("SB_SERVICE_ALIAS2")) - service_resource = GenericTerraformService.create_from_context(self.integration_data.context) + service_resource = GenericTerraformService.create_from_context(self.integration_data1.context) sandbox_messages = SandboxMessagesService( - self.integration_data.real_api, - self.integration_data.context.reservation.reservation_id, - self.integration_data.context.resource.name, + self.integration_data1.api, + self.integration_data1.context.reservation.reservation_id, + self.integration_data1.context.resource.name, False ) live_status_updater = LiveStatusUpdater( - self.integration_data.real_api, - self.integration_data.context.reservation.reservation_id, + self.integration_data1.api, + self.integration_data1.context.reservation.reservation_id, False ) - default_tags = TagsManager(self.integration_data.context.reservation) + default_tags = TagsManager(self.integration_data1.context.reservation) attr_handler = ServiceAttrHandler(service_resource) self._driver_helper = ShellHelperObject( - self.integration_data.real_api, - self.integration_data.context.reservation.reservation_id, + self.integration_data1.api, + self.integration_data1.context.reservation.reservation_id, service_resource, - self.integration_data._logger, + self.integration_data1._logger, sandbox_messages, live_status_updater, attr_handler, @@ -48,11 +60,11 @@ def setUp(self) -> None: ) def _test_download_terraform_module(self, url: str, branch: str): - self.integration_data.context.resource.attributes[ + self.integration_data1.context.resource.attributes[ f"{SHELL_NAME}.Github Terraform Module URL"] = url self._driver_helper.tf_service.attributes[ f"{SHELL_NAME}.Github Terraform Module URL"] = url - self.integration_data.context.resource.attributes[ + self.integration_data1.context.resource.attributes[ f"{SHELL_NAME}.Branch"] = branch self._driver_helper.tf_service.attributes[ f"{SHELL_NAME}.Branch"] = branch @@ -62,9 +74,9 @@ def _test_download_terraform_module(self, url: str, branch: str): self.assertTrue(os.path.exists(os.path.join(tf_workingdir, TF_HELLO_FILE))) def test_public_and_private_hello_dl(self): - self._test_download_terraform_module(GITHUB_TF_PUBLIC_HELLO_URL_FILE, "") + self._test_download_terraform_module(GH_TF_PUBLIC_HELLO_URL_FILE, "") self._test_download_terraform_module(os.environ.get("GITHUB_TF_PRIVATE_HELLO_URL"), "") - self._test_download_terraform_module(GITHUB_TF_PUBLIC_HELLO_URL_FOLDER, "") + self._test_download_terraform_module(GH_TF_PUBLIC_HELLO_URL_FOLDER, "") def test_download_terraform_executable(self): downloader = Downloader(self._driver_helper) diff --git a/package/tests/integration_tests/real_api_based_tests/int_test_real_tf_execute_destroy.py b/package/tests/integration_tests/real_api_based_tests/int_test_real_tf_execute_destroy.py new file mode 100644 index 00000000..066d7777 --- /dev/null +++ b/package/tests/integration_tests/real_api_based_tests/int_test_real_tf_execute_destroy.py @@ -0,0 +1,111 @@ +from dotenv import load_dotenv + +from package.tests.integration_tests.constants import SHELL_NAME, ATTRIBUTE_NAMES, EXPECTED_VAULT_TF_OUTPUTS, \ + EXPECTED_VAULT_TF_SENSETIVE_OUTPUTS_ENC +from package.tests.integration_tests.helper_objects.real_tests_data import RealTestsData +from package.tests.integration_tests.helper_services.real_tests_prepper import pre_exec_azure_vault, \ + pre_exec_azure_vault_with_remote_access_key_based, pre_exec_azure_vault_with_remote_cloud_cred_based, \ + pre_exec_azure_vault_with_remote_invalid_nonexistent, pre_exec_azure_vault_with_remote_invalid_wrong, \ + post_vault_cleanup + +import os +from pathlib import Path +from unittest import TestCase + +from package.tests.integration_tests.helper_objects.integration_context import RealAPIIntegrationData + + +class TestRealTerraformExecuteDestroy(TestCase): + @classmethod + def setUpClass(self): + load_dotenv(Path('../int_tests.env')) + if os.path.isfile(Path('../int_tests_secrets.env')): + load_dotenv(Path('../int_tests_secrets.env')) + + def setUp(self) -> None: + self.test_data_object = RealTestsData() + + '''------------------------------ Test Cases ---------------------------------''' + + # To be used with any preconfigured attributes set on the service in real CS instance + def test_execute_and_destroy(self): + self.test_data_object.clear_sb_data() + self.test_data_object.integration_data1.tf_shell.execute_terraform() + self.test_data_object.integration_data1.set_context_resource_attributes_from_cs( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.UUID}") + self.test_data_object.integration_data1.tf_shell.destroy_terraform() + + def est_execute_and_destroy_azure_vault(self): + self.test_data_object.clear_sb_data() + pre_exec_azure_vault(self.test_data_object.integration_data1) + self.test_data_object.integration_data1.tf_shell.execute_terraform() + self.test_data_object.integration_data1.set_context_resource_attributes_from_cs( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.UUID}") + self.assertTrue(self.are_vault_tf_outputs_correct(self.test_data_object.integration_data1)) + self.test_data_object.integration_data1.tf_shell.destroy_terraform() + + ''' + def test_execute_dual_mssql(self): + self.clear_sb_data() + try: + self.run_execute(pre_exec_function=self.pre_exec_azure_mssql, integration_data=self.test_data_object.integration_data1) + except Exception as e: + pass + try: + self.run_execute(pre_exec_function=self.pre_exec_azure_mssql, integration_data=self.test_data_object.integration_data2) + except Exception as e: + print("Error") + pass + self.run_destroy(pre_destroy_function=self.pre_destroy, integration_data=self.test_data_object.integration_data1) + self.run_execute(pre_exec_function=self.pre_exec_azure_mssql, integration_data=self.test_data_object.integration_data2) + ''' + + def test_execute_and_destroy_azure_vault_with_remote_access_key_based(self): + self.test_data_object.clear_sb_data() + pre_exec_azure_vault_with_remote_access_key_based(self.test_data_object.integration_data1) + self.test_data_object.integration_data1.tf_shell.execute_terraform() + self.test_data_object.integration_data1.set_context_resource_attributes_from_cs( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.UUID}") + self.assertTrue(self.are_vault_tf_outputs_correct(self.test_data_object.integration_data1)) + self.test_data_object.integration_data1.tf_shell.destroy_terraform() + post_vault_cleanup(self.test_data_object.integration_data1) + + def test_execute_and_destroy_azure_vault_with_remote_access_cloud_cred_based(self): + self.test_data_object.clear_sb_data() + pre_exec_azure_vault_with_remote_cloud_cred_based(self.test_data_object.integration_data1) + self.test_data_object.integration_data1.tf_shell.execute_terraform() + self.test_data_object.integration_data1.set_context_resource_attributes_from_cs( + f"{SHELL_NAME}.{ATTRIBUTE_NAMES.UUID}") + self.assertTrue(self.are_vault_tf_outputs_correct(self.test_data_object.integration_data1)) + self.test_data_object.integration_data1.tf_shell.destroy_terraform() + post_vault_cleanup(self.test_data_object.integration_data1) + + def test_execute_azure_vault_with_remote_invalid_nonexistent(self): + self.test_data_object.clear_sb_data() + pre_exec_azure_vault_with_remote_invalid_nonexistent(self.test_data_object.integration_data1) + with self.assertRaises(ValueError): + self.test_data_object.integration_data1.tf_shell.execute_terraform() + post_vault_cleanup(self.test_data_object.integration_data1) + print("") + + def test_execute_and_destroy_azure_vault_with_remote_invalid_wrong(self): + self.test_data_object.clear_sb_data() + pre_exec_azure_vault_with_remote_invalid_wrong(self.test_data_object.integration_data1) + with self.assertRaises(ValueError): + self.test_data_object.integration_data1.tf_shell.execute_terraform() + post_vault_cleanup(self.test_data_object.integration_data1) + print("") + + @staticmethod + def are_vault_tf_outputs_correct(integration_data: RealAPIIntegrationData): + tf_outputs_correct = False + tf_sensitive_outputs_correct = False + attrs = integration_data.tf_shell.get_tf_service().attributes + try: + if attrs[f"{SHELL_NAME}.{ATTRIBUTE_NAMES.TF_OUTPUTS}"] == EXPECTED_VAULT_TF_OUTPUTS: + tf_outputs_correct = True + if attrs[f"{SHELL_NAME}.{ATTRIBUTE_NAMES.TF_SENSIITVE_OUTPUTS}"] == EXPECTED_VAULT_TF_SENSETIVE_OUTPUTS_ENC: + tf_sensitive_outputs_correct = True + except Exception as e: + raise + return tf_outputs_correct and tf_sensitive_outputs_correct diff --git a/package/tests/integration_tests/tf_modules/azure-paas-mssql/outputs.tf b/package/tests/integration_tests/tf_modules/azure-paas-mssql/outputs.tf new file mode 100644 index 00000000..de1d2053 --- /dev/null +++ b/package/tests/integration_tests/tf_modules/azure-paas-mssql/outputs.tf @@ -0,0 +1,9 @@ +output "DB_HOSTNAME" { + value = azurerm_mssql_server.default.fully_qualified_domain_name +} +output "DB_USER" { + value = var.DB_USERNAME +} +output "DB_NAME" { + value = var.DB_NAME +} diff --git a/package/tests/integration_tests/tf_modules/azure-vault/outputs.tf b/package/tests/integration_tests/tf_modules/azure-vault/outputs.tf new file mode 100644 index 00000000..6530e4b1 --- /dev/null +++ b/package/tests/integration_tests/tf_modules/azure-vault/outputs.tf @@ -0,0 +1,16 @@ +output "SECRET_VALUE"{ + value = data.azurerm_key_vault_secret.keyvault_get.value + sensitive = true +} + +### mock outputs for testing +output "SECRET_VALUE_2" { + value = "my_secret" + sensitive = true +} +output "BLA1" { + value = "bla1" +} +output "BLA2" { + value = "bla2" +} \ No newline at end of file diff --git a/package/tests/tests_helper_files/tf_modules/azure-paas-mssql/main.tf b/package/tests/tests_helper_files/tf_modules/azure-paas-mssql/main.tf new file mode 100644 index 00000000..d69a8b27 --- /dev/null +++ b/package/tests/tests_helper_files/tf_modules/azure-paas-mssql/main.tf @@ -0,0 +1,86 @@ +terraform { + required_providers { + azurerm = { + version = "2.61" + } + } +} + +provider azurerm { + features {} +} + +locals { + sizeMap = { + "small" = "GP_Gen5_2", + "medium" = "GP_GEN5_4", + "large" = "GP_GEN5_8", + "extra-large" = "GP_GEN5_16", + "s2" = "S2", + "s3" = "S3", + "s4" = "S4" + } + + listOfIps = var.ALLOWED_IPS + ipList = compact(split(",", var.ALLOWED_IPS)) + + sanitizedRG = replace(var.RESOURCE_GROUP_NAME, "_", "") #Server Name cannot contain anything other than letters, numbers, and hyphens + serverName = lower("${var.SERVER_NAME}-${local.sanitizedRG}") + + customTags = { + "APPID" = var.APP_ID, + "APPLICATION OWNER" = var.APP_OWNER, + "APPLICATION NAME" = var.APP_NAME + } +} + +resource "azurerm_mssql_server" "default" { + name = local.serverName + resource_group_name = var.RESOURCE_GROUP_NAME + location = var.LOCATION + version = "12.0" + public_network_access_enabled = true + administrator_login = var.SERVER_USERNAME + administrator_login_password = var.SERVER_PASSWORD + + azuread_administrator { + login_username = "ITDBA Admin" + object_id = "ed6204e1-a9c4-408c-bb5e-bcc0d076b3bd" + tenant_id = "3dd8961f-e488-4e60-8e11-a82d994e183d" + } +} + +resource "azurerm_sql_firewall_rule" "ip_list" { + for_each = { for x in local.ipList : x => x } + name = "${each.key}-Requested-IP-Allowance" + resource_group_name = var.RESOURCE_GROUP_NAME + server_name = azurerm_mssql_server.default.name + start_ip_address = trimspace(each.key) + end_ip_address = trimspace(each.key) + depends_on = [ azurerm_mssql_server.default ] +} + +resource "azurerm_sql_firewall_rule" "allow_azure_services" { + name = "AllowAzureServices" + resource_group_name = var.RESOURCE_GROUP_NAME + server_name = azurerm_mssql_server.default.name + start_ip_address = "0.0.0.0" + end_ip_address = "0.0.0.0" + depends_on = [ azurerm_mssql_server.default ] +} + +resource "azurerm_mssql_database" "default" { + name = var.DB_NAME + server_id = azurerm_mssql_server.default.id + sku_name = lookup(local.sizeMap, lower(var.DB_SIZE), "GP_Gen5_2") + create_mode = "Default" + collation = var.COLLATION + max_size_gb = var.DB_STORAGE + depends_on = [ azurerm_mssql_server.default ] + + short_term_retention_policy { + retention_days = 35 + } + + tags = merge(local.customTags) +} diff --git a/package/tests/tests_helper_files/tf_modules/azure-paas-mssql/runSQLCMD.sh b/package/tests/tests_helper_files/tf_modules/azure-paas-mssql/runSQLCMD.sh new file mode 100644 index 00000000..3c7f21a5 --- /dev/null +++ b/package/tests/tests_helper_files/tf_modules/azure-paas-mssql/runSQLCMD.sh @@ -0,0 +1,34 @@ +apt install gnupg -y + +curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - +curl https://packages.microsoft.com/config/debian/10/prod.list | tee /etc/apt/sources.list.d/msprod.list + + +apt update + +export ACCEPT_EULA=y +export DEBIAN_FRONTEND=noninteractive +apt-get install -y --no-install-recommends mssql-tools unixodbc-dev + +export PATH=$PATH:/opt/mssql-tools/bin + +#We create the query like this because sqlcmd on Debian 10 does not support -v +cat << EOF > addUsers.sql +CREATE USER [$O_USER] WITH PASSWORD = "$O_PASS" , DEFAULT_SCHEMA = dbo; +ALTER ROLE db_owner ADD MEMBER [$O_USER]; +GO + +CREATE USER [$RO_USER] WITH PASSWORD = "$RO_PASS" , DEFAULT_SCHEMA = dbo; +ALTER ROLE db_datareader ADD MEMBER [$RO_USER]; +GO + +CREATE USER [$RW_USER] WITH PASSWORD = "$RW_PASS" , DEFAULT_SCHEMA = dbo; +ALTER ROLE db_datawriter ADD MEMBER [$RW_USER]; +GO + +CREATE USER [$DB_USER] WITH PASSWORD = "$DB_PASS" , DEFAULT_SCHEMA = dbo; +ALTER ROLE db_owner ADD MEMBER [$DB_USER] +GO +EOF + +sqlcmd -C -S $FQDN -d $DB_NAME -U $SERVER_USERNAME -P $SERVER_PASSWORD -i addUsers.sql \ No newline at end of file diff --git a/package/tests/tests_helper_files/tf_modules/azure-paas-mssql/vars.tf b/package/tests/tests_helper_files/tf_modules/azure-paas-mssql/vars.tf new file mode 100644 index 00000000..6e65ea76 --- /dev/null +++ b/package/tests/tests_helper_files/tf_modules/azure-paas-mssql/vars.tf @@ -0,0 +1,69 @@ +variable "RESOURCE_GROUP_NAME" { + type = string +} +variable "LOCATION" { + type = string +} +variable "SERVER_NAME" { + type = string +} +variable "SERVER_USERNAME" { +} +variable "SERVER_PASSWORD" { + sensitive = true +} +variable "DB_NAME" { +} +variable "DB_USERNAME" { +} +variable "DB_PASSWORD" { + sensitive = true +} + +variable "DB_SIZE" { + description = "Desired Service Level - small (2 v-core) , medium (4), or large (8)" + default = "small" + type = string + + validation { + condition = contains(["small", "medium", "large", "s3", "s4", "s2"], lower(var.DB_SIZE)) + error_message = "Valid Values: small, medium, large." + } +} + +variable "DB_STORAGE" { + type = number + default = 50 +} + +variable "ALLOWED_IPS" { + description = "List of IPs to be allowed, comma-separated. Example: 192.168.1.1,192.168.1.20,192.168.1.222" + type = string +} + +variable "COLLATION" { + default = "SQL_LATIN1_GENERAL_CP1_CI_AS" + type = string +} + +variable "RO_PASSWORD" { + type = string + sensitive = true +} +variable "RW_PASSWORD" { + type = string + sensitive = true +} +variable "O_PASSWORD" { + type = string + sensitive = true +} + +#TAGS +variable "APP_ID" { +} +variable "APP_OWNER" { +} +variable "APP_NAME" { +} + diff --git a/package/tests/tests_helper_files/tf_modules/azure-vault/main.tf b/package/tests/tests_helper_files/tf_modules/azure-vault/main.tf new file mode 100644 index 00000000..17b5a05f --- /dev/null +++ b/package/tests/tests_helper_files/tf_modules/azure-vault/main.tf @@ -0,0 +1,22 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "=2.46.0" + } + } +} + +provider "azurerm" { + features {} +} + +data "azurerm_key_vault" "keyvault" { + name = var.KEYVAULT_NAME + resource_group_name = var.KEYVAULT_RG +} + +data "azurerm_key_vault_secret" "keyvault_get" { + name = var.SECRET_NAME + key_vault_id = data.azurerm_key_vault.keyvault.id +} diff --git a/package/tests/tests_helper_files/tf_modules/azure-vault/vars.tf b/package/tests/tests_helper_files/tf_modules/azure-vault/vars.tf new file mode 100644 index 00000000..5114dc22 --- /dev/null +++ b/package/tests/tests_helper_files/tf_modules/azure-vault/vars.tf @@ -0,0 +1,9 @@ +variable KEYVAULT_NAME { + +} +variable KEYVAULT_RG { + +} +variable SECRET_NAME { + +} diff --git a/shells/backends/azure_tf_backend/tests/.env.template.env b/shells/backends/azure_tf_backend/tests/.env.template.env deleted file mode 100644 index e1686808..00000000 --- a/shells/backends/azure_tf_backend/tests/.env.template.env +++ /dev/null @@ -1,23 +0,0 @@ -SB_SERVICE_ALIAS = "" -RESERVATION_ID = "" - -CS_SERVER = "" -CS_USERNAME = "" -CS_PASSWORD = "" - -CLP_RESOURSE = "" -SB_AZURE_TF_BACKEND = "" - -GITHUB_TOKEN_ENC = "" - -TFEXEC_VERSION = "" -RESERVATION_DOMAIN = "" - -GITHUB_TF_PRIVATE_HELLO_URL = "" -GITHUB_TF_PRIVATE_AZUREAPP_URL = "" -AZUREAPP_TF_INPUTS = "" - -# OPTIONAL - -GITHUB_TF_PRIVATE_VAULT_URL = "" -CS_SERVER2 = "" \ No newline at end of file diff --git a/shells/backends/azure_tf_backend/tests/integration_tests/helper_objects/env_vars.py b/shells/backends/azure_tf_backend/tests/integration_tests/helper_objects/env_vars.py deleted file mode 100644 index 66baac53..00000000 --- a/shells/backends/azure_tf_backend/tests/integration_tests/helper_objects/env_vars.py +++ /dev/null @@ -1,14 +0,0 @@ -import os - -from dotenv import load_dotenv - - -class EnvVars(object): - def __init__(self): - load_dotenv() - self.cs_user = os.environ.get("CS_USERNAME") - self.cs_pass = os.environ.get("CS_PASSWORD") - self.cs_server = os.environ.get("CS_SERVER") - self.cs_domain = os.environ.get("RESERVATION_DOMAIN") - self.cs_res_id = os.environ.get("RESERVATION_ID") - self.cs_resource = os.environ.get("SB_AZURE_TF_BACKEND") diff --git a/shells/backends/azure_tf_backend/tests/integration_tests/helper_objects/integration_context.py b/shells/backends/azure_tf_backend/tests/integration_tests/helper_objects/integration_context.py deleted file mode 100644 index 8d0e433f..00000000 --- a/shells/backends/azure_tf_backend/tests/integration_tests/helper_objects/integration_context.py +++ /dev/null @@ -1,47 +0,0 @@ -from unittest import mock - -from cloudshell.api.cloudshell_api import CloudShellAPISession -from cloudshell.logging.qs_logger import get_qs_logger -from cloudshell.shell.core.driver_context import ResourceCommandContext - -from tests.integration_tests.helper_objects.env_vars import EnvVars - -from driver import AzureTfBackendDriver - - -class IntegrationData(object): - def __init__(self): - self._env_vars = EnvVars() - self.real_api = CloudShellAPISession( - self._env_vars.cs_server, - self._env_vars.cs_user, - self._env_vars.cs_pass, - self._env_vars.cs_domain - ) - self._set_context() - self._logger = get_qs_logger(log_group=self.context.resource.name) - - self._create_driver() - - def _set_context(self): - self.context = mock.create_autospec(ResourceCommandContext) - self.context.connectivity = mock.MagicMock() - self.context.connectivity.server_address = self._env_vars.cs_server - self.context.connectivity.admin_auth_token = self.real_api.authentication.xmlrpc_token - - self.context.resource = mock.MagicMock() - self.context.resource.attributes = dict() - self.context.resource.name = self._env_vars.cs_resource - self.set_context_resource_attributes() - - self.context.reservation = mock.MagicMock() - self.context.reservation.reservation_id = self._env_vars.cs_res_id - self.context.reservation.domain = self._env_vars.cs_domain - - def set_context_resource_attributes(self): - for attribute in self.real_api.GetResourceDetails(self.context.resource.name).ResourceAttributes: - self.context.resource.attributes[attribute.Name] = attribute.Value - - def _create_driver(self): - self.driver = AzureTfBackendDriver() - self.driver.initialize(self.context) diff --git a/shells/backends/azure_tf_backend/tests/integration_tests/int_test_validate.py b/shells/backends/azure_tf_backend/tests/integration_tests/int_test_validate.py deleted file mode 100644 index 4eb05e5c..00000000 --- a/shells/backends/azure_tf_backend/tests/integration_tests/int_test_validate.py +++ /dev/null @@ -1,17 +0,0 @@ -from unittest import TestCase - -from tests.integration_tests.helper_objects.integration_context import IntegrationData - - -class TestTerraformBackend(TestCase): - def setUp(self) -> None: - self.integration_data = IntegrationData() - - def test_get_inventory(self): - self.integration_data.driver.get_inventory(self.integration_data.context) - - def test_get_cfg_get_backend_data(self): - data = self.integration_data.driver.get_backend_data(self.integration_data.context, "UNIQUE_STRING") - - def test_delete_tfstate_file(self): - self.integration_data.driver.delete_tfstate_file(self.integration_data.context, "d") diff --git a/shells/generic_terraform_service/tests/.env.template.env b/shells/generic_terraform_service/tests/.env.template.env deleted file mode 100644 index 248a0fc3..00000000 --- a/shells/generic_terraform_service/tests/.env.template.env +++ /dev/null @@ -1,32 +0,0 @@ -SB_SERVICE_ALIAS1 = "" -SB_SERVICE_ALIAS2 = "" -RESERVATION_ID = "" - -CS_SERVER = "" -CS_USERNAME = "" -CS_PASSWORD = "" - -CLP_RESOURSE = "" -CLP_ATTRIBUTES = "" - -GITHUB_TOKEN_ENC = "" -GITHUB_TOKEN_DEC = "" - -TFEXEC_VERSION = "" -RESERVATION_DOMAIN = "" - -GITHUB_TF_PRIVATE_HELLO_URL = "" -GITHUB_TF_PRIVATE_AZUREAPP_URL = "" -GITHUB_TF_PRIVATE_AZUREMSSQL_URL = "" - -AZUREAPP_TF_INPUTS = "" -AZUREMSSQL_TF_INPUTS = "" - -REMOTE_STATE_PROVIDER_ACCESS_KEY = "" -REMOTE_STATE_PROVIDER_CLOUD_CRED = "" -REMOTE_STATE_PROVIDER_INVALID_NO_RESOURCE = "" -REMOTE_STATE_PROVIDER_INVALID_WRONG_RESOURCE = "" -# OPTIONAL - -GITHUB_TF_PRIVATE_VAULT_URL = "" -CS_SERVER2 = "" \ No newline at end of file diff --git a/shells/generic_terraform_service/tests/integration_tests/helper_objects/__init__.py b/shells/generic_terraform_service/tests/integration_tests/helper_objects/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/shells/generic_terraform_service/tests/integration_tests/helper_objects/env_vars.py b/shells/generic_terraform_service/tests/integration_tests/helper_objects/env_vars.py deleted file mode 100644 index 80d458c7..00000000 --- a/shells/generic_terraform_service/tests/integration_tests/helper_objects/env_vars.py +++ /dev/null @@ -1,14 +0,0 @@ -import os - -from dotenv import load_dotenv - - -class EnvVars(object): - def __init__(self, service_name: str): - load_dotenv() - self.cs_user = os.environ.get("CS_USERNAME") - self.cs_pass = os.environ.get("CS_PASSWORD") - self.cs_server = os.environ.get("CS_SERVER") - self.cs_domain = os.environ.get("RESERVATION_DOMAIN") - self.cs_res_id = os.environ.get("RESERVATION_ID") - self.sb_service_alias = service_name diff --git a/shells/generic_terraform_service/tests/integration_tests/helper_objects/integration_context.py b/shells/generic_terraform_service/tests/integration_tests/helper_objects/integration_context.py deleted file mode 100644 index 3528c41f..00000000 --- a/shells/generic_terraform_service/tests/integration_tests/helper_objects/integration_context.py +++ /dev/null @@ -1,64 +0,0 @@ -from unittest import mock -from unittest.mock import Mock - -from cloudshell.api.cloudshell_api import CloudShellAPISession -from cloudshell.iac.terraform import TerraformShell, TerraformShellConfig -from cloudshell.logging.qs_logger import get_qs_logger -from cloudshell.shell.core.driver_context import ResourceCommandContext - -from tests.integration_tests.helper_objects.env_vars import EnvVars -from tests.integration_tests.helper_services.service_attributes_factory import ServiceAttributesFactory - - -class IntegrationData(object): - def __init__(self, service_name: str, is_api_real: bool = True, mock_api: Mock = None): - self._env_vars = EnvVars(service_name) - - if is_api_real: - self.api = CloudShellAPISession( - self._env_vars.cs_server, - self._env_vars.cs_user, - self._env_vars.cs_pass, - self._env_vars.cs_domain - ) - - else: - self.api = mock_api - self.api.authentication.xmlrpc_token = Mock() - self._set_context(is_api_real) - self._logger = get_qs_logger(log_group=self.context.resource.name) - self.create_tf_shell() - - def _set_context(self, is_api_real: bool): - self.context = mock.create_autospec(ResourceCommandContext) - self.context.connectivity = mock.MagicMock() - self.context.connectivity.server_address = self._env_vars.cs_server - self.context.connectivity.admin_auth_token = self.api.authentication.xmlrpc_token - - self.context.resource = mock.MagicMock() - self.context.resource.attributes = dict() - self.context.resource.name = self._env_vars.sb_service_alias - self.context.resource.model = 'Generic Terraform Service' - if is_api_real: - self.set_context_resource_attributes_from_cs() - else: - self.context.resource.attributes = ServiceAttributesFactory.create_empty_attributes() - self.context.reservation = mock.MagicMock() - self.context.reservation.reservation_id = self._env_vars.cs_res_id - self.context.reservation.domain = self._env_vars.cs_domain - - def set_context_resource_attributes_from_cs(self, the_only_attribute_to_update: str = ""): - services = self.api.GetReservationDetails(self._env_vars.cs_res_id, disableCache=True) \ - .ReservationDescription.Services - for service in services: - if service.Alias == self._env_vars.sb_service_alias: - for attribute in service.Attributes: - if the_only_attribute_to_update and attribute.Name == the_only_attribute_to_update: - self.context.resource.attributes[attribute.Name] = attribute.Value - return - elif not the_only_attribute_to_update: - self.context.resource.attributes[attribute.Name] = attribute.Value - - def create_tf_shell(self): - self._config = TerraformShellConfig(write_sandbox_messages=True, update_live_status=True) - self.tf_shell = TerraformShell(self.context, self._logger, self._config) diff --git a/shells/generic_terraform_service/tests/integration_tests/helper_objects/integration_helper.py b/shells/generic_terraform_service/tests/integration_tests/helper_objects/integration_helper.py deleted file mode 100644 index 90a3ce5f..00000000 --- a/shells/generic_terraform_service/tests/integration_tests/helper_objects/integration_helper.py +++ /dev/null @@ -1,47 +0,0 @@ -import os -import cloudshell.helpers.scripts.cloudshell_dev_helpers as dev_helpers -from cloudshell.api.cloudshell_api import AttributeNameValue -from cloudshell.workflow.orchestration.sandbox import Sandbox -from tests.integration_tests.constants import SHELL_NAME, UUID_ATTRIBUTE - -from dotenv import load_dotenv - -load_dotenv() - -server_address = os.environ.get("CS_SERVER") -sb_id = os.environ.get("RESERVATION_ID") -cs_user = os.environ.get("CS_USERNAME") -cs_pass = os.environ.get("CS_PASSWORD") -cs_domain = os.environ.get("RESERVATION_DOMAIN") - - -dev_helpers.attach_to_cloudshell_as(cs_user, cs_pass, cs_domain, sb_id, server_address=server_address, cloudshell_api_port='8029') - - -def print_uuids(sb: Sandbox): - services = sb.automation_api.GetReservationDetails(sb_id, disableCache=True).ReservationDescription.Services - for service in services: - for attribute in service.Attributes: - if attribute.Name == f"{SHELL_NAME}.UUID": - service_data = "" - data = sb.automation_api.GetSandboxData(sb_id) - for sbdkv in data.SandboxDataKeyValues: - if sbdkv.Key == attribute.Value: - service_data = sbdkv.Value - print(f"{service.Alias}.UUID={attribute.Value} \n data={service_data}\n\n") - continue - - -def wipe_uuids(sb: Sandbox): - services = sb.automation_api.GetReservationDetails(sb_id, disableCache=True).ReservationDescription.Services - attr_req = [AttributeNameValue(UUID_ATTRIBUTE, "")] - - for service in services: - sb.automation_api.SetServiceAttributesValues(sb_id, service.Alias, attr_req) - - -sb = Sandbox() -print_uuids(sb) -wipe_uuids(sb) -data = sb.automation_api.GetSandboxData(sb_id) -print("") diff --git a/shells/generic_terraform_service/tests/integration_tests/int_test_mock_tf_execute_destroy.py b/shells/generic_terraform_service/tests/integration_tests/int_test_mock_tf_execute_destroy.py deleted file mode 100644 index 27c012c3..00000000 --- a/shells/generic_terraform_service/tests/integration_tests/int_test_mock_tf_execute_destroy.py +++ /dev/null @@ -1,223 +0,0 @@ -from unittest.mock import patch, Mock - -from cloudshell.api.cloudshell_api import NameValuePair -from dotenv import load_dotenv -from tests.integration_tests.constants import SHELL_NAME, ATTRIBUTE_NAMES -from typing import Callable - -from tests.integration_tests.helper_objects.integration_context import IntegrationData - -import os -from unittest import TestCase, mock - -from tests.integration_tests.helper_services.service_attributes_factory import ServiceAttributesFactory - - -class TestMockTerraformExecuteDestroy(TestCase): - @patch('cloudshell.iac.terraform.services.object_factory.CloudShellSessionContext') - def setUp(self, patched_api) -> None: - load_dotenv() - - self._prepare_mock_api() - patched_api.return_value.get_api.return_value = self.mock_api - - self._mocked_tf_working_dir = '' - self._prepare_mock_services() - - self.mock_api.GetReservationDetails.return_value.ReservationDescription.Services = [self._service1, - self._service2] - self._prepare_integration_data() - - def _prepare_integration_data(self): - self.integration_data1 = IntegrationData(self._service1.Alias, False, self.mock_api) - self.integration_data2 = IntegrationData(self._service2.Alias, False, self.mock_api) - - def _prepare_mock_services(self): - self._service1 = Mock() - self._service1.Alias = os.environ.get("SB_SERVICE_ALIAS1") - self._service1.Attributes = ServiceAttributesFactory.create_empty_attributes() - self._service2 = Mock() - self._service2.Alias = os.environ.get("SB_SERVICE_ALIAS2") - self._service2.Attributes = ServiceAttributesFactory.create_empty_attributes() - - def _prepare_mock_api(self): - self.mock_api = Mock() - self.mock_api.DecryptPassword = _decrypt_password - self.mock_api.GetResourceDetails.return_value.ResourceFamilyName = 'Cloud Provider' - self.mock_api.GetResourceDetails.return_value.ResourceModelName = 'Microsoft Azure' - self.mock_api.GetResourceDetails.return_value.ResourceAttributes = [ - NameValuePair(Name="Azure Subscription ID", Value=os.environ.get("AZURE_SUBSCRIPTION_ID")), - NameValuePair(Name="Azure Tenant ID", Value=os.environ.get("AZURE_TENANT_ID")), - NameValuePair(Name="Azure Application ID", Value=os.environ.get("AZURE_APPLICATION_ID")), - NameValuePair(Name="Azure Application Key", Value=os.environ.get("AZURE_APPLICATION_KEY_DEC")) - ] - - '''------------------------------ Generic Execute/Destroy functions ---------------------------------''' - - def run_execute(self, pre_exec_function: Callable, integration_data: IntegrationData): - self.pre_exec_prep(pre_exec_function, integration_data) - integration_data.tf_shell.execute_terraform() - - def run_destroy(self, pre_destroy_function: Callable, integration_data: IntegrationData): - self.pre_destroy_prep(pre_destroy_function, integration_data) - integration_data.tf_shell.destroy_terraform() - - def run_execute_and_destroy(self, pre_exec_function: Callable, pre_destroy_function: Callable, - integration_data: IntegrationData): - self.run_execute(pre_exec_function, integration_data) - self.run_destroy(pre_destroy_function, integration_data) - - '''------------------------------ Test Cases ---------------------------------''' - - @patch('cloudshell.iac.terraform.services.tf_proc_exec.TfProcExec.can_destroy_run') - @patch('cloudshell.iac.terraform.terraform_shell.SandboxDataHandler') - @patch('cloudshell.iac.terraform.services.object_factory.CloudShellSessionContext') - def test_execute_and_destroy_azure_vault(self, patch_api, patched_sbdata_handler, can_destroy_run): - can_destroy_run.return_value = True - patch_api.return_value.get_api.return_value = self.mock_api - mock_sbdata_handler = Mock() - mock_sbdata_handler.get_tf_working_dir = self._get_mocked_tf_working_dir - mock_sbdata_handler.set_tf_working_dir = self._set_mocked_tf_working_dir - patched_sbdata_handler.return_value = mock_sbdata_handler - self.run_execute_and_destroy( - pre_exec_function=self.pre_exec_azure_vault, - pre_destroy_function=self.pre_destroy, - integration_data=self.integration_data1 - ) - - '''------------------------------ Functions : general _pre prep functions ---------------------------------''' - - def pre_exec_prep(self, pre_exec_function: Callable, integration_data: IntegrationData): - pre_exec_function(integration_data) - - def pre_destroy_prep(self, pre_destroy_function: Callable, integration_data: IntegrationData): - pre_destroy_function(integration_data) - - def clear_sb_data(self): - self.integration_data1.api.ClearSandboxData(self.integration_data1.context.reservation.reservation_id) - - '''------------------------------ Functions : prep before exec -------------------------------------------''' - - def pre_exec(self, integration_data: IntegrationData): - pass - - def pre_exec_azure_vault(self, integration_data: IntegrationData): - self._set_attribute_on_mock_service( - f"{SHELL_NAME}.Terraform Inputs", - os.environ.get("AZUREAPP_TF_INPUTS"), - integration_data - ) - self._set_attribute_on_mock_service( - f"{SHELL_NAME}.Github Terraform Module URL", - os.environ.get("GITHUB_TF_PRIVATE_AZUREAPP_URL"), - integration_data - ) - self._set_attribute_on_mock_service( - f"{SHELL_NAME}.{ATTRIBUTE_NAMES.GITHUB_TOKEN}", - os.environ.get("GITHUB_TOKEN_DEC"), - integration_data - ) - self._set_attribute_on_mock_service( - f"{SHELL_NAME}.{ATTRIBUTE_NAMES.TERRAFORM_VERSION}", - os.environ.get("0.15.1"), - integration_data - ) - self._set_attribute_on_mock_service( - f"{SHELL_NAME}.{ATTRIBUTE_NAMES.CLOUD_PROVIDER}", - os.environ.get("CLP_RESOURSE"), - integration_data - ) - self._prepare_service1_before_execute(integration_data) - - def _prepare_service1_before_execute(self, integration_data): - service1 = Mock() - service1.Alias = integration_data.context.resource.name - service1.Attributes = integration_data.context.resource.attributes - self.mock_api.GetReservationDetails.return_value.ReservationDescription.Services = [service1] - integration_data.create_tf_shell() - - def pre_exec_azure_mssql(self, integration_data: IntegrationData): - self._set_attribute_on_mock_service( - f"{SHELL_NAME}.Terraform Inputs", - os.environ.get("AZUREMSSQL_TF_INPUTS"), - integration_data - ) - self._set_attribute_on_mock_service( - f"{SHELL_NAME}.Github Terraform Module URL", - os.environ.get("GITHUB_TF_PRIVATE_AZUREMSSQL_URL"), - integration_data - ) - self._set_attribute_on_mock_service( - f"{SHELL_NAME}.UUID", - os.environ.get(""), - integration_data - ) - self._prepare_service1_before_execute(integration_data) - - def pre_exec_azure_vault_with_remote_access_key_based(self, integration_data: IntegrationData): - self.pre_exec_azure_vault(integration_data) - self._set_attribute_on_mock_service( - f"{SHELL_NAME}.Remote State Provider", - os.environ.get("REMOTE_STATE_PROVIDER_ACCESS_KEY"), - integration_data - ) - - def pre_exec_azure_vault_with_remote_cloud_cred_based(self, integration_data: IntegrationData): - self.pre_exec_azure_vault(integration_data) - self._set_attribute_on_mock_service( - f"{SHELL_NAME}.Remote State Provider", - os.environ.get("REMOTE_STATE_PROVIDER_CLOUD_CRED"), - integration_data - ) - - def pre_exec_azure_vault_with_remote_invalid_nonexistent(self, integration_data: IntegrationData): - self.pre_exec_azure_vault(integration_data) - self._set_attribute_on_mock_service( - f"{SHELL_NAME}.Remote State Provider", - os.environ.get("REMOTE_STATE_PROVIDER_INVALID_NO_RESOURCE"), - integration_data - ) - - def pre_exec_azure_vault_with_remote_invalid_wrong(self, integration_data: IntegrationData): - self.pre_exec_azure_vault(integration_data) - self._set_attribute_on_mock_service( - f"{SHELL_NAME}.Remote State Provider", - os.environ.get("REMOTE_STATE_PROVIDER_INVALID_WRONG_RESOURCE"), - integration_data - ) - - def pre_exec_azure_vault_without_remote(self, integration_data: IntegrationData): - self.pre_exec_azure_vault(integration_data) - self._set_attribute_on_mock_service( - f"{SHELL_NAME}.Remote State Provider", - os.environ.get(""), - integration_data - ) - - '''------------------------------ Functions : prep before destroy -----------------------------------------''' - - def pre_destroy(self, integration_data: IntegrationData): - # As UUID has been created and SB data now contains UUID and Status we must update context so destroy can run - for attribute in integration_data.context.resource.attributes: - if attribute.Name == f"{SHELL_NAME}.UUID": - attribute.Value = integration_data.tf_shell._tf_service.attributes[f"{SHELL_NAME}.UUID"] - - '''------------------------------ Helper Functions ---------------------------------------------------------''' - @staticmethod - def _set_attribute_on_mock_service(attr_name: str, attr_value: str, integration_data: IntegrationData): - for attribute in integration_data.context.resource.attributes: - if attribute.Name == attr_name: - attribute.Value = attr_value - return - - def _get_mocked_tf_working_dir(self): - return self._mocked_tf_working_dir - - def _set_mocked_tf_working_dir(self, tf_working_dir: str): - self._mocked_tf_working_dir = tf_working_dir - - -def _decrypt_password(x): - result = mock.MagicMock() - result.Value = x - return result diff --git a/shells/generic_terraform_service/tests/integration_tests/int_test_real_tf_execute_destroy.py b/shells/generic_terraform_service/tests/integration_tests/int_test_real_tf_execute_destroy.py deleted file mode 100644 index 86eab030..00000000 --- a/shells/generic_terraform_service/tests/integration_tests/int_test_real_tf_execute_destroy.py +++ /dev/null @@ -1,209 +0,0 @@ -from cloudshell.api.cloudshell_api import AttributeNameValue -from dotenv import load_dotenv -from tests.integration_tests.constants import SHELL_NAME -from typing import Callable - -from tests.integration_tests.helper_objects.integration_context import IntegrationData - -import os -from unittest import TestCase - - -class TestRealTerraformExecuteDestroy(TestCase): - def setUp(self) -> None: - load_dotenv() - self.integration_data1 = IntegrationData(os.environ.get("SB_SERVICE_ALIAS1"), is_api_real=True) - self.integration_data2 = IntegrationData(os.environ.get("SB_SERVICE_ALIAS2"), is_api_real=True) - - def run_execute_and_destroy( - self, pre_exec_function: Callable, - pre_destroy_function: Callable, - integration_data: IntegrationData - ): - self.clear_sb_data() - self.run_execute(pre_exec_function, integration_data) - self.run_destroy(pre_destroy_function, integration_data) - - def run_execute(self, pre_exec_function: Callable, integration_data: IntegrationData): - self.pre_exec_prep(pre_exec_function, integration_data) - integration_data.tf_shell.execute_terraform() - - def run_destroy(self, pre_destroy_function: Callable, integration_data: IntegrationData): - self.pre_destroy_prep(pre_destroy_function, integration_data) - integration_data.tf_shell.destroy_terraform() - - '''------------------------------ Test Cases ---------------------------------''' - - def test_execute_and_destroy(self): - self.run_execute_and_destroy( - pre_exec_function=self.pre_exec, - pre_destroy_function=self.pre_destroy, - integration_data=self.integration_data1 - ) - - def test_execute_and_destroy_azure_vault(self): - self.run_execute_and_destroy( - pre_exec_function=self.pre_exec_azure_vault, - pre_destroy_function=self.pre_destroy, - integration_data=self.integration_data1 - ) - - def test_execute_dual_mssql(self): - self.clear_sb_data() - try: - self.run_execute(pre_exec_function=self.pre_exec_azure_mssql, integration_data=self.integration_data1) - except Exception as e: - pass - try: - self.run_execute(pre_exec_function=self.pre_exec_azure_mssql, integration_data=self.integration_data2) - except Exception as e: - pass - self.run_destroy(pre_destroy_function=self.pre_destroy, integration_data=self.integration_data1) - self.run_execute(pre_exec_function=self.pre_exec_azure_mssql, integration_data=self.integration_data2) - - def test_execute_and_destroy_azure_vault_with_remote_access_key_based(self): - self.run_execute_and_destroy( - pre_exec_function=self.pre_exec_azure_vault_with_remote_access_key_based, - pre_destroy_function=self.pre_destroy, - integration_data=self.integration_data1 - ) - - def test_execute_and_destroy_azure_vault_with_remote_access_cloud_cred_based(self): - self.run_execute_and_destroy( - pre_exec_function=self.pre_exec_azure_vault_with_remote_cloud_cred_based, - pre_destroy_function=self.pre_destroy, - integration_data=self.integration_data1 - ) - - def test_execute_and_destroy_azure_vault_with_remote_invalid_nonexistent(self): - try: - self.run_execute_and_destroy( - pre_exec_function=self.pre_exec_azure_vault_with_remote_invalid_nonexistent, - pre_destroy_function=self.pre_destroy, - integration_data=self.integration_data1 - ) - except Exception as e: - pass - - def test_execute_and_destroy_azure_vault_with_remote_invalid_wrong(self): - try: - self.run_execute_and_destroy( - pre_exec_function=self.pre_exec_azure_vault_with_remote_invalid_wrong, - pre_destroy_function=self.pre_destroy, - integration_data=self.integration_data1 - ) - except Exception as e: - pass - - def test_execute_and_destroy_azure_vault_without_remote(self): - self.run_execute_and_destroy( - pre_exec_function=self.pre_exec_azure_vault_without_remote, - pre_destroy_function=self.pre_destroy, - integration_data=self.integration_data1 - ) - - '''------------------------------ Functions : general _pre prep functions ---------------------------------''' - - def pre_exec_prep(self, pre_exec_function: Callable, integration_data: IntegrationData): - pre_exec_function(integration_data) - - def pre_destroy_prep(self, pre_destroy_function: Callable, integration_data: IntegrationData): - pre_destroy_function(integration_data) - - def clear_sb_data(self): - self.integration_data1.api.ClearSandboxData(self.integration_data1.context.reservation.reservation_id) - - '''------------------------------ Functions : prep before exec -------------------------------------------''' - - def pre_exec(self, integration_data: IntegrationData): - pass - - def pre_exec_azure_vault(self, integration_data: IntegrationData): - self._set_attribute_on_service( - f"{SHELL_NAME}.Terraform Inputs", - os.environ.get("AZUREAPP_TF_INPUTS"), - integration_data - ) - self._set_attribute_on_service( - f"{SHELL_NAME}.Github Terraform Module URL", - os.environ.get("GITHUB_TF_PRIVATE_AZUREAPP_URL"), - integration_data - ) - self._set_attribute_on_service( - f"{SHELL_NAME}.UUID", - os.environ.get(""), - integration_data - ) - - def pre_exec_azure_mssql(self, integration_data: IntegrationData): - self._set_attribute_on_service( - f"{SHELL_NAME}.Terraform Inputs", - os.environ.get("AZUREMSSQL_TF_INPUTS"), - integration_data - ) - self._set_attribute_on_service( - f"{SHELL_NAME}.Github Terraform Module URL", - os.environ.get("GITHUB_TF_PRIVATE_AZUREMSSQL_URL"), - integration_data - ) - self._set_attribute_on_service( - f"{SHELL_NAME}.UUID", - os.environ.get(""), - integration_data - ) - - def pre_exec_azure_vault_with_remote_access_key_based(self, integration_data: IntegrationData): - self.pre_exec_azure_vault(integration_data) - self._set_attribute_on_service( - f"{SHELL_NAME}.Remote State Provider", - os.environ.get("REMOTE_STATE_PROVIDER_ACCESS_KEY"), - integration_data - ) - - def pre_exec_azure_vault_with_remote_cloud_cred_based(self, integration_data: IntegrationData): - self.pre_exec_azure_vault(integration_data) - self._set_attribute_on_service( - f"{SHELL_NAME}.Remote State Provider", - os.environ.get("REMOTE_STATE_PROVIDER_CLOUD_CRED"), - integration_data - ) - - def pre_exec_azure_vault_with_remote_invalid_nonexistent(self, integration_data: IntegrationData): - self.pre_exec_azure_vault(integration_data) - self._set_attribute_on_service( - f"{SHELL_NAME}.Remote State Provider", - os.environ.get("REMOTE_STATE_PROVIDER_INVALID_NO_RESOURCE"), - integration_data - ) - - def pre_exec_azure_vault_with_remote_invalid_wrong(self, integration_data: IntegrationData): - self.pre_exec_azure_vault(integration_data) - self._set_attribute_on_service( - f"{SHELL_NAME}.Remote State Provider", - os.environ.get("REMOTE_STATE_PROVIDER_INVALID_WRONG_RESOURCE"), - integration_data - ) - - def pre_exec_azure_vault_without_remote(self, integration_data: IntegrationData): - self.pre_exec_azure_vault(integration_data) - self._set_attribute_on_service( - f"{SHELL_NAME}.Remote State Provider", - os.environ.get(""), - integration_data - ) - - '''------------------------------ Functions : prep before destroy -----------------------------------------''' - - def pre_destroy(self, integration_data: IntegrationData): - # As UUID has been created and SB data now contains UUID and Status we must update context so destroy can run - integration_data.set_context_resource_attributes_from_cs(f"{SHELL_NAME}.UUID") - - '''------------------------------ Helper Functions ---------------------------------------------------------''' - - def _set_attribute_on_service(self, attr_name: str, attr_value: str, integration_data: IntegrationData): - attr_req = [AttributeNameValue(attr_name, attr_value)] - integration_data.api.SetServiceAttributesValues( - integration_data.context.reservation.reservation_id, - integration_data.context.resource.name, - attr_req - ) diff --git a/shells/generic_terraform_service/tests/test_requirements.txt b/shells/generic_terraform_service/tests/test_requirements.txt index d8860af1..248fa44a 100644 --- a/shells/generic_terraform_service/tests/test_requirements.txt +++ b/shells/generic_terraform_service/tests/test_requirements.txt @@ -1,2 +1,3 @@ cloudshell-iac-terraform==0.1.0 -python-dotenv==0.18.0 \ No newline at end of file +python-dotenv==0.18.0 +pathlib==1.0.1 \ No newline at end of file