Skip to content

Commit 0d28a54

Browse files
authored
Redo codepipeline rules (#3993)
* Deprecate E2540 for new v1 version rules * Deprecate E2541 for new v1 version rules * Add rule E3700 to validate that Source actions are only in the first stage * Add rule E3701 to validate artifact names shared between InputArtifacts and OutputArtifacts * Add rule E3702 to validate the InputArtifacts, OutputArtifacts counts for the action type * Add rule E3703 to validate the configuration of an action. Start with a TemplatePath on CloudFormation based actions have a Source that matches an InputArtifact name
1 parent 6a5f009 commit 0d28a54

34 files changed

+1270
-1353
lines changed

scripts/update_schemas_manually.py

+12
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,18 @@
584584
values={"requiredXor": ["ArtifactStore", "ArtifactStores"]},
585585
path="/",
586586
),
587+
Patch(
588+
values={"minItems": 2, "uniqueKeys": ["Name"]},
589+
path="/properties/Stages",
590+
),
591+
Patch(
592+
values={"minItems": 1, "uniqueKeys": ["Name"]},
593+
path="/definitions/StageDeclaration/properties/Actions",
594+
),
595+
Patch(
596+
values={"pattern": "^[0-9A-Za-z_-]{1,9}$"},
597+
path="/definitions/ActionTypeId/properties/Version",
598+
),
587599
],
588600
),
589601
ResourcePatch(

src/cfnlint/data/schemas/extensions/aws_codepipeline_pipeline/__init__.py

Whitespace-only changes.

src/cfnlint/data/schemas/patches/extensions/all/aws_codepipeline_pipeline/manual.json

+29
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,34 @@
66
"ArtifactStore",
77
"ArtifactStores"
88
]
9+
},
10+
{
11+
"op": "add",
12+
"path": "/definitions/ActionTypeId/properties/Version/pattern",
13+
"value": "^[0-9A-Za-z_-]{1,9}$"
14+
},
15+
{
16+
"op": "add",
17+
"path": "/definitions/StageDeclaration/properties/Actions/minItems",
18+
"value": 1
19+
},
20+
{
21+
"op": "add",
22+
"path": "/definitions/StageDeclaration/properties/Actions/uniqueKeys",
23+
"value": [
24+
"Name"
25+
]
26+
},
27+
{
28+
"op": "add",
29+
"path": "/properties/Stages/minItems",
30+
"value": 2
31+
},
32+
{
33+
"op": "add",
34+
"path": "/properties/Stages/uniqueKeys",
35+
"value": [
36+
"Name"
37+
]
938
}
1039
]

src/cfnlint/data/schemas/providers/ap_southeast_5/aws-codepipeline-pipeline.json

+11-2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"type": "string"
7575
},
7676
"Version": {
77+
"pattern": "^[0-9A-Za-z_-]{1,9}$",
7778
"type": "string"
7879
}
7980
},
@@ -426,8 +427,12 @@
426427
"items": {
427428
"$ref": "#/definitions/ActionDeclaration"
428429
},
430+
"minItems": 1,
429431
"type": "array",
430-
"uniqueItems": true
432+
"uniqueItems": true,
433+
"uniqueKeys": [
434+
"Name"
435+
]
431436
},
432437
"BeforeEntry": {
433438
"$ref": "#/definitions/BeforeEntryConditions"
@@ -568,8 +573,12 @@
568573
"items": {
569574
"$ref": "#/definitions/StageDeclaration"
570575
},
576+
"minItems": 2,
571577
"type": "array",
572-
"uniqueItems": true
578+
"uniqueItems": true,
579+
"uniqueKeys": [
580+
"Name"
581+
]
573582
},
574583
"Tags": {
575584
"items": {

src/cfnlint/data/schemas/providers/ap_southeast_7/aws-codepipeline-pipeline.json

+11-2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"type": "string"
7575
},
7676
"Version": {
77+
"pattern": "^[0-9A-Za-z_-]{1,9}$",
7778
"type": "string"
7879
}
7980
},
@@ -426,8 +427,12 @@
426427
"items": {
427428
"$ref": "#/definitions/ActionDeclaration"
428429
},
430+
"minItems": 1,
429431
"type": "array",
430-
"uniqueItems": true
432+
"uniqueItems": true,
433+
"uniqueKeys": [
434+
"Name"
435+
]
431436
},
432437
"BeforeEntry": {
433438
"$ref": "#/definitions/BeforeEntryConditions"
@@ -568,8 +573,12 @@
568573
"items": {
569574
"$ref": "#/definitions/StageDeclaration"
570575
},
576+
"minItems": 2,
571577
"type": "array",
572-
"uniqueItems": true
578+
"uniqueItems": true,
579+
"uniqueKeys": [
580+
"Name"
581+
]
573582
},
574583
"Tags": {
575584
"items": {

src/cfnlint/data/schemas/providers/ca_west_1/aws-codepipeline-pipeline.json

+11-2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"type": "string"
7575
},
7676
"Version": {
77+
"pattern": "^[0-9A-Za-z_-]{1,9}$",
7778
"type": "string"
7879
}
7980
},
@@ -426,8 +427,12 @@
426427
"items": {
427428
"$ref": "#/definitions/ActionDeclaration"
428429
},
430+
"minItems": 1,
429431
"type": "array",
430-
"uniqueItems": true
432+
"uniqueItems": true,
433+
"uniqueKeys": [
434+
"Name"
435+
]
431436
},
432437
"BeforeEntry": {
433438
"$ref": "#/definitions/BeforeEntryConditions"
@@ -568,8 +573,12 @@
568573
"items": {
569574
"$ref": "#/definitions/StageDeclaration"
570575
},
576+
"minItems": 2,
571577
"type": "array",
572-
"uniqueItems": true
578+
"uniqueItems": true,
579+
"uniqueKeys": [
580+
"Name"
581+
]
573582
},
574583
"Tags": {
575584
"items": {

src/cfnlint/data/schemas/providers/us_east_1/aws-codepipeline-pipeline.json

+11-2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
"type": "string"
9999
},
100100
"Version": {
101+
"pattern": "^[0-9A-Za-z_-]{1,9}$",
101102
"type": "string"
102103
}
103104
},
@@ -474,8 +475,12 @@
474475
"items": {
475476
"$ref": "#/definitions/ActionDeclaration"
476477
},
478+
"minItems": 1,
477479
"type": "array",
478-
"uniqueItems": true
480+
"uniqueItems": true,
481+
"uniqueKeys": [
482+
"Name"
483+
]
479484
},
480485
"BeforeEntry": {
481486
"$ref": "#/definitions/BeforeEntryConditions",
@@ -624,8 +629,12 @@
624629
"items": {
625630
"$ref": "#/definitions/StageDeclaration"
626631
},
632+
"minItems": 2,
627633
"type": "array",
628-
"uniqueItems": true
634+
"uniqueItems": true,
635+
"uniqueKeys": [
636+
"Name"
637+
]
629638
},
630639
"Tags": {
631640
"items": {

src/cfnlint/jsonschema/_keywords.py

+46-14
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,36 @@ def contains(
137137
if not validator.is_type(instance, "array"):
138138
return
139139

140-
if not any(
141-
validator.evolve(schema=contains).is_valid(element) for element in instance
142-
):
143-
yield ValidationError(
144-
f"{instance!r} does not contain items matching the given schema",
145-
)
140+
matches = 0
141+
min_contains = schema.get("minContains", 1)
142+
max_contains = schema.get("maxContains", len(instance))
143+
144+
contains_validator = validator.evolve(schema=contains)
145+
146+
for each in instance:
147+
if contains_validator.is_valid(each):
148+
matches += 1
149+
if matches > max_contains:
150+
yield ValidationError(
151+
"Too many items match the given schema "
152+
f"(expected at most {max_contains})",
153+
validator="maxContains",
154+
validator_value=max_contains,
155+
)
156+
return
157+
158+
if matches < min_contains:
159+
if not matches:
160+
yield ValidationError(
161+
f"{instance!r} does not contain items matching the given schema",
162+
)
163+
else:
164+
yield ValidationError(
165+
"Too few items match the given schema (expected at least "
166+
f"{min_contains} but only {matches} matched)",
167+
validator="minContains",
168+
validator_value=min_contains,
169+
)
146170

147171

148172
def dependencies(
@@ -305,18 +329,26 @@ def items(
305329
if not validator.is_type(instance, "array"):
306330
return
307331

308-
if validator.is_type(items, "array"):
309-
for (index, item), subschema in zip(enumerate(instance), items):
332+
prefix = len(schema.get("prefixItems", []))
333+
total = len(instance)
334+
extra = total - prefix
335+
if extra <= 0:
336+
return
337+
338+
if items is False:
339+
rest = instance[prefix:] if extra != 1 else instance[prefix]
340+
item = "items" if prefix != 1 else "item"
341+
yield ValidationError(
342+
f"Expected at most {prefix} {item} but found {extra} " f"extra: {rest!r}",
343+
)
344+
else:
345+
for index in range(prefix, total):
310346
yield from validator.descend(
311-
item,
312-
subschema,
347+
instance=instance[index],
348+
schema=items,
313349
path=index,
314-
schema_path=index,
315350
property_path="*",
316351
)
317-
else:
318-
for index, item in enumerate(instance):
319-
yield from validator.descend(item, items, path=index, property_path="*")
320352

321353

322354
def maxItems(

src/cfnlint/rules/functions/DynamicReference.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111
from cfnlint.rules.functions._BaseFn import BaseFn
1212

1313
_all = {
14-
"items": [
14+
"prefixItems": [
1515
{"type": "string", "const": "resolve"},
1616
{"type": "string", "enum": ["ssm", "ssm-secure", "secretsmanager"]},
1717
],
1818
"minItems": 3,
1919
}
2020

2121
_ssm = {
22-
"items": [
22+
"prefixItems": [
2323
{"type": "string", "const": "resolve"},
2424
{"type": "string", "enum": ["ssm", "ssm-secure"]},
2525
{"type": "string", "pattern": "[a-zA-Z0-9_.-/]+"},
@@ -30,7 +30,7 @@
3030
}
3131

3232
_secrets_manager = {
33-
"items": [
33+
"prefixItems": [
3434
{"type": "string", "const": "resolve"},
3535
{"type": "string", "const": "secretsmanager"},
3636
{"type": "string", "pattern": "[ -~]*"}, # secret-id
@@ -45,7 +45,7 @@
4545
}
4646

4747
_secrets_manager_arn = {
48-
"items": [
48+
"prefixItems": [
4949
{"type": "string", "const": "resolve"},
5050
{"type": "string", "const": "secretsmanager"},
5151
{"type": "string", "const": "arn"}, # arn

0 commit comments

Comments
 (0)