Skip to content

Commit 540493d

Browse files
committed
Add Lambda versioning and alias support
1 parent 752d5bd commit 540493d

14 files changed

Lines changed: 849 additions & 59 deletions

File tree

chalice/awsclient.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,87 @@ def _update_function_config(
10021002
if kwargs:
10031003
self._do_update_function_config(function_name, kwargs)
10041004

1005+
def publish_function_version(
1006+
self, function_name: str
1007+
) -> Dict[str, Any]:
1008+
lambda_client = self._client('lambda')
1009+
try:
1010+
result = lambda_client.publish_version(
1011+
FunctionName=function_name
1012+
)
1013+
except lambda_client.exceptions.ResourceConflictException:
1014+
result = self._latest_published_function_version(function_name)
1015+
self._wait_for_active_function_version(
1016+
function_name, result['Version'])
1017+
return result
1018+
1019+
def _latest_published_function_version(
1020+
self, function_name: str
1021+
) -> Dict[str, Any]:
1022+
lambda_client = self._client('lambda')
1023+
latest = None # type: Optional[Dict[str, Any]]
1024+
kwargs = {'FunctionName': function_name} # type: Dict[str, Any]
1025+
while True:
1026+
response = lambda_client.list_versions_by_function(**kwargs)
1027+
for version in response.get('Versions', []):
1028+
version_name = version.get('Version')
1029+
if version_name is not None and version_name.isdigit():
1030+
if latest is None:
1031+
latest = version
1032+
elif int(version_name) > int(latest.get('Version', '0')):
1033+
latest = version
1034+
marker = response.get('NextMarker')
1035+
if marker is None:
1036+
break
1037+
kwargs['Marker'] = marker
1038+
if latest is None:
1039+
raise RuntimeError(
1040+
'Unable to find published version for %s' % function_name
1041+
)
1042+
return latest
1043+
1044+
def _wait_for_active_function_version(
1045+
self, function_name: str, version: str
1046+
) -> None:
1047+
lambda_client = self._client('lambda')
1048+
function_version = '%s:%s' % (function_name, version)
1049+
for _ in range(self.LAMBDA_CREATE_ATTEMPTS):
1050+
config = lambda_client.get_function_configuration(
1051+
FunctionName=function_version
1052+
)
1053+
active = config.get('State') == 'Active'
1054+
updated = config.get('LastUpdateStatus') in (None, 'Successful')
1055+
if active and updated:
1056+
return
1057+
self._sleep(self.DELAY_TIME)
1058+
raise RuntimeError(
1059+
'Timed out waiting for published version %s to become active' %
1060+
function_version
1061+
)
1062+
1063+
def create_or_update_function_alias(
1064+
self, function_name: str, alias_name: str, function_version: str
1065+
) -> Dict[str, Any]:
1066+
lambda_client = self._client('lambda')
1067+
try:
1068+
alias = lambda_client.get_alias(
1069+
FunctionName=function_name,
1070+
Name=alias_name,
1071+
)
1072+
except lambda_client.exceptions.ResourceNotFoundException:
1073+
return lambda_client.create_alias(
1074+
FunctionName=function_name,
1075+
Name=alias_name,
1076+
FunctionVersion=function_version,
1077+
)
1078+
if alias.get('FunctionVersion') == function_version:
1079+
return alias
1080+
return lambda_client.update_alias(
1081+
FunctionName=function_name,
1082+
Name=alias_name,
1083+
FunctionVersion=function_version,
1084+
)
1085+
10051086
def _do_update_function_config(
10061087
self, function_name: str, kwargs: Dict[str, Any]
10071088
) -> None:
@@ -1854,6 +1935,7 @@ def update_lambda_event_source(
18541935
batch_size: int,
18551936
maximum_batching_window_in_seconds: Optional[int] = 0,
18561937
maximum_concurrency: Optional[int] = None,
1938+
function_name: Optional[str] = None,
18571939
) -> None:
18581940
lambda_client = self._client('lambda')
18591941
batch_window = maximum_batching_window_in_seconds
@@ -1866,6 +1948,8 @@ def update_lambda_event_source(
18661948
kwargs['ScalingConfig'] = {
18671949
'MaximumConcurrency': maximum_concurrency
18681950
}
1951+
if function_name is not None:
1952+
kwargs['FunctionName'] = function_name
18691953
self._call_client_method_with_retries(
18701954
lambda_client.update_event_source_mapping,
18711955
kwargs,

chalice/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,12 @@ def reserved_concurrency(self) -> int:
343343
varies_per_chalice_stage=True,
344344
varies_per_function=True)
345345

346+
@property
347+
def lambda_alias(self) -> Optional[str]:
348+
return self._chain_lookup('lambda_alias',
349+
varies_per_chalice_stage=True,
350+
varies_per_function=True)
351+
346352
def scope(self, chalice_stage: str, function_name: str) -> Config:
347353
# Used to create a new config object that's scoped to a different
348354
# stage and/or function. This creates a completely separate copy.

chalice/deploy/appgraph.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,7 @@ def _build_lambda_function(
575575
layers=lambda_layers,
576576
managed_layer=self._get_managed_lambda_layer(config),
577577
xray=config.xray_enabled,
578+
lambda_alias=config.lambda_alias,
578579
)
579580
self._inject_role_traits(function, role)
580581
return function

chalice/deploy/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ class LambdaFunction(ManagedModel):
205205
layers: List[str]
206206
managed_layer: Opt[LambdaLayer] = None
207207
log_group: Opt[LogGroup] = None
208+
lambda_alias: Opt[str] = None
208209

209210
def dependencies(self) -> List[Model]:
210211
resources: List[Model] = []

0 commit comments

Comments
 (0)