Skip to content
Draft
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
84 changes: 84 additions & 0 deletions chalice/awsclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,87 @@ def _update_function_config(
if kwargs:
self._do_update_function_config(function_name, kwargs)

def publish_function_version(
self, function_name: str
) -> Dict[str, Any]:
lambda_client = self._client('lambda')
try:
result = lambda_client.publish_version(
FunctionName=function_name
)
except lambda_client.exceptions.ResourceConflictException:
result = self._latest_published_function_version(function_name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

When ResourceConflictException is caught, _latest_published_function_version would return the version form the previous deploy. Instead of using the latest published version when there's a conflict, is it possible to add a delay, similar how its done here?

self._wait_for_active_function_version(
function_name, result['Version'])
return result

def _latest_published_function_version(
self, function_name: str
) -> Dict[str, Any]:
lambda_client = self._client('lambda')
latest = None # type: Optional[Dict[str, Any]]
kwargs = {'FunctionName': function_name} # type: Dict[str, Any]
while True:
response = lambda_client.list_versions_by_function(**kwargs)
for version in response.get('Versions', []):
version_name = version.get('Version')
if version_name is not None and version_name.isdigit():
if latest is None:
latest = version
elif int(version_name) > int(latest.get('Version', '0')):
latest = version
marker = response.get('NextMarker')
if marker is None:
break
kwargs['Marker'] = marker
if latest is None:
raise RuntimeError(
'Unable to find published version for %s' % function_name
)
return latest

def _wait_for_active_function_version(
self, function_name: str, version: str
) -> None:
lambda_client = self._client('lambda')
function_version = '%s:%s' % (function_name, version)
for _ in range(self.LAMBDA_CREATE_ATTEMPTS):
config = lambda_client.get_function_configuration(
FunctionName=function_version
)
active = config.get('State') == 'Active'
updated = config.get('LastUpdateStatus') in (None, 'Successful')
if active and updated:
return
self._sleep(self.DELAY_TIME)
raise RuntimeError(
'Timed out waiting for published version %s to become active' %
function_version
)

def create_or_update_function_alias(
self, function_name: str, alias_name: str, function_version: str
) -> Dict[str, Any]:
lambda_client = self._client('lambda')
try:
alias = lambda_client.get_alias(
FunctionName=function_name,
Name=alias_name,
)
except lambda_client.exceptions.ResourceNotFoundException:
return lambda_client.create_alias(
FunctionName=function_name,
Name=alias_name,
FunctionVersion=function_version,
)
if alias.get('FunctionVersion') == function_version:
return alias
return lambda_client.update_alias(
FunctionName=function_name,
Name=alias_name,
FunctionVersion=function_version,
)

def _do_update_function_config(
self, function_name: str, kwargs: Dict[str, Any]
) -> None:
Expand Down Expand Up @@ -1854,6 +1935,7 @@ def update_lambda_event_source(
batch_size: int,
maximum_batching_window_in_seconds: Optional[int] = 0,
maximum_concurrency: Optional[int] = None,
function_name: Optional[str] = None,
) -> None:
lambda_client = self._client('lambda')
batch_window = maximum_batching_window_in_seconds
Expand All @@ -1866,6 +1948,8 @@ def update_lambda_event_source(
kwargs['ScalingConfig'] = {
'MaximumConcurrency': maximum_concurrency
}
if function_name is not None:
kwargs['FunctionName'] = function_name
self._call_client_method_with_retries(
lambda_client.update_event_source_mapping,
kwargs,
Expand Down
6 changes: 6 additions & 0 deletions chalice/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,12 @@ def reserved_concurrency(self) -> int:
varies_per_chalice_stage=True,
varies_per_function=True)

@property
def lambda_alias(self) -> Optional[str]:
return self._chain_lookup('lambda_alias',
varies_per_chalice_stage=True,
varies_per_function=True)

Comment on lines +346 to +351

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

  • One small UX question: should lambda_alias be validated somewhere in the config validation path against Lambda alias naming constraints?

  • For example, Chalice could reject values like $LATEST, numeric-only aliases, empty strings, or names containing unsupported characters before deployment. That might give users a clearer Chalice-level error instead of surfacing a lower-level Lambda API error later.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

+1 -> A validation function similar to the ones in validate.py would work well here.

def scope(self, chalice_stage: str, function_name: str) -> Config:
# Used to create a new config object that's scoped to a different
# stage and/or function. This creates a completely separate copy.
Expand Down
1 change: 1 addition & 0 deletions chalice/deploy/appgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,7 @@ def _build_lambda_function(
layers=lambda_layers,
managed_layer=self._get_managed_lambda_layer(config),
xray=config.xray_enabled,
lambda_alias=config.lambda_alias,
)
self._inject_role_traits(function, role)
return function
Expand Down
1 change: 1 addition & 0 deletions chalice/deploy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ class LambdaFunction(ManagedModel):
layers: List[str]
managed_layer: Opt[LambdaLayer] = None
log_group: Opt[LogGroup] = None
lambda_alias: Opt[str] = None

def dependencies(self) -> List[Model]:
resources: List[Model] = []
Expand Down
Loading
Loading