From 4019c130e4ff5c100f36485b6467bc25069884f1 Mon Sep 17 00:00:00 2001 From: apoorva9s14 Date: Thu, 28 Jul 2022 23:36:31 +0530 Subject: [PATCH 1/4] examples Signed-off-by: apoorva9s14 --- .../test_resource_controller_v2_examples.py | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/examples/test_resource_controller_v2_examples.py b/examples/test_resource_controller_v2_examples.py index 9075ed38..d6a1ad4d 100644 --- a/examples/test_resource_controller_v2_examples.py +++ b/examples/test_resource_controller_v2_examples.py @@ -364,7 +364,8 @@ def test_get_resource_binding_example(self): resource_binding = resource_controller_service.get_resource_binding( id=binding_guid ).get_result() - + if resource_binding.get('credentials') and resource_binding.get('credentials').get('REDACTED'): + print("Credentials are redacted with code:", resource_binding.get('credentials').get('REDACTED'), ".The User doesn't have the correct access to view the credentials. Refer to the API documentation for additional details.") print(json.dumps(resource_binding, indent=2)) # end-get_resource_binding @@ -477,6 +478,8 @@ def test_get_resource_key_example(self): resource_key = resource_controller_service.get_resource_key( id=instance_key_guid ).get_result() + if resource_key.get('credentials') and resource_key.get('credentials').get('REDACTED'): + print("Credentials are redacted with code:", resource_key.get('credentials').get('REDACTED'), ".The User doesn't have the correct access to view the credentials. Refer to the API documentation for additional details.") print(json.dumps(resource_key, indent=2)) @@ -721,6 +724,28 @@ def test_run_reclamation_action_example(self): except ApiException as e: pytest.fail(str(e)) + @needscredentials + def test_cancel_lastop_resource_instance_example(self): + """ + cancel_lastop_resource_instance request example + """ + try: + print('\ncancel_lastop_resource_instance() result:') + # begin-cancel_lastop_resource_instance + + resource_instance = resource_controller_service.cancel_lastop_resource_instance( + id=instance_guid + ).get_result() + print(json.dumps(resource_instance, indent=2)) + + # end-cancel_lastop_resource_instance + + except ApiException as e: + if e.message == "The instance is not cancelable.": + print("The instance is not cancelable") + else: + pytest.fail(str(e)) + # endregion ############################################################################## # End of Examples for Service: ResourceControllerV2 From c3ade56faa57583627b5866d8ef32f929b98979c Mon Sep 17 00:00:00 2001 From: apoorva9s14 Date: Thu, 28 Jul 2022 23:37:34 +0530 Subject: [PATCH 2/4] services file Signed-off-by: apoorva9s14 --- .../resource_controller_v2.py | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/ibm_platform_services/resource_controller_v2.py b/ibm_platform_services/resource_controller_v2.py index 811245a6..eb169c3d 100644 --- a/ibm_platform_services/resource_controller_v2.py +++ b/ibm_platform_services/resource_controller_v2.py @@ -598,6 +598,44 @@ def unlock_resource_instance(self, response = self.send(request) return response + def cancel_lastop_resource_instance(self, + id: str, + **kwargs + ) -> DetailedResponse: + """ + Cancel the in progress last operation of the resource instance. + + Cancel the in progress last operation of the resource instance. After successful + cancellation, the resource instance is removed. + + :param str id: The resource instance URL-encoded CRN or GUID. + :param dict headers: A `dict` containing the request headers + :return: A `DetailedResponse` containing the result, headers and HTTP status code. + :rtype: DetailedResponse with `dict` result representing a `ResourceInstance` object + """ + + if id is None: + raise ValueError('id must be provided') + headers = {} + sdk_headers = get_sdk_headers(service_name=self.DEFAULT_SERVICE_NAME, + service_version='V2', + operation_id='cancel_lastop_resource_instance') + headers.update(sdk_headers) + + if 'headers' in kwargs: + headers.update(kwargs.get('headers')) + headers['Accept'] = 'application/json' + + path_param_keys = ['id'] + path_param_values = self.encode_path_vars(id) + path_param_dict = dict(zip(path_param_keys, path_param_values)) + url = '/v2/resource_instances/{id}/last_operation'.format(**path_param_dict) + request = self.prepare_request(method='DELETE', + url=url, + headers=headers) + + response = self.send(request) + return response ######################### # Resource Keys ######################### @@ -1592,6 +1630,11 @@ class Credentials(): """ The credentials for a resource. + :attr str redacted: (optional) If present, the user doesn't have the correct + access to view the credentials and the details are redacted. The string value + identifies the level of access that's required to view the credential. For + additional information, see [viewing a + credential](https://cloud.ibm.com/docs/account?topic=account-service_credentials&interface=ui#viewing-credentials-ui). :attr str apikey: (optional) The API key for the credentials. :attr str iam_apikey_description: (optional) The optional description of the API key. @@ -1603,10 +1646,11 @@ class Credentials(): """ # The set of defined properties for the class - _properties = frozenset(['apikey', 'iam_apikey_description', 'iam_apikey_name', 'iam_role_crn', 'iam_serviceid_crn']) + _properties = frozenset(['REDACTED', 'apikey', 'iam_apikey_description', 'iam_apikey_name', 'iam_role_crn', 'iam_serviceid_crn']) def __init__(self, *, + redacted: str = None, apikey: str = None, iam_apikey_description: str = None, iam_apikey_name: str = None, @@ -1616,6 +1660,11 @@ def __init__(self, """ Initialize a Credentials object. + :param str redacted: (optional) If present, the user doesn't have the + correct access to view the credentials and the details are redacted. The + string value identifies the level of access that's required to view the + credential. For additional information, see [viewing a + credential](https://cloud.ibm.com/docs/account?topic=account-service_credentials&interface=ui#viewing-credentials-ui). :param str apikey: (optional) The API key for the credentials. :param str iam_apikey_description: (optional) The optional description of the API key. @@ -1626,6 +1675,7 @@ def __init__(self, service ID of the credentials. :param **kwargs: (optional) Any additional properties. """ + self.redacted = redacted self.apikey = apikey self.iam_apikey_description = iam_apikey_description self.iam_apikey_name = iam_apikey_name @@ -1638,6 +1688,8 @@ def __init__(self, def from_dict(cls, _dict: Dict) -> 'Credentials': """Initialize a Credentials object from a json dictionary.""" args = {} + if 'REDACTED' in _dict: + args['redacted'] = _dict.get('REDACTED') if 'apikey' in _dict: args['apikey'] = _dict.get('apikey') if 'iam_apikey_description' in _dict: @@ -1659,6 +1711,8 @@ def _from_dict(cls, _dict): def to_dict(self) -> Dict: """Return a json dictionary representing this model.""" _dict = {} + if hasattr(self, 'redacted') and self.redacted is not None: + _dict['REDACTED'] = self.redacted if hasattr(self, 'apikey') and self.apikey is not None: _dict['apikey'] = self.apikey if hasattr(self, 'iam_apikey_description') and self.iam_apikey_description is not None: From 0e91492923c157dabb1fbba2f7d8b11872d1e4d0 Mon Sep 17 00:00:00 2001 From: apoorva9s14 Date: Thu, 28 Jul 2022 23:39:38 +0530 Subject: [PATCH 3/4] integration tests for cancel operation Signed-off-by: apoorva9s14 --- test/integration/test_resource_controller_v2.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/integration/test_resource_controller_v2.py b/test/integration/test_resource_controller_v2.py index f73dbd0f..ba270798 100644 --- a/test/integration/test_resource_controller_v2.py +++ b/test/integration/test_resource_controller_v2.py @@ -1292,6 +1292,20 @@ def test_52_reclaim_resource_instance(self): assert result.get('state') == 'RECLAIMING' time.sleep(20) + + def test_53_cancel_last_operation(self): + + customHeaders = {} + customHeaders["Transaction-Id"] = "rc-sdk-python-test53-" + \ + self.transactionId + try: + response = self.service.cancel_lastop_resource_instance( + id=self.testInstanceGuid, + headers=customHeaders) + assert response is not None + except ApiException as e: + assert e.message == "The instance is not cancelable." + # Commented because redis timeouts cause intermittent failure From 7eb04dec88e7784f7b86c8a5c351ae66307c9a4a Mon Sep 17 00:00:00 2001 From: apoorva9s14 Date: Thu, 28 Jul 2022 23:40:03 +0530 Subject: [PATCH 4/4] unit tests for cancel operation Signed-off-by: apoorva9s14 --- test/unit/test_resource_controller_v2.py | 65 ++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/test/unit/test_resource_controller_v2.py b/test/unit/test_resource_controller_v2.py index b448e4f2..640dc79f 100644 --- a/test/unit/test_resource_controller_v2.py +++ b/test/unit/test_resource_controller_v2.py @@ -900,6 +900,71 @@ def test_unlock_resource_instance_value_error(self): with pytest.raises(ValueError): _service.unlock_resource_instance(**req_copy) +class TestCancelLastopResourceInstance(): + """ + Test Class for cancel_lastop_resource_instance + """ + + def preprocess_url(self, request_url: str): + """ + Preprocess the request URL to ensure the mock response will be found. + """ + if re.fullmatch('.*/+', request_url) is None: + return request_url + else: + return re.compile(request_url.rstrip('/') + '/+') + @responses.activate + def test_cancel_lastop_resource_instance_all_params(self): + """ + cancel_lastop_resource_instance() + """ + # Set up mock + url = self.preprocess_url(_base_url + '/v2/resource_instances/testString/last_operation') + mock_response = '{"id": "id", "guid": "guid", "url": "url", "created_at": "2019-01-01T12:00:00.000Z", "updated_at": "2019-01-01T12:00:00.000Z", "deleted_at": "2019-01-01T12:00:00.000Z", "created_by": "created_by", "updated_by": "updated_by", "deleted_by": "deleted_by", "scheduled_reclaim_at": "2019-01-01T12:00:00.000Z", "restored_at": "2019-01-01T12:00:00.000Z", "restored_by": "restored_by", "scheduled_reclaim_by": "scheduled_reclaim_by", "name": "name", "region_id": "region_id", "account_id": "account_id", "reseller_channel_id": "reseller_channel_id", "resource_plan_id": "resource_plan_id", "resource_group_id": "resource_group_id", "resource_group_crn": "resource_group_crn", "target_crn": "target_crn", "parameters": {"mapKey": "anyValue"}, "allow_cleanup": false, "crn": "crn", "state": "active", "type": "type", "sub_type": "sub_type", "resource_id": "resource_id", "dashboard_url": "dashboard_url", "last_operation": {"type": "type", "state": "in progress", "sub_type": "sub_type", "async": true, "description": "description", "reason_code": "reason_code", "poll_after": 10, "cancelable": true, "poll": true}, "resource_aliases_url": "resource_aliases_url", "resource_bindings_url": "resource_bindings_url", "resource_keys_url": "resource_keys_url", "plan_history": [{"resource_plan_id": "resource_plan_id", "start_date": "2019-01-01T12:00:00.000Z", "requestor_id": "requestor_id"}], "migrated": true, "extensions": {"mapKey": "anyValue"}, "controlled_by": "controlled_by", "locked": true}' + responses.add(responses.DELETE, + url, + body=mock_response, + content_type='application/json', + status=200) + + # Set up parameter values + id = 'testString' + + # Invoke method + response = _service.cancel_lastop_resource_instance( + id, + headers={} + ) + + # Check for correct operation + assert len(responses.calls) == 1 + assert response.status_code == 200 + + @responses.activate + def test_cancel_lastop_resource_instance_value_error(self): + """ + test_cancel_lastop_resource_instance_value_error() + """ + # Set up mock + url = self.preprocess_url(_base_url + '/v2/resource_instances/testString/last_operation') + mock_response = '{"id": "id", "guid": "guid", "url": "url", "created_at": "2019-01-01T12:00:00.000Z", "updated_at": "2019-01-01T12:00:00.000Z", "deleted_at": "2019-01-01T12:00:00.000Z", "created_by": "created_by", "updated_by": "updated_by", "deleted_by": "deleted_by", "scheduled_reclaim_at": "2019-01-01T12:00:00.000Z", "restored_at": "2019-01-01T12:00:00.000Z", "restored_by": "restored_by", "scheduled_reclaim_by": "scheduled_reclaim_by", "name": "name", "region_id": "region_id", "account_id": "account_id", "reseller_channel_id": "reseller_channel_id", "resource_plan_id": "resource_plan_id", "resource_group_id": "resource_group_id", "resource_group_crn": "resource_group_crn", "target_crn": "target_crn", "parameters": {"mapKey": "anyValue"}, "allow_cleanup": false, "crn": "crn", "state": "active", "type": "type", "sub_type": "sub_type", "resource_id": "resource_id", "dashboard_url": "dashboard_url", "last_operation": {"type": "type", "state": "in progress", "sub_type": "sub_type", "async": true, "description": "description", "reason_code": "reason_code", "poll_after": 10, "cancelable": true, "poll": true}, "resource_aliases_url": "resource_aliases_url", "resource_bindings_url": "resource_bindings_url", "resource_keys_url": "resource_keys_url", "plan_history": [{"resource_plan_id": "resource_plan_id", "start_date": "2019-01-01T12:00:00.000Z", "requestor_id": "requestor_id"}], "migrated": true, "extensions": {"mapKey": "anyValue"}, "controlled_by": "controlled_by", "locked": true}' + responses.add(responses.DELETE, + url, + body=mock_response, + content_type='application/json', + status=200) + + # Set up parameter values + id = 'testString' + + # Pass in all but one required param and check for a ValueError + req_param_dict = { + "id": id, + } + for param in req_param_dict.keys(): + req_copy = {key:val if key is not param else None for (key,val) in req_param_dict.items()} + with pytest.raises(ValueError): + _service.cancel_lastop_resource_instance(**req_copy) # endregion