Skip to content

Add resource teardown, CLI validation, and idempotent provisioning#6

Draft
Copilot wants to merge 3 commits intocopilot/add-cloud-resource-provisioningfrom
copilot/add-resource-teardown-methods
Draft

Add resource teardown, CLI validation, and idempotent provisioning#6
Copilot wants to merge 3 commits intocopilot/add-cloud-resource-provisioningfrom
copilot/add-resource-teardown-methods

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 25, 2026

Adds production safety features: CLI validation before cloud API calls, idempotent provisioning (no crash on already-exists), and resource teardown functions to prevent cloud bill accumulation.

New Module: fastops/teardown.py (362 lines)

  • destroy(resource_type, name, provider, **kw) - Destroy individual resources
  • destroy_stack(resources, provider, **kw) - Bulk teardown in reverse dependency order
  • status(resource_type, name, provider, **kw) - Resource health checks
  • _infer_resource_type(env_dict) - Infer resource type from environment variables

All teardown operations are idempotent and return structured responses instead of raising on not-found.

Updates: fastops/resources.py

CLI Validation:

  • _check_cli(cli_name) validates CLI presence before API calls with actionable error messages
  • Applied to all AWS (aws), Azure (az), and GCP (gcloud) provider branches

Idempotent Provisioning:

  • AWS: Catches DBInstanceAlreadyExists, CacheClusterAlreadyExists, BucketAlreadyOwnedByYou, ResourceAlreadyExistsException, ResourceConflictException → describes existing resource
  • Azure: Catches ResourceAlreadyExists and text-based "already exists" patterns → shows existing resource
  • GCP: Added --quiet flags, catches "already exists" in stderr

Usage

from fastops import database, destroy, destroy_stack, status

# Provision (now idempotent)
env, _ = database('mydb', 'postgres', 'aws')  # Creates or returns existing
env, _ = database('mydb', 'postgres', 'aws')  # No error on second call

# Teardown individual resource
result = destroy('database', 'mydb', 'aws')
# {'destroyed': True, 'resource': 'mydb', 'provider': 'aws', 'message': '...'}

# Teardown entire stack (reverse order)
resources = {
    'db': lambda: database('db', provider='docker'),
    'cache': lambda: cache('redis', provider='docker')
}
results = destroy_stack(resources, 'docker')  # Destroys cache, then db

# Health check
status('database', 'mydb', 'docker')  # {'healthy': False, 'provider': 'docker', ...}

Coverage

All 7 resource types (database, cache, queue, bucket, llm, search, function) × all providers (Docker, AWS, Azure, GCP, OpenAI).

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • metadata.google.internal
    • Triggering command: /usr/bin/../lib/google-cloud-sdk/platform/bundledpythonunix/bin/python3 /usr/bin/../lib/google-cloud-sdk/platform/bundledpythonunix/bin/python3 /usr/bin/../lib/google-cloud-sdk/lib/gcloud.py pubsub topics create testqueue --quiet (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

P1: Error handling, idempotency, and resource teardown for fastops/resources.py

This PR adds production-critical safety features to resources.py: proper error handling so CLI-not-installed and resource-already-exists don't crash, idempotent provisioning, and destroy() functions so users don't accumulate cloud bills.

Branch off copilot/add-cloud-resource-provisioning.


Part 1: New file fastops/teardown.py

Module docstring

"""Resource teardown and lifecycle management. Safely destroy provisioned resources."""

__all__

['destroy', 'destroy_stack', 'status']

Imports

import os, json, subprocess, shutil


Function: destroy(resource_type, name, provider='docker', **kw)

Destroy a single provisioned resource. resource_type is one of: 'database', 'cache', 'queue', 'bucket', 'llm', 'search', 'function'.

Dispatches to _destroy_{resource_type}(name, provider, **kw).

Each destroyer should:

  1. Wrap the actual teardown call in try/except
  2. If resource doesn't exist, print a warning but don't error
  3. Return a dict {'destroyed': True/False, 'resource': name, 'provider': provider, 'message': str}

_destroy_database(name, provider, **kw)

Docker: No-op for compose-managed resources (handled by docker compose down -v). Return {'destroyed': True, 'message': 'Remove via docker compose down -v'}.

AWS:

from .aws import callaws
try:
    callaws('rds', 'delete-db-instance',
            '--db-instance-identifier', name,
            '--skip-final-snapshot',
            '--delete-automated-backups')
except Exception as e:
    if 'DBInstanceNotFound' in str(e): return {'destroyed': False, 'message': f'RDS instance {name} not found'}
    raise
return {'destroyed': True, 'message': f'RDS instance {name} deletion initiated'}

Azure:

from .azure import callaz
rg = kw.get('resource_group')
try:
    callaz('postgres', 'flexible-server', 'delete', '--name', name, '--resource-group', rg, '--yes')
except Exception as e:
    if 'ResourceNotFound' in str(e): return {'destroyed': False, 'message': f'Azure DB {name} not found'}
    raise
return {'destroyed': True, 'message': f'Azure Postgres {name} deleted'}

_destroy_cache(name, provider, **kw)

  • Docker: no-op (compose down)
  • AWS: callaws('elasticache', 'delete-cache-cluster', '--cache-cluster-id', name)
  • Azure: callaz('redis', 'delete', '--name', name, '--resource-group', rg, '--yes')

_destroy_queue(name, provider, **kw)

  • Docker: no-op
  • AWS: First get queue URL via callaws('sqs', 'get-queue-url', '--queue-name', name), then callaws('sqs', 'delete-queue', '--queue-url', url)
  • Azure: callaz('servicebus', 'namespace', 'delete', '--name', kw.get('namespace', f'{name}-ns'), '--resource-group', rg, '--yes')
  • GCP: subprocess.run(['gcloud', 'pubsub', 'topics', 'delete', name, '--quiet']) and delete subscription f'{name}-sub'

_destroy_bucket(name, provider, **kw)

  • Docker: no-op
  • AWS: First empty the bucket callaws('s3', 'rm', f's3://{name}', '--recursive'), then callaws('s3api', 'delete-bucket', '--bucket', name)
  • Azure: Delete container then storage account: callaz('storage', 'container', 'delete', '--name', name, '--account-name', account_name, '--yes'), then callaz('storage', 'account', 'delete', '--name', account_name, '--resource-group', rg, '--yes')
  • GCP: subprocess.run(['gcloud', 'storage', 'rm', '-r', f'gs://{name}', '--quiet'])

_destroy_llm(name, provider, **kw)

  • Docker: no-op
  • OpenAI: no-op (nothing to tear down)
  • Azure: Delete deployment then account: callaz('cognitiveservices', 'account', 'deployment', 'delete', ...) then callaz('cognitiveservices', 'account', 'delete', ...)
  • AWS/Bedrock: no-op

_destroy_search(name, provider, **kw)

  • Docker: no-op
  • AWS: callaws('opensearch', 'delete-domain', '--domain-name', name)
  • Azure: callaz('search', 'service', 'delete', '--name', name, '--resource-group', rg, '--yes')

_destroy_function(name, provider, **kw)

  • AWS: callaws('lambda', 'delete-function', '--function-name', name)
  • Azure: callaz('functionapp', 'delete', '--name', name, '--resource-group', rg, '--yes')
  • GCP: subprocess.run(['gcloud', 'functions', 'delete', name, '--quiet', '--region', kw.get('region', 'us-central1')])

Function: destroy_stack(resources, provider='docker', **kw)

Takes the same resources dict format as stack() in resources.py. Tears down all resources in reverse order.

def destroy_stack(resources, provider='docker', **kw):
    'Tear down all resources in a stack (reverse order for dependency safety)'
    results = {}
    # Reverse to tear down dependents before dependencies
    for name in reversed(list(resources.keys())):
        resource_fn = resources[name]
        # Infer resource type from the function name or env output
        env, _ = resource_fn()
        rtype = _infer_resource_type(env)
        results[name] = d...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

Copilot AI and others added 2 commits February 25, 2026 04:30
Co-authored-by: Karthik777 <7102951+Karthik777@users.noreply.github.com>
Co-authored-by: Karthik777 <7102951+Karthik777@users.noreply.github.com>
Copilot AI changed the title [WIP] Add error handling and resource teardown features for resources.py Add resource teardown, CLI validation, and idempotent provisioning Feb 25, 2026
Copilot AI requested a review from Karthik777 February 25, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants