Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce completely separate chalice stages #264

Merged
merged 3 commits into from
Mar 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a lot of arguments. Wonder if this can be simplified but seems to be in all practicality is a namedtuple. So I don't feel too strongly about needing to change it.

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