@@ -522,10 +522,9 @@ class AwsBatchTaskHandlerTest extends Specification {
522
522
vol2 : ' /here:/there:ro' ,
523
523
vol3 : ' /this:/that:rw' ,
524
524
]
525
- and :
526
- handler. addVolumeMountsToContainer(mounts, containerModel)
527
-
525
+
528
526
when :
527
+ handler. addVolumeMountsToContainer(mounts, containerModel)
529
528
def container = containerModel. toBatchContainerProperties()
530
529
then :
531
530
container. volumes(). size() == 4
@@ -578,7 +577,6 @@ class AwsBatchTaskHandlerTest extends Specification {
578
577
result. containerProperties. logConfiguration == null
579
578
result. containerProperties. mountPoints == null
580
579
result. containerProperties. privileged == false
581
-
582
580
when :
583
581
result = handler. makeJobDefRequest(task)
584
582
then :
@@ -907,7 +905,7 @@ class AwsBatchTaskHandlerTest extends Specification {
907
905
then :
908
906
1 * handler. isCompleted() >> false
909
907
1 * handler. getMachineInfo() >> new CloudMachineInfo (' x1.large' , ' us-east-1b' , PriceModel . spot)
910
-
908
+
911
909
and :
912
910
trace. native_id == ' xyz-123'
913
911
trace. executorName == ' awsbatch'
@@ -1078,7 +1076,7 @@ class AwsBatchTaskHandlerTest extends Specification {
1078
1076
1079
1077
expect :
1080
1078
handler. normaliseJobId(JOB_ID ) == EXPECTED
1081
-
1079
+
1082
1080
where :
1083
1081
JOB_ID | EXPECTED
1084
1082
null | null
@@ -1097,7 +1095,7 @@ class AwsBatchTaskHandlerTest extends Specification {
1097
1095
task. getName() >> NAME
1098
1096
and :
1099
1097
result == EXPECTED
1100
-
1098
+
1101
1099
where :
1102
1100
ENV | NAME | EXPECTED
1103
1101
[:] | ' foo' | ' foo'
@@ -1134,8 +1132,164 @@ class AwsBatchTaskHandlerTest extends Specification {
1134
1132
2 | true | false | 2
1135
1133
and :
1136
1134
null | true | true | 5 // <-- default to 5
1137
- 0 | true | true | 5 // <-- default to 5
1135
+ 0 | true | true | 5 // <-- default to 5
1138
1136
1 | true | true | 1
1139
1137
2 | true | true | 2
1140
1138
}
1139
+
1140
+ @Unroll
1141
+ def ' should sanitize AWS Batch label' () {
1142
+ given :
1143
+ def handler = Spy (AwsBatchTaskHandler )
1144
+
1145
+ expect :
1146
+ handler. sanitizeAwsBatchLabel(INPUT , MAX_LENGTH ) == EXPECTED
1147
+
1148
+ where :
1149
+ INPUT | MAX_LENGTH | EXPECTED
1150
+ // Valid labels that don't need sanitization
1151
+ ' validLabel' | 50 | ' validLabel'
1152
+ ' valid-label_123' | 50 | ' valid-label_123'
1153
+ ' valid.label:test/path=value+more' | 50 | ' valid.label:test/path=value+more'
1154
+ ' label with spaces' | 50 | ' label with spaces'
1155
+ ' label-with@symbol' | 50 | ' label-with@symbol'
1156
+ and :
1157
+ // Labels with invalid characters
1158
+ ' label#with#hash' | 50 | ' label_with_hash'
1159
+ ' label$with%special&chars' | 50 | ' label_with_special_chars'
1160
+ ' label(with)brackets[and]braces{}' | 50 | ' label_with_brackets_and_braces__'
1161
+ ' label*with?wildcards' | 50 | ' label_with_wildcards'
1162
+ ' unicode_λαβελ_test' | 50 | ' unicode____abel_test'
1163
+ and :
1164
+ // Multiple consecutive invalid characters
1165
+ ' label###multiple###hashes' | 50 | ' label_multiple_hashes'
1166
+ ' label multiple spaces' | 50 | ' label_multiple_spaces'
1167
+ ' label___multiple___underscores' | 50 | ' label_multiple_underscores'
1168
+ ' label$%^&*special*&^%$chars' | 50 | ' label_special_chars'
1169
+ and :
1170
+ // Leading/trailing invalid characters
1171
+ ' ###leading-hashes' | 50 | ' leading-hashes'
1172
+ ' trailing-hashes###' | 50 | ' trailing-hashes'
1173
+ ' leading-spaces' | 50 | ' leading-spaces'
1174
+ ' trailing-spaces ' | 50 | ' trailing-spaces'
1175
+ ' ___leading-underscores' | 50 | ' leading-underscores'
1176
+ ' trailing-underscores___' | 50 | ' trailing-underscores'
1177
+ and :
1178
+ // Length truncation
1179
+ ' very-long-label-that-exceeds-max' | 10 | ' very-long-'
1180
+ ' very-long-label-ending-with-_' | 25 | ' very-long-label-ending-w'
1181
+ ' very-long-label-ending-with-___' | 28 | ' very-long-label-ending-w'
1182
+ and :
1183
+ // Edge cases
1184
+ null | 50 | null
1185
+ ' ' | 50 | ' '
1186
+ ' ' | 50 | null
1187
+ ' ___' | 50 | null
1188
+ ' ###' | 50 | null
1189
+ ' _' | 50 | null
1190
+ ' ' | 50 | null
1191
+ ' #' | 50 | null
1192
+ and :
1193
+ // Complex real-world scenarios
1194
+
1195
+ ' workflow-run-2024/01/15' | 50 | ' workflow-run-2024/01/15'
1196
+ ' task.hash.0x1234abcd' | 50 | ' task.hash.0x1234abcd'
1197
+ ' pipeline#name%with&special*chars' | 50 | ' pipeline_name_with_special_chars'
1198
+ ' session-id:abc123#$%' | 50 | ' session-id:abc123'
1199
+ }
1200
+
1201
+ @Unroll
1202
+ def ' should sanitize AWS Batch labels map' () {
1203
+ given :
1204
+ def handler = Spy (AwsBatchTaskHandler )
1205
+
1206
+ expect :
1207
+ handler. sanitizeAwsBatchLabels(INPUT ) == EXPECTED
1208
+
1209
+ where :
1210
+ INPUT | EXPECTED
1211
+ // Null/empty input
1212
+ null | null
1213
+ [:] | [:]
1214
+ and :
1215
+ // Valid labels
1216
+ [validKey : ' validValue' ] | [validKey : ' validValue' ]
1217
+ [' valid-key_123' : ' valid-value_456' ] | [' valid-key_123' : ' valid-value_456' ]
1218
+ [' key.with:path/chars=test+more@symbol' : ' value with spaces' ] | [' key.with:path/chars=test+more@symbol' : ' value with spaces' ]
1219
+ and :
1220
+ // Invalid characters in keys and values
1221
+ [' key#with#hash' : ' value$with%special&chars' ] | [' key_with_hash' : ' value_with_special_chars' ]
1222
+ [' key(brackets)' : ' value[squares]{braces}' ] | [' key_brackets_' : ' value_squares__braces_' ]
1223
+ [' unicode_λkey' : ' unicode_λvalue' ] | [' unicode__key' : ' unicode__value' ]
1224
+ and :
1225
+ // Multiple entries with mixed validity
1226
+ [' validKey' : ' validValue' , ' invalid#key' : ' invalid$value' , ' another.valid:key' : ' another+valid@value' ] |
1227
+ [' validKey' : ' validValue' , ' invalid_key' : ' invalid_value' , ' another.valid:key' : ' another+valid@value' ]
1228
+ and :
1229
+ // Entries that should be filtered out (null/empty after sanitization)
1230
+ [' validKey' : ' validValue' , ' ###' : ' $$$' , ' ' : ' %%%' , ' goodKey' : ' goodValue' ] |
1231
+ [' validKey' : ' validValue' , ' goodKey' : ' goodValue' ]
1232
+ and :
1233
+ // Null keys or values
1234
+ [' validKey' : null , null : ' validValue' , ' goodKey' : ' goodValue' ] |
1235
+ [' goodKey' : ' goodValue' ]
1236
+ and :
1237
+ // Real-world example with Nextflow resource labels
1238
+ [
1239
+ ' uniqueRunId' : ' tw-12345-workflow-run' ,
1240
+ ' taskHash' : ' task.hash.0x1a2b3c4d#special' ,
1241
+ ' pipelineUser' :
' [email protected] ' ,
1242
+ ' pipelineRunName' : ' my-pipeline-run(2024)' ,
1243
+ ' pipelineSessionId' : ' session#id$with%special&chars' ,
1244
+ ' pipelineResume' : ' false' ,
1245
+ ' pipelineName' : ' my_pipeline/name:version+tag'
1246
+ ] |
1247
+ [
1248
+ ' uniqueRunId' : ' tw-12345-workflow-run' ,
1249
+ ' taskHash' : ' task.hash.0x1a2b3c4d_special' ,
1250
+ ' pipelineUser' :
' [email protected] ' ,
1251
+ ' pipelineRunName' : ' my-pipeline-run_2024_' ,
1252
+ ' pipelineSessionId' : ' session_id_with_special_chars' ,
1253
+ ' pipelineResume' : ' false' ,
1254
+ ' pipelineName' : ' my_pipeline/name:version+tag'
1255
+ ]
1256
+ }
1257
+
1258
+ def ' should apply label sanitization in submit request' () {
1259
+ given :
1260
+ def task = Mock (TaskRun )
1261
+ task. getName() >> ' batch-task'
1262
+ task. getConfig() >> new TaskConfig (
1263
+ memory : ' 8GB' ,
1264
+ cpus : 4 ,
1265
+ resourceLabels : [
1266
+ ' validLabel' : ' validValue' ,
1267
+ ' invalid#key' : ' invalid$value' ,
1268
+ ' long-key-that-might-be-truncated-if-very-very-long' : ' long-value-that-should-be-truncated-because-it-exceeds-the-maximum-allowed-length-for-aws-batch-tags-which-is-256-characters-and-this-string-is-definitely-longer-than-that-limit-so-it-will-be-cut-off-at-the-appropriate-length-and-cleaned-up'
1269
+ ]
1270
+ )
1271
+
1272
+ def handler = Spy (AwsBatchTaskHandler )
1273
+
1274
+ when :
1275
+ def req = handler. newSubmitRequest(task)
1276
+ then :
1277
+ 1 * handler. getSubmitCommand() >> [' bash' , ' -c' , ' test' ]
1278
+ 1 * handler. maxSpotAttempts() >> 0
1279
+ 1 * handler. getAwsOptions() >> new AwsOptions ()
1280
+ 1 * handler. getJobQueue(task) >> ' test-queue'
1281
+ 1 * handler. getJobDefinition(task) >> ' test-job-def'
1282
+ 1 * handler. getEnvironmentVars() >> []
1283
+
1284
+ and :
1285
+ def tags = req. getTags()
1286
+ tags. size() == 3
1287
+ tags[' validLabel' ] == ' validValue'
1288
+ tags[' invalid_key' ] == ' invalid_value'
1289
+ // Check that long value was truncated
1290
+ tags[' long-key-that-might-be-truncated-if-very-very-long' ]. length() <= 256
1291
+ tags[' long-key-that-might-be-truncated-if-very-very-long' ]. startsWith(' long-value-that-should-be-truncated' )
1292
+ ! tags[' long-key-that-might-be-truncated-if-very-very-long' ]. endsWith(' _' )
1293
+ req. getPropagateTags() == true
1294
+ }
1141
1295
}
0 commit comments