diff --git a/cloudwash/cli.py b/cloudwash/cli.py index bbcff222..ec96a2a3 100644 --- a/cloudwash/cli.py +++ b/cloudwash/cli.py @@ -6,6 +6,7 @@ from cloudwash.providers.aws import cleanup as awsCleanup from cloudwash.providers.azure import cleanup as azureCleanup from cloudwash.providers.gce import cleanup as gceCleanup +from cloudwash.providers.podman import cleanup as podmanCleanup # Adding the pythonpath for importing modules from cloudwash packages # sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) @@ -115,6 +116,19 @@ def aws(ctx, vms, discs, nics, images, pips, stacks, _all): ) +@cleanup_providers.command(help="Cleanup Podman provider") +@click.option("--containers", is_flag=True, help="Remove containers from the podman host") +@click.pass_context +def podman(ctx, containers): + # Validate Podman Settings + validate_provider(ctx.command.name) + is_dry_run = ctx.parent.params["dry"] + podmanCleanup( + containers=containers, + dry_run=is_dry_run, + ) + + @cleanup_providers.command(help="Cleanup VMWare provider") @common_options @click.pass_context diff --git a/cloudwash/client.py b/cloudwash/client.py index 3db7f46d..b155dec5 100644 --- a/cloudwash/client.py +++ b/cloudwash/client.py @@ -34,11 +34,16 @@ def compute_client(compute_resource, **kwargs): password=settings.aws.auth.secret_key, region=kwargs['aws_region'], ) + elif compute_resource == "podman": + client = wrapanapi.Podman( + hostname=settings.podman.auth.hostname, + username=settings.podman.auth.username, + port=settings.podman.auth.ssh_port, + ) else: raise ValueError( f"{compute_resource} is an incorrect value. It should be one of azure or gce or ec2" ) - try: yield client finally: diff --git a/cloudwash/constants.py b/cloudwash/constants.py index a8e58d2c..750d1613 100644 --- a/cloudwash/constants.py +++ b/cloudwash/constants.py @@ -1,3 +1,4 @@ aws_data = ['VMS', 'NICS', 'DISCS', 'PIPS', 'RESOURCES', 'STACKS'] azure_data = ['VMS', 'NICS', 'DISCS', 'IMAGES', 'PIPS', 'RESOURCES'] gce_data = ['VMS', 'NICS', 'DISCS'] +container_data = ['CONTAINERS'] diff --git a/cloudwash/entities/providers.py b/cloudwash/entities/providers.py index 8ae29933..33dcd7cb 100644 --- a/cloudwash/entities/providers.py +++ b/cloudwash/entities/providers.py @@ -1,3 +1,4 @@ +from cloudwash.entities.resources.containers import CleanPodmanContainers from cloudwash.entities.resources.discs import CleanAWSDiscs from cloudwash.entities.resources.discs import CleanAzureDiscs from cloudwash.entities.resources.images import CleanAWSImages @@ -81,3 +82,13 @@ class GCECleanup(providerCleanup): def __init__(self, client): self.client = client super().__init__(client) + + +class PodmanCleanup(providerCleanup): + def __init__(self, client): + self.client = client + super().__init__(client) + + @property + def containers(self): + return CleanPodmanContainers(client=self.client) diff --git a/cloudwash/entities/resources/base.py b/cloudwash/entities/resources/base.py index 5aff9844..aadf16c2 100644 --- a/cloudwash/entities/resources/base.py +++ b/cloudwash/entities/resources/base.py @@ -132,6 +132,32 @@ def _set_dry(self): pass +class ContainerCleanup(ResourceCleanup): + @abstractmethod + def list(self): + pass + + @abstractmethod + def cleanup(self): + pass + + @abstractmethod + def stop(self): + pass + + @abstractmethod + def remove(self): + pass + + @abstractmethod + def skip(self): + pass + + @abstractmethod + def _set_dry(self): + pass + + class ResourceCleanupManager: def __init__(self): self.resources = [] diff --git a/cloudwash/entities/resources/containers.py b/cloudwash/entities/resources/containers.py new file mode 100644 index 00000000..6e266a71 --- /dev/null +++ b/cloudwash/entities/resources/containers.py @@ -0,0 +1,60 @@ +from cloudwash.config import settings +from cloudwash.entities.resources.base import ContainerCleanup +from cloudwash.logger import logger +from cloudwash.utils import dry_data +from cloudwash.utils import total_running_time + + +class CleanContainers(ContainerCleanup): + def __init__(self, client): + self.client = client + self._delete = [] + self._stop = [] + self._skip = [] + self.list() + + def _set_dry(self): + dry_data['CONTAINERS']['delete'] = self._delete + dry_data['CONTAINERS']['stop'] = self._stop + dry_data['CONTAINERS']['skip'] = self._skip + + def list(self): + pass + + def stop(self): + for container in self._stop: + self.client.get_container(key=container).stop() + logger.info(f"Stopped Podman Containers: \n{self._stop}") + + def remove(self): + for container in self._delete: + self.client.get_container(key=container).delete(force=True) + logger.info(f"Removed Podman Containers: \n{self._delete}") + + def skip(self): + logger.info(f"Skipped VMs: \n{self._skip}") + + def cleanup(self): + if not settings.dry_run: + self.remove() + self.stop() + self.skip() + + +class CleanPodmanContainers(CleanContainers): + def list(self): + + for container in self.client.containers: + if container.name in settings.podman.exceptions.container.skip_list: + self._skip.append(container.name) + continue + elif total_running_time(container).minutes >= settings.podman.criteria.container.sla: + + if container.name in settings.podman.exceptions.container.stop_list: + self._stop.append(container.name) + continue + + elif container.name.startswith(settings.podman.criteria.container.name_prefix): + self._delete.append(container.name) + + self._set_dry() diff --git a/cloudwash/providers/podman.py b/cloudwash/providers/podman.py new file mode 100644 index 00000000..75968606 --- /dev/null +++ b/cloudwash/providers/podman.py @@ -0,0 +1,19 @@ +"""Azure CR Cleanup Utilities""" +from cloudwash.client import compute_client +from cloudwash.constants import container_data as data +from cloudwash.entities.providers import PodmanCleanup +from cloudwash.utils import dry_data +from cloudwash.utils import echo_dry + + +def cleanup(**kwargs): + is_dry_run = kwargs.get("dry_run", False) + for items in data: + dry_data[items]['delete'] = [] + with compute_client("podman") as podman_client: + podmancleanup = PodmanCleanup(client=podman_client) + # Actual Cleaning and dry execution + if kwargs["containers"]: + podmancleanup.containers.cleanup() + if is_dry_run: + echo_dry(dry_data) diff --git a/cloudwash/utils.py b/cloudwash/utils.py index e450b991..9b9fde45 100644 --- a/cloudwash/utils.py +++ b/cloudwash/utils.py @@ -7,6 +7,8 @@ from cloudwash.logger import logger _vms_dict = {"VMS": {"delete": [], "stop": [], "skip": []}} +_containers_dict = {"CONTAINERS": {"delete": [], "stop": [], "skip": []}} + dry_data = { "NICS": {"delete": []}, "DISCS": {"delete": []}, @@ -16,6 +18,7 @@ "IMAGES": {"delete": []}, } dry_data.update(_vms_dict) +dry_data.update(_containers_dict) def echo_dry(dry_data=None) -> None: @@ -28,6 +31,9 @@ def echo_dry(dry_data=None) -> None: deletable_vms = dry_data["VMS"]["delete"] stopable_vms = dry_data["VMS"]["stop"] skipped_vms = dry_data["VMS"]["skip"] + deletable_containers = dry_data["CONTAINERS"]["delete"] + stopable_containers = dry_data["CONTAINERS"]["stop"] + skipped_containers = dry_data["CONTAINERS"]["skip"] deletable_discs = dry_data["DISCS"]["delete"] deletable_nics = dry_data["NICS"]["delete"] deletable_images = dry_data["IMAGES"]["delete"] @@ -39,6 +45,12 @@ def echo_dry(dry_data=None) -> None: f"VMs:\n\tDeletable: {deletable_vms}\n\tStoppable: {stopable_vms}\n\t" f"Skip: {skipped_vms}" ) + if deletable_containers or stopable_containers or skipped_containers: + logger.info( + f"Containers:\n\tDeletable: {deletable_containers}\n\t" + f"Stoppable: {stopable_containers}\n\t" + f"Skip: {skipped_containers}" + ) if deletable_discs: logger.info(f"DISCs:\n\tDeletable: {deletable_discs}") if deletable_nics: @@ -61,6 +73,9 @@ def echo_dry(dry_data=None) -> None: deletable_resources, deletable_stacks, deletable_images, + deletable_containers, + stopable_containers, + skipped_containers, ] ): logger.info("\nNo resources are eligible for cleanup!") diff --git a/settings.yaml.template b/settings.yaml.template index 70c65165..af7c65a7 100644 --- a/settings.yaml.template +++ b/settings.yaml.template @@ -109,3 +109,20 @@ AWS: # CloudFormations names that would be skipped from cleanup STACK_LIST: [] IMAGES: [] +PODMAN: + AUTH: + HOSTNAME: + USERNAME: root + SSH_PORT: 22 + CRITERIA: + CONTAINER: + # Container Prepended Test + NAME_PREFIX: 'satci' + # Container running time in minutes + SLA: 720 + EXCEPTIONS: + CONTAINER: + # Containers to skip and not to delete + SKIP_LIST: [] + # Containers to stop if running but not delete + STOP_LIST: []