Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for collecting deletable OCPs from AWS #481

Merged
merged 5 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ dependencies = [
"vspk==5.3.2",
"wait_for",
"websocket_client",
"datetime",
oharan2 marked this conversation as resolved.
Show resolved Hide resolved
]

[project.optional-dependencies]
Expand Down
1 change: 1 addition & 0 deletions wrapanapi/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
"""

CACHED_PROPERTY_TTL = 1
EC2_INSTANCE = "ec2:instance"
93 changes: 76 additions & 17 deletions wrapanapi/systems/ec2.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import base64
import os
import re
from datetime import datetime

import boto3
from boto3 import client as boto3client
from boto3 import resource as boto3resource
from botocore.config import Config
from botocore.exceptions import ClientError

from wrapanapi.const import EC2_INSTANCE
from wrapanapi.entities import (
Instance,
Network,
Expand Down Expand Up @@ -113,10 +115,17 @@ def __init__(self, system, raw=None, **kwargs):

self._api = self.system.ec2_connection

def __repr__(self):
return f"{type(self)} Id: {self.id}, Name: {self.name}"

@property
def id(self):
oharan2 marked this conversation as resolved.
Show resolved Hide resolved
return self.raw.id

@property
def name(self):
tag_value = self.get_tag_value("Name")
return getattr(self.raw, "name", None) or tag_value if tag_value else self.raw.id
return getattr(self.raw, "name", None) or tag_value if tag_value else self.id

def _get_state(self):
self.refresh()
Expand Down Expand Up @@ -487,13 +496,24 @@ class ResourceExplorerResource:
This class represents a resource returned by Resource Explorer.
"""

def __init__(self, arn, region, resource_type, service, properties=[]):
def __init__(self, arn, region, resource_type, service, properties, **kwargs):
self.arn = arn
self.region = region
self.resource_type = resource_type
self.service = service
self.properties = properties

if self.resource_type == EC2_INSTANCE and kwargs:
if system := kwargs.get("system"):
kwargs["raw"] = system.ec2_resource.Instance(self.id)
kwargs["uuid"] = self.id
# When Calling the raw ec2_instance with non-matching AWS Client Region,
# the API call fails with a 'ClientError' (InvalidInstanceID.NotFound)
self.ec2_instance = EC2Instance(**kwargs)

def __repr__(self):
return f"{type(self)} Id: {self.id}, Type: {self.resource_type}"

def get_tag_value(self, key) -> str:
"""
Returns a tag value for a given tag key.
Expand Down Expand Up @@ -530,8 +550,14 @@ def id(self) -> str:
Returns the last part of the arn.
This part is used as id in aws cli.
"""
# According to the docs:
# https://docs.aws.amazon.com/quicksight/latest/APIReference/qs-arn-format.html
# ARNs can end in either scheme: {ARN_VALUE}:resource-id, {ARN_VALUE}/resource-id
if self.arn:
return self.arn.split(":")[-1]
arn = self.arn.split(":")[-1]
if "/" in arn:
arn = arn.split("/")[-1]
return arn
return None

@property
Expand All @@ -545,6 +571,23 @@ def name(self) -> str:
name = self.id
return name

@property
def date_modified(self) -> datetime:
"""
Returns the time reference of the "LastReportedAt" tag, by the latest value.
If the tag doesn't exist, returns the current time.
Used for filtering resources by the latest modified time recorded.

Example:
datetime.datetime(2019, 3, 13, 14, 45, 33, tzinfo=tzutc())
"""
modified_date = None
if self.properties:
modified_date = max(self.properties, key=lambda x: x == "LastReportedAt").get(
"LastReportedAt"
)
return modified_date


class EC2System(System, VmMixin, TemplateMixin, StackMixin, NetworkMixin):
"""EC2 Management System, powered by boto
Expand Down Expand Up @@ -667,9 +710,10 @@ def _get_instances(self, **kwargs):
instances = list()
for reservation in reservations:
for instance in reservation.get("Instances"):
instance_id = instance.get("InstanceId")
instances.append(
EC2Instance(
system=self, raw=self.ec2_resource.Instance(instance.get("InstanceId"))
system=self, raw=self.ec2_resource.Instance(instance_id), uuid=instance_id
)
)
return instances
Expand Down Expand Up @@ -1814,28 +1858,43 @@ def list_resources(self, query="", view="") -> list[ResourceExplorerResource]:
Args:
query: keywords and filters for resources; default is "" (all)
view: arn of the view to use for the query; default is "" (default view)

Return:
a list of resources satisfying the query

Examples:
Use query "tag.key:kubernetes.io/cluster/*" to list OCP resources
"""
resource_list = []
kwargs = {"system": self}
args = {"QueryString": query}
if view:
args["ViewArn"] = view
list = []

paginator = self.resource_explorer_connection.get_paginator("search")
page_iterator = paginator.paginate(**args)
for page in page_iterator:
resources = page.get("Resources")
for r in resources:
resource = ResourceExplorerResource(
arn=r.get("Arn"),
region=r.get("Region"),
service=r.get("Service"),
properties=r.get("Properties"),
resource_type=r.get("ResourceType"),

try:
for page in page_iterator:
resources = page.get("Resources")
for r in resources:
resource = ResourceExplorerResource(
arn=r.get("Arn"),
region=r.get("Region"),
service=r.get("Service"),
properties=r.get("Properties"),
resource_type=r.get("ResourceType"),
**kwargs,
)
resource_list.append(resource)
except ClientError as error:
if error.response["Error"]["Code"] == "UnauthorizedException":
# Raised when the client uses a region that is not defined as an
# Index Aggregator in the Resource Explorer. Official docs:
# https://docs.aws.amazon.com/resource-explorer/latest/userguide/manage-aggregator-region.html
self.logger.info(
f"Region {self._region_name} is not an Index Aggregator. "
f"Cannot fetch resources with current client."
)
list.append(resource)
return list
else:
raise error
return resource_list
Loading