1
1
import { ISDK } from '../aws-auth' ;
2
- import { assetMetadataChanged , ChangeHotswapImpact , ChangeHotswapResult , HotswapOperation , HotswappableChangeCandidate , establishResourcePhysicalName } from './common' ;
2
+ import { ChangeHotswapImpact , ChangeHotswapResult , HotswapOperation , HotswappableChangeCandidate , establishResourcePhysicalName } from './common' ;
3
3
import { EvaluateCloudFormationTemplate } from './evaluate-cloudformation-template' ;
4
4
5
5
/**
@@ -15,22 +15,19 @@ export async function isHotswappableLambdaFunctionChange(
15
15
if ( typeof lambdaCodeChange === 'string' ) {
16
16
return lambdaCodeChange ;
17
17
} else {
18
- // verify that the Asset changed - otherwise,
19
- // it's a Code property-only change,
20
- // but not to an asset change
21
- // (for example, going from Code.fromAsset() to Code.fromInline())
22
- if ( ! assetMetadataChanged ( change ) ) {
23
- return ChangeHotswapImpact . REQUIRES_FULL_DEPLOYMENT ;
24
- }
25
-
26
18
const functionName = await establishResourcePhysicalName ( logicalId , change . newValue . Properties ?. FunctionName , evaluateCfnTemplate ) ;
27
19
if ( ! functionName ) {
28
20
return ChangeHotswapImpact . REQUIRES_FULL_DEPLOYMENT ;
29
21
}
30
22
23
+ const functionArn = await evaluateCfnTemplate . evaluateCfnExpression ( {
24
+ 'Fn::Sub' : 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:' + functionName ,
25
+ } ) ;
26
+
31
27
return new LambdaFunctionHotswapOperation ( {
32
28
physicalName : functionName ,
33
- code : lambdaCodeChange ,
29
+ functionArn : functionArn ,
30
+ resource : lambdaCodeChange ,
34
31
} ) ;
35
32
}
36
33
}
@@ -46,7 +43,7 @@ export async function isHotswappableLambdaFunctionChange(
46
43
*/
47
44
async function isLambdaFunctionCodeOnlyChange (
48
45
change : HotswappableChangeCandidate , evaluateCfnTemplate : EvaluateCloudFormationTemplate ,
49
- ) : Promise < LambdaFunctionCode | ChangeHotswapImpact > {
46
+ ) : Promise < LambdaFunctionChange | ChangeHotswapImpact > {
50
47
const newResourceType = change . newValue . Type ;
51
48
if ( newResourceType !== 'AWS::Lambda::Function' ) {
52
49
return ChangeHotswapImpact . REQUIRES_FULL_DEPLOYMENT ;
@@ -63,44 +60,95 @@ async function isLambdaFunctionCodeOnlyChange(
63
60
* even if only one of them was actually changed,
64
61
* which means we don't need the "old" values at all, and we can safely initialize these with just `''`.
65
62
*/
66
- let s3Bucket = '' , s3Key = '' ;
67
- let foundCodeDifference = false ;
68
63
// Make sure only the code in the Lambda function changed
69
64
const propertyUpdates = change . propertyUpdates ;
65
+ let code : LambdaFunctionCode | undefined = undefined ;
66
+ let tags : LambdaFunctionTags | undefined = undefined ;
67
+
70
68
for ( const updatedPropName in propertyUpdates ) {
71
69
const updatedProp = propertyUpdates [ updatedPropName ] ;
72
- for ( const newPropName in updatedProp . newValue ) {
73
- switch ( newPropName ) {
74
- case 'S3Bucket' :
75
- foundCodeDifference = true ;
76
- s3Bucket = await evaluateCfnTemplate . evaluateCfnExpression ( updatedProp . newValue [ newPropName ] ) ;
77
- break ;
78
- case 'S3Key' :
79
- foundCodeDifference = true ;
80
- s3Key = await evaluateCfnTemplate . evaluateCfnExpression ( updatedProp . newValue [ newPropName ] ) ;
81
- break ;
82
- default :
83
- return ChangeHotswapImpact . REQUIRES_FULL_DEPLOYMENT ;
84
- }
70
+
71
+ switch ( updatedPropName ) {
72
+ case 'Code' :
73
+ let foundCodeDifference = false ;
74
+ let s3Bucket = '' , s3Key = '' ;
75
+
76
+ for ( const newPropName in updatedProp . newValue ) {
77
+ switch ( newPropName ) {
78
+ case 'S3Bucket' :
79
+ foundCodeDifference = true ;
80
+ s3Bucket = await evaluateCfnTemplate . evaluateCfnExpression ( updatedProp . newValue [ newPropName ] ) ;
81
+ break ;
82
+ case 'S3Key' :
83
+ foundCodeDifference = true ;
84
+ s3Key = await evaluateCfnTemplate . evaluateCfnExpression ( updatedProp . newValue [ newPropName ] ) ;
85
+ break ;
86
+ default :
87
+ return ChangeHotswapImpact . REQUIRES_FULL_DEPLOYMENT ;
88
+ }
89
+ }
90
+ if ( foundCodeDifference ) {
91
+ code = {
92
+ s3Bucket,
93
+ s3Key,
94
+ } ;
95
+ }
96
+ break ;
97
+ case 'Tags' :
98
+ /*
99
+ * Tag updates are a bit odd; they manifest as two lists, are flagged only as
100
+ * `isDifferent`, and we have to reconcile them.
101
+ */
102
+ const tagUpdates : { [ tag : string ] : string | TagDeletion } = { } ;
103
+ if ( updatedProp ?. isDifferent ) {
104
+ updatedProp . newValue . forEach ( ( tag : CfnDiffTagValue ) => {
105
+ tagUpdates [ tag . Key ] = tag . Value ;
106
+ } ) ;
107
+
108
+ updatedProp . oldValue . forEach ( ( tag : CfnDiffTagValue ) => {
109
+ if ( tagUpdates [ tag . Key ] === undefined ) {
110
+ tagUpdates [ tag . Key ] = TagDeletion . DELETE ;
111
+ }
112
+ } ) ;
113
+
114
+ tags = { tagUpdates } ;
115
+ }
116
+ break ;
117
+ default :
118
+ return ChangeHotswapImpact . REQUIRES_FULL_DEPLOYMENT ;
85
119
}
86
120
}
87
121
88
- return foundCodeDifference
89
- ? {
90
- s3Bucket ,
91
- s3Key ,
92
- }
93
- : ChangeHotswapImpact . IRRELEVANT ;
122
+ return code || tags ? { code , tags } : ChangeHotswapImpact . IRRELEVANT ;
123
+ }
124
+
125
+ interface CfnDiffTagValue {
126
+ readonly Key : string ;
127
+ readonly Value : string ;
94
128
}
95
129
96
130
interface LambdaFunctionCode {
97
131
readonly s3Bucket : string ;
98
132
readonly s3Key : string ;
99
133
}
100
134
135
+ enum TagDeletion {
136
+ DELETE = - 1 ,
137
+ }
138
+
139
+ interface LambdaFunctionTags {
140
+ readonly tagUpdates : { [ tag : string ] : string | TagDeletion } ;
141
+ }
142
+
143
+ interface LambdaFunctionChange {
144
+ readonly code ?: LambdaFunctionCode ;
145
+ readonly tags ?: LambdaFunctionTags ;
146
+ }
147
+
101
148
interface LambdaFunctionResource {
102
149
readonly physicalName : string ;
103
- readonly code : LambdaFunctionCode ;
150
+ readonly functionArn : string ;
151
+ readonly resource : LambdaFunctionChange ;
104
152
}
105
153
106
154
class LambdaFunctionHotswapOperation implements HotswapOperation {
@@ -110,10 +158,47 @@ class LambdaFunctionHotswapOperation implements HotswapOperation {
110
158
}
111
159
112
160
public async apply ( sdk : ISDK ) : Promise < any > {
113
- return sdk . lambda ( ) . updateFunctionCode ( {
114
- FunctionName : this . lambdaFunctionResource . physicalName ,
115
- S3Bucket : this . lambdaFunctionResource . code . s3Bucket ,
116
- S3Key : this . lambdaFunctionResource . code . s3Key ,
117
- } ) . promise ( ) ;
161
+ const lambda = sdk . lambda ( ) ;
162
+ const resource = this . lambdaFunctionResource . resource ;
163
+ const operations : Promise < any > [ ] = [ ] ;
164
+
165
+ if ( resource . code !== undefined ) {
166
+ operations . push ( lambda . updateFunctionCode ( {
167
+ FunctionName : this . lambdaFunctionResource . physicalName ,
168
+ S3Bucket : resource . code . s3Bucket ,
169
+ S3Key : resource . code . s3Key ,
170
+ } ) . promise ( ) ) ;
171
+ }
172
+
173
+ if ( resource . tags !== undefined ) {
174
+ const tagsToDelete : string [ ] = Object . entries ( resource . tags . tagUpdates )
175
+ . filter ( ( [ _key , val ] ) => val === TagDeletion . DELETE )
176
+ . map ( ( [ key , _val ] ) => key ) ;
177
+
178
+ const tagsToSet : { [ tag : string ] : string } = { } ;
179
+ Object . entries ( resource . tags ! . tagUpdates )
180
+ . filter ( ( [ _key , val ] ) => val !== TagDeletion . DELETE )
181
+ . forEach ( ( [ tagName , tagValue ] ) => {
182
+ tagsToSet [ tagName ] = tagValue as string ;
183
+ } ) ;
184
+
185
+
186
+ if ( tagsToDelete . length > 0 ) {
187
+ operations . push ( lambda . untagResource ( {
188
+ Resource : this . lambdaFunctionResource . functionArn ,
189
+ TagKeys : tagsToDelete ,
190
+ } ) . promise ( ) ) ;
191
+ }
192
+
193
+ if ( Object . keys ( tagsToSet ) . length > 0 ) {
194
+ operations . push ( lambda . tagResource ( {
195
+ Resource : this . lambdaFunctionResource . functionArn ,
196
+ Tags : tagsToSet ,
197
+ } ) . promise ( ) ) ;
198
+ }
199
+ }
200
+
201
+ // run all of our updates in parallel
202
+ return Promise . all ( operations ) ;
118
203
}
119
204
}
0 commit comments