1313)
1414
1515from azul import (
16+ JSON ,
1617 R ,
1718 cache ,
1819 config ,
@@ -37,6 +38,7 @@ class Lambda:
3738 name : str
3839 role : str
3940 slot_location : Optional [str ]
41+ version : str
4042
4143 @property
4244 def is_contribution_lambda (self ) -> bool :
@@ -82,13 +84,15 @@ def has_notification_queue(handler) -> bool:
8284 def from_response (cls , response : 'FunctionConfigurationTypeDef' ) -> Self :
8385 name = response ['FunctionName' ]
8486 role = response ['Role' ]
87+ version = response ['Version' ]
8588 try :
8689 slot_location = response ['Environment' ]['Variables' ]['AZUL_TDR_SOURCE_LOCATION' ]
8790 except KeyError :
8891 slot_location = None
8992 return cls (name = name ,
9093 role = role ,
91- slot_location = slot_location )
94+ slot_location = slot_location ,
95+ version = version )
9296
9397 def __attrs_post_init__ (self ):
9498 if self .slot_location is None :
@@ -105,25 +109,65 @@ class Lambdas:
105109 def _lambda (self ):
106110 return aws .lambda_
107111
108- def list_lambdas (self ) -> list [Lambda ]:
112+ def list_lambdas (self ,
113+ deployment : str
114+ ) -> list [Lambda ]:
115+ """
116+ Return a list of AWS Lambda functions. Only the largest numbered version
117+ of each function will be included in the list.
118+
119+ :param deployment: Limit output to the specified deployment stage. If
120+ 'ALL', functions from all deployments will be
121+ returned.
122+ """
123+ paginator = self ._lambda .get_paginator ('list_functions' )
124+ lambda_prefixes = None if deployment == 'ALL' else [
125+ config .qualified_resource_name (lambda_name , stage = deployment )
126+ for lambda_name in config .lambda_names ()
127+ ]
128+ functions : dict [str , JSON ] = dict ()
129+ for response in paginator .paginate (FunctionVersion = 'ALL' ):
130+ for function in response ['Functions' ]:
131+ version = function ['Version' ]
132+ version = None if version == '$LATEST' else int (version )
133+ if version and (lambda_prefixes is None or any (
134+ function ['FunctionName' ].startswith (prefix )
135+ for prefix in lambda_prefixes
136+ )):
137+ name = function ['FunctionName' ]
138+ previous_function = functions .get (name )
139+ if previous_function is None or version > int (previous_function ['Version' ]):
140+ functions [name ] = function
109141 return [
110142 Lambda .from_response (function )
111- for response in self ._lambda .get_paginator ('list_functions' ).paginate ()
112- for function in response ['Functions' ]
143+ for function in functions .values ()
113144 ]
114145
146+ def get_function (self , function_name : str ) -> JSON :
147+ """
148+ Return the Lambda client `get_function()` response for the largest
149+ numbered version of the Lambda function.
150+ """
151+ paginator = self ._lambda .get_paginator ('list_versions_by_function' )
152+ params = {'FunctionName' : function_name }
153+ version = max ([
154+ int (function ['Version' ])
155+ for response in paginator .paginate (** params )
156+ for function in response ['Versions' ]
157+ if function ['Version' ] != '$LATEST'
158+ ])
159+ return self ._lambda .get_function (FunctionName = function_name ,
160+ Qualifier = str (version ))
161+
115162 def manage_lambdas (self , enabled : bool ):
116- paginator = self ._lambda .get_paginator ('list_functions' )
117- lambda_prefixes = [config .qualified_resource_name (lambda_infix ) for lambda_infix in config .lambda_names ()]
118- assert all (lambda_prefixes )
119- for lambda_page in paginator .paginate (FunctionVersion = 'ALL' , MaxItems = 500 ):
120- for lambda_name in [metadata ['FunctionName' ] for metadata in lambda_page ['Functions' ]]:
121- if any (lambda_name .startswith (prefix ) for prefix in lambda_prefixes ):
122- self .manage_lambda (lambda_name , enabled )
163+ for function in self .list_lambdas (deployment = config .deployment_stage ):
164+ self .manage_lambda (function .name , enabled )
123165
124166 def manage_lambda (self , lambda_name : str , enable : bool ):
125- lambda_settings = self ._lambda . get_function (FunctionName = lambda_name )
167+ lambda_settings = self .get_function (function_name = lambda_name )
126168 lambda_arn = lambda_settings ['Configuration' ]['FunctionArn' ]
169+ # Lambda does not support adding tags to function aliases or versions
170+ lambda_arn , _ , _ = lambda_arn .rpartition (':' )
127171 lambda_tags = self ._lambda .list_tags (Resource = lambda_arn )['Tags' ]
128172 lambda_name = lambda_settings ['Configuration' ]['FunctionName' ]
129173 if enable :
@@ -132,13 +176,15 @@ def manage_lambda(self, lambda_name: str, enable: bool):
132176
133177 if original_concurrency_limit is not None :
134178 log .info (f'Setting concurrency limit for { lambda_name } back to { original_concurrency_limit } .' )
179+ # Concurrency settings apply to the function as a whole,
180+ # including all published versions and the unpublished
181+ # version
135182 self ._lambda .put_function_concurrency (FunctionName = lambda_name ,
136183 ReservedConcurrentExecutions = original_concurrency_limit )
137184 else :
138185 log .info (f'Removed concurrency limit for { lambda_name } .' )
139186 self ._lambda .delete_function_concurrency (FunctionName = lambda_name )
140187
141- lambda_arn = lambda_settings ['Configuration' ]['FunctionArn' ]
142188 self ._lambda .untag_resource (Resource = lambda_arn , TagKeys = [self .tag_name ])
143189 else :
144190 log .warning (f'{ lambda_name } is already enabled.' )
@@ -156,7 +202,7 @@ def manage_lambda(self, lambda_name: str, enable: bool):
156202
157203 log .info (f'Setting concurrency limit for { lambda_name } to zero.' )
158204 new_tag = {self .tag_name : repr (concurrency_limit )}
159- self ._lambda .tag_resource (Resource = lambda_settings [ 'Configuration' ][ 'FunctionArn' ] , Tags = new_tag )
205+ self ._lambda .tag_resource (Resource = lambda_arn , Tags = new_tag )
160206 self ._lambda .put_function_concurrency (FunctionName = lambda_name , ReservedConcurrentExecutions = 0 )
161207 else :
162208 log .warning (f'{ lambda_name } is already disabled.' )
@@ -165,7 +211,7 @@ def reset_lambda_roles(self):
165211 client = self ._lambda
166212 lambda_names = set (config .lambda_names ())
167213
168- for lambda_ in self .list_lambdas ():
214+ for lambda_ in self .list_lambdas (deployment = 'ALL' ):
169215 for lambda_name in lambda_names :
170216 if lambda_ .name .startswith (config .qualified_resource_name (lambda_name )):
171217 other_lambda_name = one (lambda_names - {lambda_name })
@@ -174,6 +220,8 @@ def reset_lambda_roles(self):
174220 config .qualified_resource_name (other_lambda_name )
175221 )
176222 log .info ('Temporarily updating %r to role %r' , lambda_ .name , temporary_role )
223+ # You can’t modify the configuration of a published version,
224+ # only the unpublished version
177225 client .update_function_configuration (FunctionName = lambda_ .name ,
178226 Role = temporary_role )
179227 log .info ('Updating %r to role %r' , lambda_ .name , lambda_ .role )
0 commit comments