Skip to content

Commit

Permalink
[feat][fixlib] Extend base resources with additional common properties (
Browse files Browse the repository at this point in the history
#2278)

Co-authored-by: Matthias Veit <[email protected]>
  • Loading branch information
1101-1 and aquamatthias authored Nov 15, 2024
1 parent 2cccb44 commit 5b8dc07
Show file tree
Hide file tree
Showing 21 changed files with 183 additions and 49 deletions.
39 changes: 37 additions & 2 deletions fixlib/fixlib/baseresources.py
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,16 @@ class BaseBucket(BaseResource):
_metadata: ClassVar[Dict[str, Any]] = {"icon": "bucket", "group": "storage"}
_categories: ClassVar[List[Category]] = [Category.storage]

encryption_enabled: Optional[bool] = None
versioning_enabled: Optional[bool] = None


@unique
class QueueType(Enum):
kind: ClassVar[str] = "queue_type"
STANDARD = "standard"
FIFO = "fifo"


@define(eq=False, slots=False)
class BaseQueue(BaseResource):
Expand All @@ -1097,6 +1107,9 @@ class BaseQueue(BaseResource):
_kind_description: ClassVar[str] = "A storage queue."
_metadata: ClassVar[Dict[str, Any]] = {"icon": "queue", "group": "storage"}
_categories: ClassVar[List[Category]] = [Category.storage]
queue_type: Optional[QueueType] = None
approximate_message_count: Optional[int] = None
message_retention_period: Optional[int] = None


@define(eq=False, slots=False)
Expand Down Expand Up @@ -1125,6 +1138,8 @@ class BaseServerlessFunction(BaseResource):
_metadata: ClassVar[Dict[str, Any]] = {"icon": "function", "group": "compute"}
_categories: ClassVar[List[Category]] = [Category.compute]

memory_size: Optional[int] = None


@define(eq=False, slots=False)
class BaseNetwork(BaseResource):
Expand All @@ -1134,6 +1149,8 @@ class BaseNetwork(BaseResource):
_metadata: ClassVar[Dict[str, Any]] = {"icon": "network", "group": "networking"}
_categories: ClassVar[List[Category]] = [Category.networking]

cidr_blocks: List[str] = field(factory=list)


@define(eq=False, slots=False)
class BaseNetworkQuota(BaseQuota):
Expand Down Expand Up @@ -1215,6 +1232,8 @@ class BaseSubnet(BaseResource):
_metadata: ClassVar[Dict[str, Any]] = {"icon": "subnet", "group": "networking"}
_categories: ClassVar[List[Category]] = [Category.networking]

cidr_block: Optional[str] = None


@define(eq=False, slots=False)
class BaseGateway(BaseResource):
Expand Down Expand Up @@ -1374,8 +1393,8 @@ class BaseAccessKey(BaseResource):
_kind_display: ClassVar[str] = "Access Key"
_kind_description: ClassVar[str] = "An access key."
_metadata: ClassVar[Dict[str, Any]] = {"icon": "key", "group": "access_control"}
access_key_status: str = ""
_categories: ClassVar[List[Category]] = [Category.access_control, Category.security]
access_key_status: Optional[str] = None


@define(eq=False, slots=False)
Expand Down Expand Up @@ -1404,10 +1423,10 @@ class BaseStack(BaseResource):
_kind_display: ClassVar[str] = "Stack"
_kind_description: ClassVar[str] = "A stack."
_metadata: ClassVar[Dict[str, Any]] = {"icon": "stack", "group": "management"}
_categories: ClassVar[List[Category]] = [Category.devops, Category.management]
stack_status: str = ""
stack_status_reason: str = ""
stack_parameters: Dict[str, str] = field(factory=dict)
_categories: ClassVar[List[Category]] = [Category.devops, Category.management]


@define(eq=False, slots=False)
Expand Down Expand Up @@ -1453,6 +1472,7 @@ class BaseDNSZone(BaseResource):
_kind_description: ClassVar[str] = "A DNS zone."
_metadata: ClassVar[Dict[str, Any]] = {"icon": "dns", "group": "networking"}
_categories: ClassVar[List[Category]] = [Category.dns, Category.networking]
private_zone: Optional[bool] = None


@define(eq=False, slots=False)
Expand Down Expand Up @@ -1574,6 +1594,19 @@ class BaseManagedKubernetesClusterProvider(BaseResource):
endpoint: Optional[str] = field(default=None, metadata={"description": "The kubernetes API endpoint"})


class AIJobStatus(Enum):
PENDING = "pending"
PREPARING = "preparing"
RUNNING = "running"
STOPPING = "stopping"
STOPPED = "stopped"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
PAUSED = "paused"
UNKNOWN = "unknown"


@define(eq=False, slots=False)
class BaseAIResource(BaseResource):
kind: ClassVar[str] = "ai_resource"
Expand All @@ -1590,6 +1623,8 @@ class BaseAIJob(BaseAIResource):
_kind_description: ClassVar[str] = "An AI Job resource."
_metadata: ClassVar[Dict[str, Any]] = {"icon": "job", "group": "ai"}

ai_job_status: Optional[AIJobStatus] = field(default=None, metadata={"description": "Current status of the AI job"})


@define(eq=False, slots=False)
class BaseAIModel(BaseAIResource):
Expand Down
1 change: 1 addition & 0 deletions plugins/aws/fix_plugin_aws/resource/acm.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class AwsAcmCertificate(AwsResource, BaseCertificate):
"tags": S("Tags", default=[]) >> ToDict(),
"name": S("DomainName"),
"ctime": S("CreatedAt"),
"mtime": S("RenewalSummary", "UpdatedAt"),
"arn": S("CertificateArn"),
"subject_alternative_names": S("SubjectAlternativeNames", default=[]),
"domain_validation_options": S("DomainValidationOptions", default=[])
Expand Down
20 changes: 14 additions & 6 deletions plugins/aws/fix_plugin_aws/resource/bedrock.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
from fix_plugin_aws.resource.lambda_ import AwsLambdaFunction
from fix_plugin_aws.resource.s3 import AwsS3Bucket
from fix_plugin_aws.resource.rds import AwsRdsCluster, AwsRdsInstance
from fixlib.baseresources import BaseAIJob, ModelReference, BaseAIModel
from fixlib.baseresources import AIJobStatus, BaseAIJob, ModelReference, BaseAIModel
from fixlib.graph import Graph
from fixlib.json_bender import Bender, S, ForallBend, Bend, Sort
from fixlib.json_bender import Bender, S, ForallBend, Bend, MapEnum, Sort
from fixlib.types import Json

log = logging.getLogger("fix.plugins.aws")
Expand Down Expand Up @@ -82,6 +82,16 @@ def service_name(cls) -> str:
return service_name


AWS_BEDROCK_JOB_STATUS_MAPPING = {
"InProgress": AIJobStatus.RUNNING,
"Completed": AIJobStatus.COMPLETED,
"Failed": AIJobStatus.FAILED,
"Stopping": AIJobStatus.STOPPING,
"Stopped": AIJobStatus.STOPPED,
"Deleting": AIJobStatus.STOPPING,
}


@define(eq=False, slots=False)
class AwsBedrockFoundationModel(BaseAIModel, AwsResource):
kind: ClassVar[str] = "aws_bedrock_foundation_model"
Expand Down Expand Up @@ -553,7 +563,7 @@ class AwsBedrockModelCustomizationJob(BedrockTaggable, BaseAIJob, AwsResource):
"output_model_arn": S("outputModelArn"),
"client_request_token": S("clientRequestToken"),
"role_arn": S("roleArn"),
"status": S("status"),
"status": S("status") >> MapEnum(AWS_BEDROCK_JOB_STATUS_MAPPING, AIJobStatus.UNKNOWN),
"failure_message": S("failureMessage"),
"creation_time": S("creationTime"),
"last_modified_time": S("lastModifiedTime"),
Expand All @@ -575,7 +585,6 @@ class AwsBedrockModelCustomizationJob(BedrockTaggable, BaseAIJob, AwsResource):
output_model_arn: Optional[str] = field(default=None, metadata={"description": "The Amazon Resource Name (ARN) of the output model."}) # fmt: skip
client_request_token: Optional[str] = field(default=None, metadata={"description": "The token that you specified in the CreateCustomizationJob request."}) # fmt: skip
role_arn: Optional[str] = field(default=None, metadata={"description": "The Amazon Resource Name (ARN) of the IAM role."}) # fmt: skip
status: Optional[str] = field(default=None, metadata={"description": "The status of the job. A successful job transitions from in-progress to completed when the output model is ready to use. If the job failed, the failure message contains information about why the job failed."}) # fmt: skip
failure_message: Optional[str] = field(default=None, metadata={"description": "Information about why the job failed."}) # fmt: skip
creation_time: Optional[datetime] = field(default=None, metadata={"description": "Time that the resource was created."}) # fmt: skip
last_modified_time: Optional[datetime] = field(default=None, metadata={"description": "Time that the resource was last modified."}) # fmt: skip
Expand Down Expand Up @@ -777,7 +786,7 @@ class AwsBedrockEvaluationJob(BedrockTaggable, BaseAIJob, AwsResource):
"ctime": S("creationTime"),
"mtime": S("lastModifiedTime"),
"job_name": S("jobName"),
"status": S("status"),
"status": S("status") >> MapEnum(AWS_BEDROCK_JOB_STATUS_MAPPING, AIJobStatus.UNKNOWN),
"job_arn": S("jobArn"),
"job_description": S("jobDescription"),
"role_arn": S("roleArn"),
Expand All @@ -791,7 +800,6 @@ class AwsBedrockEvaluationJob(BedrockTaggable, BaseAIJob, AwsResource):
"failure_messages": S("failureMessages", default=[]),
}
job_name: Optional[str] = field(default=None, metadata={"description": "The name of the model evaluation job."}) # fmt: skip
status: Optional[str] = field(default=None, metadata={"description": "The status of the model evaluation job."}) # fmt: skip
job_arn: Optional[str] = field(default=None, metadata={"description": "The Amazon Resource Name (ARN) of the model evaluation job."}) # fmt: skip
job_description: Optional[str] = field(default=None, metadata={"description": "The description of the model evaluation job."}) # fmt: skip
role_arn: Optional[str] = field(default=None, metadata={"description": "The Amazon Resource Name (ARN) of the IAM service role used in the model evaluation job."}) # fmt: skip
Expand Down
1 change: 1 addition & 0 deletions plugins/aws/fix_plugin_aws/resource/cognito.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class AwsCognitoUser(AwsResource, BaseUser):
"enabled": S("Enabled"),
"user_status": S("UserStatus"),
"mfa_options": S("MFAOptions", default=[]) >> ForallBend(AwsCognitoMFAOptionType.mapping),
"username": S("Username"),
}
user_attributes: List[AwsCognitoAttributeType] = field(factory=list)
enabled: Optional[bool] = field(default=None)
Expand Down
32 changes: 29 additions & 3 deletions plugins/aws/fix_plugin_aws/resource/dynamodb.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,16 @@
from fix_plugin_aws.resource.kinesis import AwsKinesisStream
from fix_plugin_aws.resource.kms import AwsKmsKey
from fix_plugin_aws.utils import ToDict
from fixlib.baseresources import HasResourcePolicy, ModelReference, PolicySource, PolicySourceKind
from fixlib.baseresources import (
BaseDatabase,
DatabaseInstanceStatus,
HasResourcePolicy,
ModelReference,
PolicySource,
PolicySourceKind,
)
from fixlib.graph import Graph
from fixlib.json_bender import S, Bend, Bender, ForallBend, bend
from fixlib.json_bender import S, Bend, Bender, ForallBend, bend, K, MapValue
from fixlib.types import Json
from fixlib.json import sort_json

Expand Down Expand Up @@ -356,7 +363,7 @@ class AwsDynamoDbContinuousBackup:


@define(eq=False, slots=False)
class AwsDynamoDbTable(DynamoDbTaggable, AwsResource, HasResourcePolicy):
class AwsDynamoDbTable(DynamoDbTaggable, BaseDatabase, AwsResource, HasResourcePolicy):
kind: ClassVar[str] = "aws_dynamodb_table"
_kind_display: ClassVar[str] = "AWS DynamoDB Table"
_kind_description: ClassVar[str] = "AWS DynamoDB Table is a fully managed NoSQL database service that stores and retrieves data. It supports key-value and document data models, offering automatic scaling and low-latency performance. DynamoDB Tables handle data storage, indexing, and querying, providing consistent read and write throughput. They offer data encryption, backup, and recovery features for secure and reliable data management." # fmt: skip
Expand Down Expand Up @@ -396,6 +403,25 @@ class AwsDynamoDbTable(DynamoDbTaggable, AwsResource, HasResourcePolicy):
"dynamodb_sse_description": S("SSEDescription") >> Bend(AwsDynamoDbSSEDescription.mapping),
"dynamodb_archival_summary": S("ArchivalSummary") >> Bend(AwsDynamoDbArchivalSummary.mapping),
"dynamodb_table_class_summary": S("TableClassSummary") >> Bend(AwsDynamoDbTableClassSummary.mapping),
"db_type": K("dynamodb"),
"db_status": S("TableStatus")
>> MapValue(
{
"CREATING": DatabaseInstanceStatus.BUSY,
"UPDATING": DatabaseInstanceStatus.BUSY,
"DELETING": DatabaseInstanceStatus.BUSY,
"ACTIVE": DatabaseInstanceStatus.AVAILABLE,
"INACCESSIBLE_ENCRYPTION_CREDENTIALS": DatabaseInstanceStatus.FAILED,
"ARCHIVING": DatabaseInstanceStatus.BUSY,
"ARCHIVED": DatabaseInstanceStatus.STOPPED,
},
default=DatabaseInstanceStatus.UNKNOWN,
),
"volume_encrypted": S("SSEDescription", "Status")
>> MapValue(
{"ENABLING": True, "ENABLED": True, "DISABLING": False, "DISABLED": False, "UPDATING": None},
default=None,
),
}
arn: Optional[str] = field(default=None)
dynamodb_attribute_definitions: List[AwsDynamoDbAttributeDefinition] = field(factory=list)
Expand Down
3 changes: 2 additions & 1 deletion plugins/aws/fix_plugin_aws/resource/ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from fix_plugin_aws.resource.kms import AwsKmsKey
from fix_plugin_aws.resource.s3 import AwsS3Bucket
from fix_plugin_aws.utils import ToDict, TagsValue
from fix_plugin_aws.aws_client import AwsClient
from fixlib.baseresources import (
BaseInstance,
BaseKeyPair,
Expand Down Expand Up @@ -2155,6 +2154,7 @@ class AwsEc2Vpc(EC2Taggable, AwsResource, BaseNetwork):
"vpc_cidr_block_association_set": S("CidrBlockAssociationSet", default=[])
>> ForallBend(AwsEc2VpcCidrBlockAssociation.mapping),
"vpc_is_default": S("IsDefault"),
"cidr_blocks": S("CidrBlockAssociationSet", default=[]) >> ForallBend(S("CidrBlock")),
}
vpc_cidr_block: Optional[str] = field(default=None)
vpc_dhcp_options_id: Optional[str] = field(default=None)
Expand Down Expand Up @@ -2506,6 +2506,7 @@ class AwsEc2Subnet(EC2Taggable, AwsResource, BaseSubnet):
"subnet_ipv6_native": S("Ipv6Native"),
"subnet_private_dns_name_options_on_launch": S("PrivateDnsNameOptionsOnLaunch")
>> Bend(AwsEc2PrivateDnsNameOptionsOnLaunch.mapping),
"cidr_block": S("CidrBlock"),
}
subnet_availability_zone: Optional[str] = field(default=None)
subnet_availability_zone_id: Optional[str] = field(default=None)
Expand Down
1 change: 1 addition & 0 deletions plugins/aws/fix_plugin_aws/resource/iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,7 @@ class AwsIamUser(AwsResource, BaseUser, BaseIamPrincipal):
"arn": S("Arn"),
"user_policies": S("UserPolicyList", default=[]) >> ForallBend(AwsIamPolicyDetail.mapping),
"user_permissions_boundary": S("PermissionsBoundary") >> Bend(AwsIamAttachedPermissionsBoundary.mapping),
"username": S("UserName"),
}
path: Optional[str] = field(default=None)
user_policies: List[AwsIamPolicyDetail] = field(factory=list)
Expand Down
1 change: 1 addition & 0 deletions plugins/aws/fix_plugin_aws/resource/lambda_.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ class AwsLambdaFunction(AwsResource, BaseServerlessFunction, HasResourcePolicy):
"function_signing_job_arn": S("SigningJobArn"),
"function_architectures": S("Architectures", default=[]),
"function_ephemeral_storage": S("EphemeralStorage", "Size"),
"memory_size": S("MemorySize"),
}
function_runtime: Optional[str] = field(default=None)
function_role: Optional[str] = field(default=None)
Expand Down
5 changes: 3 additions & 2 deletions plugins/aws/fix_plugin_aws/resource/route53.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,13 @@ class AwsRoute53Zone(AwsResource, BaseDNSZone):
"name": S("Name"),
"zone_caller_reference": S("CallerReference"),
"zone_config": S("Config") >> Bend(AwsRoute53ZoneConfig.mapping),
"zone_resource_record_set_count": S("ResourceRecordSetCount"),
"zone_linked_service": S("LinkedService") >> Bend(AwsRoute53LinkedService.mapping),
"private_zone": S("Config", "PrivateZone"),
"zone_resource_record_set_count": S("ResourceRecordSetCount"),
}
zone_resource_record_set_count: Optional[int] = field(default=None, metadata=dict(ignore_history=True))
zone_caller_reference: Optional[str] = field(default=None)
zone_config: Optional[AwsRoute53ZoneConfig] = field(default=None)
zone_resource_record_set_count: Optional[int] = field(default=None, metadata=dict(ignore_history=True))
zone_linked_service: Optional[AwsRoute53LinkedService] = field(default=None)
zone_logging_config: Optional[AwsRoute53LoggingConfig] = field(default=None)

Expand Down
3 changes: 3 additions & 0 deletions plugins/aws/fix_plugin_aws/resource/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ def add_bucket_encryption(bck: AwsS3Bucket) -> None:
mapped = bend(AwsS3ServerSideEncryptionRule.mapping, raw)
if rule := parse_json(mapped, AwsS3ServerSideEncryptionRule, builder):
bck.bucket_encryption_rules.append(rule)
bck.encryption_enabled = len(bck.bucket_encryption_rules) > 0

def add_bucket_policy(bck: AwsS3Bucket) -> None:
with builder.suppress(f"{service_name}.get-bucket-policy"):
Expand Down Expand Up @@ -267,9 +268,11 @@ def add_bucket_versioning(bck: AwsS3Bucket) -> None:
):
bck.bucket_versioning = raw_versioning.get("Status") == "Enabled"
bck.bucket_mfa_delete = raw_versioning.get("MFADelete") == "Enabled"
bck.versioning_enabled = bck.bucket_versioning
else:
bck.bucket_versioning = False
bck.bucket_mfa_delete = False
bck.versioning_enabled = False

def add_public_access(bck: AwsS3Bucket) -> None:
with builder.suppress(f"{service_name}.get-public-access-block"):
Expand Down
8 changes: 6 additions & 2 deletions plugins/aws/fix_plugin_aws/resource/sqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
ModelReference,
PolicySource,
PolicySourceKind,
QueueType,
)
from fixlib.graph import Graph
from fixlib.json_bender import F, Bender, S, AsInt, AsBool, Bend, ParseJson, Sorted
Expand Down Expand Up @@ -80,6 +81,8 @@ class AwsSqsQueue(AwsResource, BaseQueue, HasResourcePolicy):
"sqs_delay_seconds": S("DelaySeconds") >> AsInt(),
"sqs_receive_message_wait_time_seconds": S("ReceiveMessageWaitTimeSeconds") >> AsInt(),
"sqs_managed_sse_enabled": S("SqsManagedSseEnabled") >> AsBool(),
"message_retention_period": S("MessageRetentionPeriod") >> AsInt(),
"approximate_message_count": S("ApproximateNumberOfMessages") >> AsInt(),
}
sqs_queue_url: Optional[str] = field(default=None)
sqs_approximate_number_of_messages: Optional[int] = field(default=None, metadata=dict(ignore_history=True))
Expand Down Expand Up @@ -118,16 +121,17 @@ def called_collect_apis(cls) -> List[AwsApiSpec]:
]

@classmethod
def collect(cls: Type[AwsResource], json: List[Json], builder: GraphBuilder) -> None:
def collect(cls, json: List[Json], builder: GraphBuilder) -> None:
def add_instance(queue_url: str) -> None:
queue_attributes = builder.client.get(
service_name, "get-queue-attributes", "Attributes", QueueUrl=queue_url, AttributeNames=["All"]
)
if queue_attributes is not None:
queue_attributes["QueueUrl"] = queue_url
queue_attributes["QueueName"] = queue_url.rsplit("/", 1)[-1]
if instance := cls.from_api(queue_attributes, builder):
if instance := AwsSqsQueue.from_api(queue_attributes, builder):
builder.add_node(instance, queue_attributes)
instance.queue_type = QueueType.FIFO if instance.sqs_fifo_queue else QueueType.STANDARD
builder.submit_work(service_name, add_tags, instance)

def add_tags(queue: AwsSqsQueue) -> None:
Expand Down
4 changes: 2 additions & 2 deletions plugins/aws/test/resources/cloudfront_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ def validate_delete_args(**kwargs: Any) -> Any:


def test_functions() -> None:
first, builder = round_trip_for(AwsCloudFrontFunction)
first, builder = round_trip_for(AwsCloudFrontFunction, "memory_size")
assert len(builder.resources_of(AwsCloudFrontFunction)) == 1
assert len(first.tags) == 1
assert first.arn == "arn"


def test_function_deletion() -> None:
func, _ = round_trip_for(AwsCloudFrontFunction)
func, _ = round_trip_for(AwsCloudFrontFunction, "memory_size")

def validate_delete_args(**kwargs: Any) -> Any:
assert kwargs["action"] == "delete-function"
Expand Down
Loading

0 comments on commit 5b8dc07

Please sign in to comment.