Skip to content

Commit

Permalink
Create rule E3695 to validate cache cluster engines (#3824)
Browse files Browse the repository at this point in the history
  • Loading branch information
kddejong authored Nov 12, 2024
1 parent 4b21477 commit d5c3da9
Show file tree
Hide file tree
Showing 4 changed files with 332 additions and 5 deletions.
89 changes: 84 additions & 5 deletions scripts/update_schemas_from_aws_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@

session = boto3.session.Session()
config = Config(retries={"max_attempts": 10})
client = session.client("rds", region_name="us-east-1", config=config)
rds_client = session.client("rds", region_name="us-east-1", config=config)
elasticache_client = session.client(
"elasticache", region_name="us-east-1", config=config
)


def configure_logging():
Expand Down Expand Up @@ -180,12 +183,65 @@ def write_db_instance(results):
write_output("aws_rds_dbinstance", "engine_version", schema)


def main():
"""main function"""
configure_logging()
def write_elasticache_engines(results):
schema = {"allOf": []}

engines = [
"memcached",
"redis",
"valkey",
]

schema["allOf"].append(
{
"if": {
"properties": {
"Engine": {
"type": "string",
}
},
"required": ["Engine"],
},
"then": {
"properties": {
"Engine": {
"enum": sorted(engines),
}
}
},
}
)

for engine in engines:
if not results.get(engine):
continue

engine_versions = sorted(results.get(engine))
schema["allOf"].append(
{
"if": {
"properties": {
"Engine": {
"const": engine,
},
"EngineVersion": {
"type": ["string", "number"],
},
},
"required": ["Engine", "EngineVersion"],
},
"then": {
"properties": {"EngineVersion": {"enum": sorted(engine_versions)}}
},
}
)

write_output("aws_elasticache_cachecluster", "engine_version", schema)


def rds_api():
results = {}
for page in client.get_paginator("describe_db_engine_versions").paginate():
for page in rds_client.get_paginator("describe_db_engine_versions").paginate():
for version in page.get("DBEngineVersions"):
engine = version.get("Engine")
engine_version = version.get("EngineVersion")
Expand All @@ -197,6 +253,29 @@ def main():
write_db_instance(results)


def elasticache_api():
results = {}
for page in elasticache_client.get_paginator(
"describe_cache_engine_versions"
).paginate():
print(page)
for version in page.get("CacheEngineVersions"):
engine = version.get("Engine")
engine_version = version.get("EngineVersion")
if engine not in results:
results[engine] = []
results[engine].append(engine_version)

write_elasticache_engines(results)


def main():
"""main function"""
configure_logging()
rds_api()
elasticache_api()


if __name__ == "__main__":
try:
main()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
{
"_description": [
"Automatically updated using aws api"
],
"allOf": [
{
"if": {
"properties": {
"Engine": {
"type": "string"
}
},
"required": [
"Engine"
]
},
"then": {
"properties": {
"Engine": {
"enum": [
"memcached",
"redis",
"valkey"
]
}
}
}
},
{
"if": {
"properties": {
"Engine": {
"const": "memcached"
},
"EngineVersion": {
"type": [
"string",
"number"
]
}
},
"required": [
"Engine",
"EngineVersion"
]
},
"then": {
"properties": {
"EngineVersion": {
"enum": [
"1.4.14",
"1.4.24",
"1.4.33",
"1.4.34",
"1.4.5",
"1.5.10",
"1.5.16",
"1.6.12",
"1.6.17",
"1.6.22",
"1.6.6"
]
}
}
}
},
{
"if": {
"properties": {
"Engine": {
"const": "redis"
},
"EngineVersion": {
"type": [
"string",
"number"
]
}
},
"required": [
"Engine",
"EngineVersion"
]
},
"then": {
"properties": {
"EngineVersion": {
"enum": [
"4.0.10",
"5.0.6",
"6.0",
"6.2",
"7.0",
"7.1"
]
}
}
}
},
{
"if": {
"properties": {
"Engine": {
"const": "valkey"
},
"EngineVersion": {
"type": [
"string",
"number"
]
}
},
"required": [
"Engine",
"EngineVersion"
]
},
"then": {
"properties": {
"EngineVersion": {
"enum": [
"7.2"
]
}
}
}
}
]
}
29 changes: 29 additions & 0 deletions src/cfnlint/rules/resources/elasticache/CacheClusterEngine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""

from __future__ import annotations

import cfnlint.data.schemas.extensions.aws_elasticache_cachecluster
from cfnlint.rules.jsonschema.CfnLintJsonSchema import CfnLintJsonSchema, SchemaDetails


class CacheClusterEngine(CfnLintJsonSchema):
id = "E3695"
shortdesc = "Validate Elasticache Cluster Engine and Engine Version"
description = (
"Validate the Elasticache cluster engine along with the engine version"
)
tags = ["resources"]
source_url = "https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/supported-engine-versions.html"

def __init__(self) -> None:
super().__init__(
keywords=["Resources/AWS::ElastiCache::CacheCluster/Properties"],
schema_details=SchemaDetails(
module=cfnlint.data.schemas.extensions.aws_elasticache_cachecluster,
filename="engine_version.json",
),
all_matches=True,
)
90 changes: 90 additions & 0 deletions test/unit/rules/resources/elasticache/test_cache_cluster_engine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""

from collections import deque

import pytest

from cfnlint.jsonschema import ValidationError
from cfnlint.rules.resources.elasticache.CacheClusterEngine import CacheClusterEngine


@pytest.fixture(scope="module")
def rule():
rule = CacheClusterEngine()
yield rule


@pytest.mark.parametrize(
"instance,expected",
[
(
{"Engine": "redis", "EngineVersion": 7.1},
[],
),
(
{
"Engine": "redis",
},
[],
),
(
{},
[],
),
(
{
"Engine": "redis",
"EngineVersion": "7.1.0",
},
[
ValidationError(
(
"'7.1.0' is not one of "
"['4.0.10', '5.0.6', "
"'6.0', '6.2', '7.0', '7.1']"
),
validator="enum",
path=deque(["EngineVersion"]),
schema_path=[
"allOf",
2,
"then",
"properties",
"EngineVersion",
"enum",
],
rule=CacheClusterEngine(),
)
],
),
(
{
"Engine": "oss-redis",
"EngineVersion": "7.1",
},
[
ValidationError(
("'oss-redis' is not one of " "['memcached', 'redis', 'valkey']"),
validator="enum",
path=deque(["Engine"]),
schema_path=["allOf", 0, "then", "properties", "Engine", "enum"],
rule=CacheClusterEngine(),
)
],
),
(
{
"Engine": "redis",
"EngineVersion": {"Ref": "AWS::AccountId"},
},
[],
),
],
)
def test_validate(instance, expected, rule, validator):
errs = list(rule.validate(validator, "", instance, {}))

assert errs == expected, f"Expected {expected} got {errs}"

0 comments on commit d5c3da9

Please sign in to comment.