Skip to content

Commit

Permalink
Merge branch 'separate-stages' into package
Browse files Browse the repository at this point in the history
* separate-stages:
  Add additional test coverage
  Use modeled exceptions instead of generic exception
  Introduce completely separate chalice stages
  • Loading branch information
jamesls committed Mar 29, 2017
2 parents 06239b7 + 06b3561 commit 7607b22
Show file tree
Hide file tree
Showing 10 changed files with 374 additions and 171 deletions.
69 changes: 35 additions & 34 deletions chalice/awsclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
import shutil
import json

import botocore.session
import botocore.exceptions
import botocore.session # noqa
from typing import Any, Optional, Dict, Callable, List # noqa


Expand All @@ -40,14 +39,12 @@ def __init__(self, session, sleep=time.sleep):

def lambda_function_exists(self, name):
# type: (str) -> bool
client = self._client('lambda')
try:
self._client('lambda').get_function(FunctionName=name)
except botocore.exceptions.ClientError as e:
error = e.response['Error']
if error['Code'] == 'ResourceNotFoundException':
return False
raise
return True
client.get_function(FunctionName=name)
return True
except client.exceptions.ResourceNotFoundException:
return False

def create_function(self, function_name, role_arn, zip_contents):
# type: (str, str, str) -> str
Expand All @@ -64,35 +61,30 @@ def create_function(self, function_name, role_arn, zip_contents):
while True:
try:
response = client.create_function(**kwargs)
except botocore.exceptions.ClientError as e:
code = e.response['Error'].get('Code')
if code == 'InvalidParameterValueException':
# We're assuming that if we receive an
# InvalidParameterValueException, it's because
# the role we just created can't be used by
# Lambda.
self._sleep(self.DELAY_TIME)
attempts += 1
if attempts >= self.LAMBDA_CREATE_ATTEMPTS:
raise
continue
raise
except client.exceptions.InvalidParameterValueException:
# We're assuming that if we receive an
# InvalidParameterValueException, it's because
# the role we just created can't be used by
# Lambda.
self._sleep(self.DELAY_TIME)
attempts += 1
if attempts >= self.LAMBDA_CREATE_ATTEMPTS:
raise
continue
return response['FunctionArn']

def update_function_code(self, function_name, zip_contents):
# type: (str, str) -> None
self._client('lambda').update_function_code(
# type: (str, str) -> Dict[str, Any]
return self._client('lambda').update_function_code(
FunctionName=function_name, ZipFile=zip_contents)

def get_role_arn_for_name(self, name):
# type: (str) -> str
client = self._client('iam')
try:
role = self._client('iam').get_role(RoleName=name)
except botocore.exceptions.ClientError as e:
error = e.response['Error']
if error['Code'] == 'NoSuchEntity':
raise ValueError("No role ARN found for: %s" % name)
raise
role = client.get_role(RoleName=name)
except client.exceptions.NoSuchEntityException:
raise ValueError("No role ARN found for: %s" % name)
return role['Role']['Arn']

def delete_role_policy(self, role_name, policy_name):
Expand Down Expand Up @@ -138,6 +130,16 @@ def get_rest_api_id(self, name):
return api['id']
return None

def rest_api_exists(self, rest_api_id):
# type: (str) -> bool
"""Check if an an API Gateway REST API exists."""
client = self._client('apigateway')
try:
client.get_rest_api(restApiId=rest_api_id)
return True
except client.exceptions.NotFoundException:
return False

def import_rest_api(self, swagger_document):
# type: (Dict[str, Any]) -> str
client = self._client('apigateway')
Expand Down Expand Up @@ -174,12 +176,11 @@ def add_permission_for_apigateway_if_needed(self, function_name,
"""
has_necessary_permissions = False
client = self._client('lambda')
try:
policy = self.get_function_policy(function_name)
except botocore.exceptions.ClientError as e:
error = e.response['Error']
if error['Code'] == 'ResourceNotFoundException':
pass
except client.exceptions.ResourceNotFoundException:
pass
else:
source_arn = self._build_source_arn_str(region_name, account_id,
rest_api_id)
Expand Down
13 changes: 10 additions & 3 deletions chalice/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from chalice.deploy import deployer
from chalice.logs import LogRetriever
from chalice.package import create_app_packager
from chalice.utils import create_zip_file
from chalice.utils import create_zip_file, record_deployed_values


TEMPLATE_APP = """\
Expand Down Expand Up @@ -172,13 +172,20 @@ def local(ctx, port=8000):
def deploy(ctx, autogen_policy, profile, stage):
# type: (click.Context, bool, str, str) -> None
config = create_config_obj(
ctx, stage_name=stage, autogen_policy=autogen_policy,
# Note: stage_name is not the same thing as the chalice stage.
# This is a legacy artifact that just means "API gateway stage",
# or for our purposes, the URL prefix.
ctx, stage_name='dev', autogen_policy=autogen_policy,
profile=profile)
if stage is None:
stage = 'dev'
session = create_botocore_session(profile=config.profile,
debug=ctx.obj['debug'])
d = deployer.create_default_deployer(session=session, prompter=click)
try:
d.deploy(config)
deployed_values = d.deploy(config, stage_name=stage)
record_deployed_values(deployed_values, os.path.join(
config.project_dir, '.chalice', 'deployed.json'))
except botocore.exceptions.NoRegionError:
e = click.ClickException("No region configured. "
"Either export the AWS_DEFAULT_REGION "
Expand Down
62 changes: 56 additions & 6 deletions chalice/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from typing import Dict, Any # noqa
import os
import json

from typing import Dict, Any, Optional # noqa
from chalice.app import Chalice # noqa

StrMap = Dict[str, Any]
Expand Down Expand Up @@ -29,11 +32,6 @@ def create(cls, **kwargs):
# type: (**Any) -> Config
return cls(user_provided_params=kwargs.copy())

@property
def lambda_arn(self):
# type: () -> str
return self._chain_lookup('lambda_arn')

@property
def profile(self):
# type: () -> str
Expand Down Expand Up @@ -97,3 +95,55 @@ def _chain_lookup(self, name):
for cfg_dict in all_dicts:
if isinstance(cfg_dict, dict) and cfg_dict.get(name) is not None:
return cfg_dict[name]

@property
def lambda_arn(self):
# type: () -> str
return self._chain_lookup('lambda_arn')

def deployed_resources(self, stage_name):
# type: (str) -> Optional[DeployedResources]
"""Return resources associated with a given stage.
If a deployment to a given stage has never happened,
this method will return a value of None.
"""
# This is arguably the wrong level of abstraction.
# We might be able to move this elsewhere.
deployed_file = os.path.join(self.project_dir, '.chalice',
'deployed.json')
if not os.path.isfile(deployed_file):
return None
with open(deployed_file, 'r') as f:
data = json.load(f)
if stage_name not in data:
return None
return DeployedResources.from_dict(data[stage_name])


class DeployedResources(object):
def __init__(self, backend, api_handler_arn,
api_handler_name, rest_api_id, api_gateway_stage,
region, chalice_version):
# type: (str, str, str, str, str, str, str) -> None
self.backend = backend
self.api_handler_arn = api_handler_arn
self.api_handler_name = api_handler_name
self.rest_api_id = rest_api_id
self.api_gateway_stage = api_gateway_stage
self.region = region
self.chalice_version = chalice_version

@classmethod
def from_dict(cls, data):
# type: (Dict[str, str]) -> DeployedResources
return cls(
data['backend'],
data['api_handler_arn'],
data['api_handler_name'],
data['rest_api_id'],
data['api_gateway_stage'],
data['region'],
data['chalice_version'],
)
Loading

0 comments on commit 7607b22

Please sign in to comment.