Skip to content

Commit ad55796

Browse files
authored
Add rule E3674 to validate instance PrivateIpAddress (#3657)
1 parent dfc305b commit ad55796

File tree

3 files changed

+182
-0
lines changed

3 files changed

+182
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"if": {
3+
"required": [
4+
"PrivateIpAddress"
5+
]
6+
},
7+
"then": {
8+
"properties": {
9+
"NetworkInterfaces": {
10+
"items": {
11+
"properties": {
12+
"PrivateIpAddresses": {
13+
"items": {
14+
"properties": {
15+
"Primary": {
16+
"enum": [
17+
false
18+
]
19+
}
20+
}
21+
},
22+
"type": "array"
23+
}
24+
}
25+
},
26+
"type": "array"
27+
}
28+
}
29+
}
30+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: MIT-0
4+
"""
5+
6+
from __future__ import annotations
7+
8+
from typing import Any
9+
10+
import cfnlint.data.schemas.extensions.aws_ec2_instance
11+
from cfnlint.jsonschema import ValidationError
12+
from cfnlint.rules.jsonschema.CfnLintJsonSchema import CfnLintJsonSchema, SchemaDetails
13+
14+
15+
class PrivateIpWithNetworkInterface(CfnLintJsonSchema):
16+
id = "E3674"
17+
shortdesc = "Primary cannoy be True when PrivateIpAddress is specified"
18+
description = "Only specify the private IP address for an instance in one spot"
19+
tags = ["resources", "ec2"]
20+
21+
def __init__(self) -> None:
22+
super().__init__(
23+
keywords=[
24+
"Resources/AWS::EC2::Instance/Properties",
25+
],
26+
schema_details=SchemaDetails(
27+
module=cfnlint.data.schemas.extensions.aws_ec2_instance,
28+
filename="privateipaddress.json",
29+
),
30+
)
31+
32+
def message(self, instance: Any, err: ValidationError) -> str:
33+
return "'Primary' cannot be True when 'PrivateIpAddress' is specified"
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: MIT-0
4+
"""
5+
6+
from collections import deque
7+
8+
import pytest
9+
10+
from cfnlint.jsonschema import ValidationError
11+
from cfnlint.rules.resources.ectwo.PrivateIpWithNetworkInterface import (
12+
PrivateIpWithNetworkInterface,
13+
)
14+
15+
16+
@pytest.fixture(scope="module")
17+
def rule():
18+
rule = PrivateIpWithNetworkInterface()
19+
yield rule
20+
21+
22+
@pytest.mark.parametrize(
23+
"name,instance,path,expected",
24+
[
25+
(
26+
"Valid with no Private Ip Address",
27+
{
28+
"NetworkInterfaces": [
29+
{
30+
"PrivateIpAddresses": [
31+
{"PrivateIpAddress": "172.31.35.42", "Primary": True}
32+
]
33+
}
34+
]
35+
},
36+
{
37+
"path": ["Resources", "Instance", "Properties"],
38+
},
39+
[],
40+
),
41+
(
42+
"Valid with Private Ip Address with Primary False",
43+
{
44+
"PrivateIpAddress": "172.31.35.42",
45+
"NetworkInterfaces": [
46+
{
47+
"PrivateIpAddresses": [
48+
{"PrivateIpAddress": "172.31.35.42", "Primary": False}
49+
]
50+
}
51+
],
52+
},
53+
{
54+
"path": ["Resources", "Instance", "Properties"],
55+
},
56+
[],
57+
),
58+
(
59+
"Valid with Private Ip Address without Primary specified",
60+
{
61+
"PrivateIpAddress": "172.31.35.42",
62+
"NetworkInterfaces": [
63+
{"PrivateIpAddresses": [{"PrivateIpAddress": "172.31.35.42"}]}
64+
],
65+
},
66+
{
67+
"path": ["Resources", "Instance", "Properties"],
68+
},
69+
[],
70+
),
71+
(
72+
"Invalid with a private ip address",
73+
{
74+
"PrivateIpAddress": "172.31.35.42",
75+
"NetworkInterfaces": [
76+
{
77+
"PrivateIpAddresses": [
78+
{"PrivateIpAddress": "172.31.35.42", "Primary": True}
79+
]
80+
}
81+
],
82+
},
83+
{
84+
"path": ["Resources", "Instance", "Properties"],
85+
},
86+
[
87+
ValidationError(
88+
"'Primary' cannot be True when 'PrivateIpAddress' is specified",
89+
validator="enum",
90+
rule=PrivateIpWithNetworkInterface(),
91+
path=deque(
92+
["NetworkInterfaces", 0, "PrivateIpAddresses", 0, "Primary"]
93+
),
94+
schema_path=deque(
95+
[
96+
"then",
97+
"properties",
98+
"NetworkInterfaces",
99+
"items",
100+
"properties",
101+
"PrivateIpAddresses",
102+
"items",
103+
"properties",
104+
"Primary",
105+
"enum",
106+
]
107+
),
108+
)
109+
],
110+
),
111+
],
112+
indirect=["path"],
113+
)
114+
def test_validate(name, instance, expected, rule, validator):
115+
errs = list(rule.validate(validator, "", instance, {}))
116+
117+
assert (
118+
errs == expected
119+
), f"Expected test {name!r} to have {expected!r} but got {errs!r}"

0 commit comments

Comments
 (0)