diff --git a/CHANGELOG.md b/CHANGELOG.md index 68538ae6..3bd24291 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - remove_user_from_tenant command + - Added support for tenant start/stop ### Updates - changed handling of tenant arg in user resource diff --git a/src/duplo_resource/rds.py b/src/duplo_resource/rds.py index 485fe1ec..79891909 100644 --- a/src/duplo_resource/rds.py +++ b/src/duplo_resource/rds.py @@ -65,6 +65,22 @@ def wait_check(): "message": "DB instance stopped" } + @Command() + def start(self, + name: args.NAME, + wait: args.WAIT=False): + """Start a DB instance.""" + def wait_check(): + i = self.find(name) + if i["InstanceStatus"] in ["starting"]: + raise DuploError(f"DB instance {name} is still starting") + self.duplo.post(self.endpoint(name, "start")) + if wait: + self.wait(wait_check, 1800, 10) + return { + "message": "DB instance started" + } + @Command() def reboot(self, name: args.NAME, diff --git a/src/duplo_resource/tenant.py b/src/duplo_resource/tenant.py index 3ca01e77..cd5ba1e3 100644 --- a/src/duplo_resource/tenant.py +++ b/src/duplo_resource/tenant.py @@ -402,3 +402,123 @@ def region(self, return { "region": response.json() } + + @Command() + def start(self, name: args.NAME = None, wait: args.WAIT=False, exclude: args.EXCLUDE=None): + """Start Tenant All Resources + + Starts all resources of a tenant. + + Usage: Basic CLI Use + ```bash + duploctl tenant start + ``` + + Args: + wait: Wait for the resources to start. + exclude (optional): A list of resources to exclude from starting. Can include: + - hosts/: Exclude a specific host. + - rds/: Exclude a specific RDS instance. + - hosts/at/: Exclude hosts with specific allocation tags. + + Returns: + message: A success message. + """ + service_types = {"hosts": [], "rds": []} + host_at = [] + if exclude: + for item in exclude: + category, value = item.split('/', 1) + if 'at/' in value: + _, at_name = value.split('at/', 1) + host_at.append(at_name) + elif category in {"hosts", "rds"}: + tenant_name = self.find(name)['AccountName'] + if category == "hosts": + prefix = f"duploservices-{tenant_name}" + value = f"{prefix}-{value}" if not value.startswith(prefix) else value + elif category == "rds": + value = f"duplo{value}" if not value.startswith("duplo") else value + service_types[category].append(value) + else: + print(f"Unknown service: {category}") + + host_at_exclude = self.get_hosts_to_exclude(host_at) + service_types['hosts'] = list(set(service_types['hosts']) | set(host_at_exclude)) + + for service_type in service_types.keys(): + service = self.duplo.load(service_type) + for item in service.list(): + service_name = service.name_from_body(item) + if service_name not in service_types[service_type]: + service.start(service_name, wait) + return { + "message": "Successfully started all resources for tenant" + } + + @Command() + def stop(self, name: args.NAME = None, wait: args.WAIT=False, exclude: args.EXCLUDE=None): + """Stop Tenant All Resources + + Stops all resources of a tenant. + + Usage: Basic CLI Use + ```bash + duploctl tenant stop + ``` + + Args: + wait: Wait for the resources to stop. + exclude (optional): A list of resources to exclude from stopping. Can include: + - hosts/: Exclude a specific host. + - rds/: Exclude a specific RDS instance. + - hosts/at/: Exclude hosts with specific allocation tags. + + Returns: + message: A success message. + """ + service_types = {"hosts": [], "rds": []} + host_at = [] + if exclude: + for item in exclude: + category, value = item.split('/', 1) + if 'at/' in value: + _, at_name = value.split('at/', 1) + host_at.append(at_name) + elif category in {"hosts", "rds"}: + tenant_name = self.find(name)['AccountName'] + if category == "hosts": + prefix = f"duploservices-{tenant_name}" + value = f"{prefix}-{value}" if not value.startswith(prefix) else value + elif category == "rds": + value = f"duplo{value}" if not value.startswith("duplo") else value + service_types[category].append(value) + else: + print(f"Unknown service: {category}") + + host_at_exclude = self.get_hosts_to_exclude(host_at) + service_types['hosts'] = list(set(service_types['hosts']) | set(host_at_exclude)) + + for service_type in service_types.keys(): + service = self.duplo.load(service_type) + for item in service.list(): + service_name = service.name_from_body(item) + if service_name not in service_types[service_type]: + service.stop(service_name, wait) + return { + "message": "Successfully stopped all resources for tenant" + } + + def get_hosts_to_exclude(self, host_at): + host_at_exclude = [] + hosts = self.duplo.load('hosts').list() + for host in hosts: + allocation_tags_value = '' + minion_tags = host.get('MinionTags', []) + for tag in minion_tags: + if tag.get('Key') == 'AllocationTags': + allocation_tags_value = tag.get('Value') + + if allocation_tags_value in host_at: + host_at_exclude.append(host['FriendlyName']) + return host_at_exclude diff --git a/src/duplocloud/args.py b/src/duplocloud/args.py index 626bd952..e3c6b886 100644 --- a/src/duplocloud/args.py +++ b/src/duplocloud/args.py @@ -92,6 +92,10 @@ version=f"%(prog)s {VERSION}", type=bool) +EXCLUDE = Arg("exclude", '--exclude', + action='append', + help='Exclude from the command') + # The rest are resource level args for commands SERVICE = Arg('service', help='The service to run',