def test_deployed_api_mapping_resource(): deployed = DeployedResources( {'resources': [ {'name': 'foo'}, { "name": "api_gateway_custom_domain", "resource_type": "domain_name", "api_mapping": [ { "key": "path_key" } ] } ]} ) name = 'api_gateway_custom_domain.api_mapping.path_key' result = deployed.resource_values(name) assert result == { "name": "api_gateway_custom_domain", "resource_type": "domain_name", "api_mapping": [ { "key": "path_key" } ] }
class SmokeTestApplication(object): # Number of seconds to wait after redeploy before starting # to poll for successful 200. _REDEPLOY_SLEEP = 20 # Seconds to wait between poll attempts after redeploy. _POLLING_DELAY = 5 def __init__(self, deployed_values, stage_name, app_name, app_dir, region): self._deployed_resources = DeployedResources(deployed_values) self.stage_name = stage_name self.app_name = app_name # The name of the tmpdir where the app is copied. self.app_dir = app_dir self._has_redeployed = False self._region = region @property def websocket_api_id(self): return self._deployed_resources.resource_values( 'websocket_api')['websocket_api_id'] @property def websocket_connect_url(self): return ("wss://{websocket_api_id}.execute-api.{region}.amazonaws.com/" "{api_gateway_stage}".format( websocket_api_id=self.websocket_api_id, region=self._region, api_gateway_stage='api', )) @property def websocket_message_handler_arn(self): return self._deployed_resources.resource_values( 'websocket_message')['lambda_arn'] @property def region(self): return self._region def redeploy_once(self): # Redeploy the application once. If a redeploy # has already happened, this function is a noop. if self._has_redeployed: return new_file = os.path.join(self.app_dir, 'app-redeploy.py') original_app_py = os.path.join(self.app_dir, 'app.py') shutil.move(original_app_py, original_app_py + '.bak') shutil.copy(new_file, original_app_py) self._clear_app_import() _deploy_app(self.app_dir) self._has_redeployed = True # Give it settling time before running more tests. time.sleep(self._REDEPLOY_SLEEP) def _clear_app_import(self): # Now that we're using `import` instead of `exec` we need # to clear out sys.modules in order to pick up the new # version of the app we just copied over. del sys.modules['app']
def __init__(self, deployed_values, stage_name, app_name, app_dir, region): self._deployed_resources = DeployedResources(deployed_values) self.stage_name = stage_name self.app_name = app_name # The name of the tmpdir where the app is copied. self.app_dir = app_dir self._has_redeployed = False self._region = region
def test_prompted_on_runtime_change_can_reject_change(app_policy, sample_app): osutils = InMemoryOSUtils({'packages.zip': b'package contents'}) aws_client = mock.Mock(spec=TypedAWSClient) packager = mock.Mock(spec=LambdaDeploymentPackager) packager.deployment_package_filename.return_value = 'packages.zip' aws_client.lambda_function_exists.return_value = True aws_client.get_function_configuration.return_value = { 'Runtime': 'python1.0', } aws_client.update_function.return_value = {"FunctionArn": "myarn"} cfg = Config.create( chalice_stage='dev', chalice_app=sample_app, manage_iam_role=False, app_name='appname', iam_role_arn=True, project_dir='./myproject', environment_variables={"FOO": "BAR"}, ) prompter = mock.Mock(spec=NoPrompt) prompter.confirm.side_effect = RuntimeError("Aborted") d = LambdaDeployer(aws_client, packager, prompter, osutils, app_policy) # Doing a lambda deploy with a different runtime: lambda_function_name = 'lambda_function_name' deployed = DeployedResources('api', 'api_handler_arn', lambda_function_name, None, 'dev', None, None) with pytest.raises(RuntimeError): d.deploy(cfg, deployed, 'dev') assert not packager.inject_latest_app.called assert not aws_client.update_function.called assert prompter.confirm.called message = prompter.confirm.call_args[0][0] assert 'runtime will change' in message
def test_can_provide_lambda_name_for_logs(runner, mock_cli_factory): deployed_resources = DeployedResources({ "resources": [ {"name": "foo", "lambda_arn": "arn:aws:lambda::app-dev-foo", "resource_type": "lambda_function"}] }) mock_cli_factory.create_config_obj.return_value = FakeConfig( deployed_resources) log_retriever = mock.Mock(spec=LogRetriever) log_retriever.retrieve_logs.return_value = [] mock_cli_factory.create_log_retriever.return_value = log_retriever with runner.isolated_filesystem(): cli.create_new_project_skeleton('testproject') os.chdir('testproject') result = _run_cli_command( runner, cli.logs, ['--name', 'foo'], cli_factory=mock_cli_factory ) assert result.exit_code == 0 log_retriever.retrieve_logs.assert_called_with( include_lambda_messages=False, max_entries=None) mock_cli_factory.create_log_retriever.assert_called_with( mock.sentinel.Session, 'arn:aws:lambda::app-dev-foo' )
def test_invoke_does_raise_if_service_error(runner, mock_cli_factory): deployed_resources = DeployedResources({"resources": []}) mock_cli_factory.create_config_obj.return_value = FakeConfig( deployed_resources) invoke_handler = mock.Mock(spec=LambdaInvokeHandler) invoke_handler.invoke.side_effect = ClientError( { 'Error': { 'Code': 'LambdaError', 'Message': 'Error message' } }, 'Invoke' ) mock_cli_factory.create_lambda_invoke_handler.return_value = invoke_handler mock_reader = mock.Mock(spec=PipeReader) mock_reader.read.return_value = 'barbaz' mock_cli_factory.create_stdin_reader.return_value = mock_reader with runner.isolated_filesystem(): cli.create_new_project_skeleton('testproject') os.chdir('testproject') result = _run_cli_command(runner, cli.invoke, ['-n', 'foo'], cli_factory=mock_cli_factory) assert result.exit_code == 1 assert invoke_handler.invoke.call_args == mock.call('barbaz') assert ( "Error: got 'LambdaError' exception back from Lambda\n" "Error message" ) in result.output
def __init__(self, deployed_values, stage_name, app_name, app_dir, region): self._deployed_resources = DeployedResources(deployed_values) self.stage_name = stage_name self.app_name = app_name # The name of the tmpdir where the app is copied. self.app_dir = app_dir self._has_redeployed = False self._region = region
def test_api_gateway_deployer_delete(config_obj): aws_client = mock.Mock(spec=TypedAWSClient, region_name='us-west-2') rest_api_id = 'abcdef1234' deployed = DeployedResources(None, None, None, rest_api_id, 'dev', None, None) aws_client.rest_api_exists.return_value = True d = APIGatewayDeployer(aws_client) d.delete(deployed) aws_client.delete_rest_api.assert_called_with(rest_api_id)
def test_can_get_deployed_values(self): remote_state = RemoteState( self.client, DeployedResources( {'resources': [{ 'name': 'rest_api', 'rest_api_id': 'foo' }]})) rest_api = self.create_rest_api_model() values = remote_state.resource_deployed_values(rest_api) assert values == {'name': 'rest_api', 'rest_api_id': 'foo'}
def test_value_error_raised_for_unknown_resource_name(self): remote_state = RemoteState( self.client, DeployedResources({ 'resources': [{ 'name': 'not_rest_api', 'rest_api_id': 'foo' }] })) rest_api = self.create_rest_api_model() with pytest.raises(ValueError): remote_state.resource_deployed_values(rest_api)
def test_lambda_deployer_delete(): aws_client = mock.Mock(spec=TypedAWSClient) aws_client.get_role_arn_for_name.return_value = 'arn_prefix/role_name' lambda_function_name = 'lambda_name' deployed = DeployedResources('api', 'api_handler_arn/lambda_name', lambda_function_name, None, 'dev', None, None) d = LambdaDeployer(aws_client, None, CustomConfirmPrompt(True), None, None) d.delete(deployed) aws_client.get_role_arn_for_name.assert_called_with(lambda_function_name) aws_client.delete_function.assert_called_with(lambda_function_name) aws_client.delete_role.assert_called_with('role_name')
def test_lambda_deployer_repeated_deploy(app_policy, sample_app): osutils = InMemoryOSUtils({'packages.zip': b'package contents'}) aws_client = mock.Mock(spec=TypedAWSClient) packager = mock.Mock(spec=LambdaDeploymentPackager) packager.deployment_package_filename.return_value = 'packages.zip' # Given the lambda function already exists: aws_client.lambda_function_exists.return_value = True aws_client.update_function.return_value = {"FunctionArn": "myarn"} # And given we don't want chalice to manage our iam role for the lambda # function: cfg = Config.create(chalice_stage='dev', chalice_app=sample_app, manage_iam_role=False, app_name='appname', iam_role_arn=True, project_dir='./myproject', environment_variables={"FOO": "BAR"}, lambda_timeout=120, lambda_memory_size=256, tags={'mykey': 'myvalue'}) aws_client.get_function_configuration.return_value = { 'Runtime': cfg.lambda_python_version, } prompter = mock.Mock(spec=NoPrompt) prompter.confirm.return_value = True d = LambdaDeployer(aws_client, packager, prompter, osutils, app_policy) # Doing a lambda deploy: lambda_function_name = 'lambda_function_name' deployed = DeployedResources('api', 'api_handler_arn', lambda_function_name, None, 'dev', None, None) d.deploy(cfg, deployed, 'dev') # Should result in injecting the latest app code. packager.inject_latest_app.assert_called_with('packages.zip', './myproject') # And should result in the lambda function being updated with the API. aws_client.update_function.assert_called_with( function_name=lambda_function_name, zip_contents=b'package contents', runtime=cfg.lambda_python_version, environment_variables={"FOO": "BAR"}, tags={ 'aws-chalice': 'version=%s:stage=%s:app=%s' % (chalice_version, 'dev', 'appname'), 'mykey': 'myvalue' }, timeout=120, memory_size=256)
def test_lambda_functions_not_required_from_dict(): older_version = { # Older versions of chalice did not include the # lambda_functions key. 'backend': 'api', 'api_handler_arn': 'arn', 'api_handler_name': 'name', 'rest_api_id': 'id', 'api_gateway_stage': 'stage', 'region': 'region', 'chalice_version': '1.0.0', } d = DeployedResources.from_dict(older_version) assert d.lambda_functions == {}
def test_rest_api_not_exists_with_preexisting_deploy(self): rest_api = self.create_rest_api_model() deployed_resources = { 'resources': [{ 'name': 'rest_api', 'resource_type': 'rest_api', 'rest_api_id': 'my_rest_api_id', }] } self.client.rest_api_exists.return_value = False remote_state = RemoteState(self.client, DeployedResources(deployed_resources)) assert not remote_state.resource_exists(rest_api) self.client.rest_api_exists.assert_called_with('my_rest_api_id')
def test_lambda_deployer_delete_already_deleted(capsys): lambda_function_name = 'lambda_name' aws_client = mock.Mock(spec=TypedAWSClient) aws_client.get_role_arn_for_name.return_value = 'arn_prefix/role_name' aws_client.delete_function.side_effect = ResourceDoesNotExistError( lambda_function_name) deployed = DeployedResources('api', 'api_handler_arn/lambda_name', lambda_function_name, None, 'dev', None, None) d = LambdaDeployer(aws_client, None, NoPrompt(), None, None) d.delete(deployed) # check that we printed that no lambda function with that name was found out, _ = capsys.readouterr() assert "No lambda function named %s found." % lambda_function_name in out aws_client.delete_function.assert_called_with(lambda_function_name)
def test_api_gateway_deployer_delete_already_deleted(capsys): rest_api_id = 'abcdef1234' aws_client = mock.Mock(spec=TypedAWSClient, region_name='us-west-2') aws_client.delete_rest_api.side_effect = ResourceDoesNotExistError( rest_api_id) deployed = DeployedResources(None, None, None, rest_api_id, 'dev', None, None) aws_client.rest_api_exists.return_value = True d = APIGatewayDeployer(aws_client) d.delete(deployed) # Check that we printed out that no rest api with that id was found out, _ = capsys.readouterr() assert "No rest API with id %s found." % rest_api_id in out aws_client.delete_rest_api.assert_called_with(rest_api_id)
def test_can_create_deployed_resource_from_dict(): d = DeployedResources.from_dict({ 'backend': 'api', 'api_handler_arn': 'arn', 'api_handler_name': 'name', 'rest_api_id': 'id', 'api_gateway_stage': 'stage', 'region': 'region', 'chalice_version': '1.0.0', }) assert d.backend == 'api' assert d.api_handler_arn == 'arn' assert d.api_handler_name == 'name' assert d.rest_api_id == 'id' assert d.api_gateway_stage == 'stage' assert d.region == 'region' assert d.chalice_version == '1.0.0'
def test_can_create_deployed_resource_from_dict(): d = DeployedResources.from_dict({ 'backend': 'api', 'api_handler_arn': 'arn', 'api_handler_name': 'name', 'rest_api_id': 'id', 'api_gateway_stage': 'stage', 'region': 'region', 'chalice_version': '1.0.0', }) assert d.backend == 'api' assert d.api_handler_arn == 'arn' assert d.api_handler_name == 'name' assert d.rest_api_id == 'id' assert d.api_gateway_stage == 'stage' assert d.region == 'region' assert d.chalice_version == '1.0.0'
def test_invoke_does_raise_if_unhandled_error(runner, mock_cli_factory): deployed_resources = DeployedResources({"resources": []}) mock_cli_factory.create_config_obj.return_value = FakeConfig( deployed_resources) invoke_handler = mock.Mock(spec=LambdaInvokeHandler) invoke_handler.invoke.side_effect = UnhandledLambdaError('foo') mock_cli_factory.create_lambda_invoke_handler.return_value = invoke_handler mock_reader = mock.Mock(spec=PipeReader) mock_reader.read.return_value = 'barbaz' mock_cli_factory.create_stdin_reader.return_value = mock_reader with runner.isolated_filesystem(): cli.create_new_project_skeleton('testproject') os.chdir('testproject') result = _run_cli_command(runner, cli.invoke, ['-n', 'foo'], cli_factory=mock_cli_factory) assert result.exit_code == 1 assert invoke_handler.invoke.call_args == mock.call('barbaz') assert 'Unhandled exception in Lambda function, details above.' \ in result.output
def test_dynamically_lookup_iam_role(self): remote_state = RemoteState( self.client, DeployedResources( {'resources': [{ 'name': 'rest_api', 'rest_api_id': 'foo' }]})) resource = models.ManagedIAMRole( resource_name='default-role', role_name='myrole', trust_policy={'trust': 'policy'}, policy=models.AutoGenIAMPolicy(document={'iam': 'policy'}), ) self.client.get_role_arn_for_name.return_value = 'my-role-arn' values = remote_state.resource_deployed_values(resource) assert values == { 'name': 'default-role', 'resource_type': 'iam_role', 'role_arn': 'my-role-arn', 'role_name': 'myrole' }
def test_api_gateway_deployer_redeploy_api(config_obj): aws_client = mock.Mock(spec=TypedAWSClient, region_name='us-west-2') # The rest_api_id does not exist which will trigger # the initial import deployed = DeployedResources(None, None, None, 'existing-id', 'dev', None, None) aws_client.rest_api_exists.return_value = True lambda_arn = 'arn:aws:lambda:us-west-2:account-id:function:func-name' d = APIGatewayDeployer(aws_client) d.deploy(config_obj, deployed, lambda_arn) aws_client.update_api_from_swagger.assert_called_with( 'existing-id', mock.ANY) second_arg = aws_client.update_api_from_swagger.call_args[0][1] assert isinstance(second_arg, dict) assert 'swagger' in second_arg aws_client.deploy_rest_api.assert_called_with('existing-id', 'dev') aws_client.add_permission_for_apigateway_if_needed.assert_called_with( 'func-name', 'us-west-2', 'account-id', 'existing-id', mock.ANY)
def test_deployer_delete_calls_deletes(): # Check that athe deployer class calls other deployer classes delete # methods. lambda_deploy = mock.Mock(spec=LambdaDeployer) apig_deploy = mock.Mock(spec=APIGatewayDeployer) cfg = mock.Mock(spec=Config) deployed_resources = DeployedResources.from_dict({ 'backend': 'api', 'api_handler_arn': 'lambda_arn', 'api_handler_name': 'lambda_name', 'rest_api_id': 'rest_id', 'api_gateway_stage': 'dev', 'region': 'us-west-2', 'chalice_version': '0', }) cfg.deployed_resources.return_value = deployed_resources d = Deployer(apig_deploy, lambda_deploy) d.delete(cfg) lambda_deploy.delete.assert_called_with(deployed_resources) apig_deploy.delete.assert_called_with(deployed_resources)
def setup_deployer_dependencies(self, app_policy): # This autouse fixture is used instead of ``setup_method`` because it: # * Is ran for every test # * Allows additional fixtures to be passed in to reduce the number # of fixtures that need to be supplied for the test methods. # * ``setup_method`` is called before fixtures get applied so # they cannot be applied to the ``setup_method`` or you will # will get a TypeError for too few arguments. self.package_name = 'packages.zip' self.package_contents = b'package contents' self.lambda_arn = 'lambda-arn' self.lambda_function_name = 'lambda_function_name' self.osutils = InMemoryOSUtils( {self.package_name: self.package_contents}) self.aws_client = mock.Mock(spec=TypedAWSClient) self.aws_client.lambda_function_exists.return_value = True self.aws_client.update_function.return_value = { 'FunctionArn': self.lambda_arn } self.aws_client.get_function_configuration.return_value = { 'Runtime': 'python2.7', } self.prompter = mock.Mock(spec=NoPrompt) self.prompter.confirm.return_value = True self.packager = mock.Mock(LambdaDeploymentPackager) self.packager.create_deployment_package.return_value =\ self.package_name self.app_policy = app_policy self.deployed_resources = DeployedResources('api', 'api_handler_arn', self.lambda_function_name, None, 'dev', None, None)
def test_lambda_deployer_repeated_deploy(app_policy, sample_app): osutils = InMemoryOSUtils({'packages.zip': b'package contents'}) aws_client = mock.Mock(spec=TypedAWSClient) packager = mock.Mock(spec=LambdaDeploymentPackager) packager.deployment_package_filename.return_value = 'packages.zip' # Given the lambda function already exists: aws_client.lambda_function_exists.return_value = True aws_client.update_function.return_value = {"FunctionArn": "myarn"} # And given we don't want chalice to manage our iam role for the lambda # function: cfg = Config.create( chalice_stage='dev', chalice_app=sample_app, manage_iam_role=False, app_name='appname', iam_role_arn=True, project_dir='./myproject', environment_variables={"FOO": "BAR"}, ) d = LambdaDeployer(aws_client, packager, None, osutils, app_policy) # Doing a lambda deploy: lambda_function_name = 'lambda_function_name' deployed = DeployedResources('api', 'api_handler_arn', lambda_function_name, None, 'dev', None, None) d.deploy(cfg, deployed, 'dev') # Should result in injecting the latest app code. packager.inject_latest_app.assert_called_with('packages.zip', './myproject') # And should result in the lambda function being updated with the API. aws_client.update_function.assert_called_with(lambda_function_name, 'package contents', {"FOO": "BAR"})
def test_deployed_resource_exists(): deployed = DeployedResources({'resources': [{'name': 'foo'}]}) assert deployed.resource_values('foo') == {'name': 'foo'} assert deployed.resource_names() == ['foo']
def test_deployed_resource_does_not_exist(): deployed = DeployedResources({'resources': [{'name': 'foo'}]}) with pytest.raises(ValueError): deployed.resource_values('bar')
class SmokeTestApplication(object): # Number of seconds to wait after redeploy before starting # to poll for successful 200. _REDEPLOY_SLEEP = 30 # Seconds to wait between poll attempts after redeploy. _POLLING_DELAY = 5 # Number of successful wait attempts before we consider the app # stabilized. _NUM_SUCCESS = 3 def __init__(self, deployed_values, stage_name, app_name, app_dir, region): self._deployed_resources = DeployedResources(deployed_values) self.stage_name = stage_name self.app_name = app_name # The name of the tmpdir where the app is copied. self.app_dir = app_dir self._has_redeployed = False self._region = region @property def url(self): return ("https://{rest_api_id}.execute-api.{region}.amazonaws.com/" "{api_gateway_stage}".format(rest_api_id=self.rest_api_id, region=self._region, api_gateway_stage='api')) @property def rest_api_id(self): return self._deployed_resources.resource_values( 'rest_api')['rest_api_id'] @property def websocket_api_id(self): return self._deployed_resources.resource_values( 'websocket_api')['websocket_api_id'] @property def websocket_connect_url(self): return ("wss://{websocket_api_id}.execute-api.{region}.amazonaws.com/" "{api_gateway_stage}".format( websocket_api_id=self.websocket_api_id, region=self._region, api_gateway_stage='api', )) @retry(max_attempts=10, delay=5) def get_json(self, url): try: return self._get_json(url) except requests.exceptions.HTTPError: pass def _get_json(self, url): if not url.startswith('/'): url = '/' + url response = requests.get(self.url + url) response.raise_for_status() return response.json() @retry(max_attempts=10, delay=5) def get_response(self, url, headers=None): try: return self._send_request('GET', url, headers=headers) except InternalServerError: pass def _send_request(self, http_method, url, headers=None, data=None): kwargs = {} if headers is not None: kwargs['headers'] = headers if data is not None: kwargs['data'] = data response = requests.request(http_method, self.url + url, **kwargs) if response.status_code >= 500: raise InternalServerError() return response @retry(max_attempts=10, delay=5) def post_response(self, url, headers=None, data=None): try: return self._send_request('POST', url, headers=headers, data=data) except InternalServerError: pass @retry(max_attempts=10, delay=5) def put_response(self, url): try: return self._send_request('PUT', url) except InternalServerError: pass @retry(max_attempts=10, delay=5) def options_response(self, url): try: return self._send_request('OPTIONS', url) except InternalServerError: pass def redeploy_once(self): # Redeploy the application once. If a redeploy # has already happened, this function is a noop. if self._has_redeployed: return new_file = os.path.join(self.app_dir, 'app-redeploy.py') original_app_py = os.path.join(self.app_dir, 'app.py') shutil.move(original_app_py, original_app_py + '.bak') shutil.copy(new_file, original_app_py) _deploy_app(self.app_dir) self._has_redeployed = True # Give it settling time before running more tests. time.sleep(self._REDEPLOY_SLEEP) for _ in range(self._NUM_SUCCESS): self._wait_for_stablize() time.sleep(self._POLLING_DELAY) def _wait_for_stablize(self): # After a deployment we sometimes need to wait for # API Gateway to propagate all of its changes. # We're going to give it num_attempts to give us a # 200 response before failing. return self.get_json('/')
class SmokeTestApplication(object): # Number of seconds to wait after redeploy before starting # to poll for successful 200. _REDEPLOY_SLEEP = 20 # Seconds to wait between poll attempts after redeploy. _POLLING_DELAY = 5 def __init__(self, deployed_values, stage_name, app_name, app_dir, region): self._deployed_resources = DeployedResources(deployed_values) self.stage_name = stage_name self.app_name = app_name # The name of the tmpdir where the app is copied. self.app_dir = app_dir self._has_redeployed = False self._region = region @property def url(self): return ( "https://{rest_api_id}.execute-api.{region}.amazonaws.com/" "{api_gateway_stage}".format(rest_api_id=self.rest_api_id, region=self._region, api_gateway_stage='api') ) @property def rest_api_id(self): return self._deployed_resources.resource_values( 'rest_api')['rest_api_id'] def get_json(self, url): if not url.startswith('/'): url = '/' + url response = requests.get(self.url + url) response.raise_for_status() return response.json() def redeploy_once(self): # Redeploy the application once. If a redeploy # has already happened, this function is a noop. if self._has_redeployed: return new_file = os.path.join(self.app_dir, 'app-redeploy.py') original_app_py = os.path.join(self.app_dir, 'app.py') shutil.move(original_app_py, original_app_py + '.bak') shutil.copy(new_file, original_app_py) self._clear_app_import() _deploy_app(self.app_dir) self._has_redeployed = True # Give it settling time before running more tests. time.sleep(self._REDEPLOY_SLEEP) self._wait_for_stablize() @retry(max_attempts=10, delay=5) def _wait_for_stablize(self): # After a deployment we sometimes need to wait for # API Gateway to propagate all of its changes. # We're going to give it num_attempts to give us a # 200 response before failing. try: return self.get_json('/') except requests.exceptions.HTTPError: pass def _clear_app_import(self): # Now that we're using `import` instead of `exec` we need # to clear out sys.modules in order to pick up the new # version of the app we just copied over. del sys.modules['app']
def no_deployed_values(): return DeployedResources({'resources': [], 'schema_version': '2.0'})
class SmokeTestApplication(object): # Number of seconds to wait after redeploy before starting # to poll for successful 200. _REDEPLOY_SLEEP = 20 # Seconds to wait between poll attempts after redeploy. _POLLING_DELAY = 5 def __init__(self, deployed_values, stage_name, app_name, app_dir, region): self._deployed_resources = DeployedResources(deployed_values) self.stage_name = stage_name self.app_name = app_name # The name of the tmpdir where the app is copied. self.app_dir = app_dir self._has_redeployed = False self._region = region @property def url(self): return ("https://{rest_api_id}.execute-api.{region}.amazonaws.com/" "{api_gateway_stage}".format(rest_api_id=self.rest_api_id, region=self._region, api_gateway_stage='api')) @property def rest_api_id(self): return self._deployed_resources.resource_values( 'rest_api')['rest_api_id'] @property def websocket_api_id(self): return self._deployed_resources.resource_values( 'websocket_api')['websocket_api_id'] @property def websocket_connect_url(self): return ("wss://{websocket_api_id}.execute-api.{region}.amazonaws.com/" "{api_gateway_stage}".format( websocket_api_id=self.websocket_api_id, region=self._region, api_gateway_stage='api', )) def get_json(self, url): if not url.startswith('/'): url = '/' + url response = requests.get(self.url + url) response.raise_for_status() return response.json() def redeploy_once(self): # Redeploy the application once. If a redeploy # has already happened, this function is a noop. if self._has_redeployed: return new_file = os.path.join(self.app_dir, 'app-redeploy.py') original_app_py = os.path.join(self.app_dir, 'app.py') shutil.move(original_app_py, original_app_py + '.bak') shutil.copy(new_file, original_app_py) _deploy_app(self.app_dir) self._has_redeployed = True # Give it settling time before running more tests. time.sleep(self._REDEPLOY_SLEEP) self._wait_for_stablize() @retry(max_attempts=10, delay=5) def _wait_for_stablize(self): # After a deployment we sometimes need to wait for # API Gateway to propagate all of its changes. # We're going to give it num_attempts to give us a # 200 response before failing. try: return self.get_json('/') except requests.exceptions.HTTPError: pass
def deployed_resources(self, chalice_stage_name): return DeployedResources(self._deployed_values)
def test_deployed_resource_exists(): deployed = DeployedResources( {'resources': [{'name': 'foo'}]} ) assert deployed.resource_values('foo') == {'name': 'foo'} assert deployed.resource_names() == ['foo']
def test_deployed_resource_does_not_exist(): deployed = DeployedResources( {'resources': [{'name': 'foo'}]} ) with pytest.raises(ValueError): deployed.resource_values('bar')