delete functionality on instances page
This commit is contained in:
189
routes/main.py
189
routes/main.py
@@ -19,6 +19,7 @@ import smtplib
|
||||
import requests
|
||||
from functools import wraps
|
||||
import socket
|
||||
from urllib.parse import urlparse
|
||||
|
||||
# Set up logging to show in console
|
||||
logging.basicConfig(
|
||||
@@ -491,13 +492,197 @@ def init_routes(main_bp):
|
||||
return jsonify({'error': 'Unauthorized'}), 403
|
||||
|
||||
instance = Instance.query.get_or_404(instance_id)
|
||||
|
||||
# Get Portainer settings
|
||||
portainer_settings = KeyValueSettings.get_value('portainer_settings')
|
||||
if not portainer_settings:
|
||||
current_app.logger.warning(f"Portainer settings not configured, proceeding with database deletion only for instance {instance.name}")
|
||||
# Continue with database deletion even if Portainer is not configured
|
||||
try:
|
||||
db.session.delete(instance)
|
||||
db.session.commit()
|
||||
current_app.logger.info(f"Successfully deleted instance from database: {instance.name}")
|
||||
return jsonify({'message': 'Instance deleted from database (Portainer not configured)'})
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f"Error deleting instance {instance.name} from database: {str(e)}")
|
||||
return jsonify({'error': f'Failed to delete instance: {str(e)}'}), 500
|
||||
|
||||
try:
|
||||
# First, delete the Portainer stack and its volumes if stack information exists
|
||||
if instance.portainer_stack_id and instance.portainer_stack_name:
|
||||
current_app.logger.info(f"Deleting Portainer stack: {instance.portainer_stack_name} (ID: {instance.portainer_stack_id})")
|
||||
|
||||
# Get Portainer endpoint ID (assuming it's the first endpoint)
|
||||
try:
|
||||
endpoint_response = requests.get(
|
||||
f"{portainer_settings['url'].rstrip('/')}/api/endpoints",
|
||||
headers={
|
||||
'X-API-Key': portainer_settings['api_key'],
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if not endpoint_response.ok:
|
||||
current_app.logger.error(f"Failed to get Portainer endpoints: {endpoint_response.text}")
|
||||
return jsonify({'error': 'Failed to get Portainer endpoints'}), 500
|
||||
|
||||
endpoints = endpoint_response.json()
|
||||
if not endpoints:
|
||||
current_app.logger.error("No Portainer endpoints found")
|
||||
return jsonify({'error': 'No Portainer endpoints found'}), 400
|
||||
|
||||
endpoint_id = endpoints[0]['Id']
|
||||
|
||||
# Delete the stack (this will also remove associated volumes)
|
||||
delete_url = f"{portainer_settings['url'].rstrip('/')}/api/stacks/{instance.portainer_stack_id}"
|
||||
delete_response = requests.delete(
|
||||
delete_url,
|
||||
headers={
|
||||
'X-API-Key': portainer_settings['api_key'],
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
params={'endpointId': endpoint_id},
|
||||
timeout=60 # Give more time for stack deletion
|
||||
)
|
||||
|
||||
if delete_response.ok:
|
||||
current_app.logger.info(f"Successfully deleted Portainer stack: {instance.portainer_stack_name}")
|
||||
else:
|
||||
current_app.logger.warning(f"Failed to delete Portainer stack: {delete_response.status_code} - {delete_response.text}")
|
||||
# Continue with database deletion even if Portainer deletion fails
|
||||
|
||||
# Also try to delete any orphaned volumes associated with this stack
|
||||
try:
|
||||
volumes_url = f"{portainer_settings['url'].rstrip('/')}/api/endpoints/{endpoint_id}/docker/volumes"
|
||||
volumes_response = requests.get(
|
||||
volumes_url,
|
||||
headers={
|
||||
'X-API-Key': portainer_settings['api_key'],
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if volumes_response.ok:
|
||||
volumes = volumes_response.json().get('Volumes', [])
|
||||
stack_volumes = [vol for vol in volumes if vol.get('Labels', {}).get('com.docker.stack.namespace') == instance.portainer_stack_name]
|
||||
|
||||
for volume in stack_volumes:
|
||||
volume_name = volume.get('Name')
|
||||
if volume_name:
|
||||
delete_volume_url = f"{volumes_url}/{volume_name}"
|
||||
volume_delete_response = requests.delete(
|
||||
delete_volume_url,
|
||||
headers={
|
||||
'X-API-Key': portainer_settings['api_key'],
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if volume_delete_response.ok:
|
||||
current_app.logger.info(f"Successfully deleted volume: {volume_name}")
|
||||
else:
|
||||
current_app.logger.warning(f"Failed to delete volume {volume_name}: {volume_delete_response.status_code}")
|
||||
else:
|
||||
current_app.logger.warning(f"Failed to get volumes list: {volumes_response.status_code}")
|
||||
except Exception as volume_error:
|
||||
current_app.logger.warning(f"Error cleaning up volumes: {str(volume_error)}")
|
||||
|
||||
except requests.exceptions.RequestException as req_error:
|
||||
current_app.logger.error(f"Network error during Portainer operations: {str(req_error)}")
|
||||
# Continue with database deletion even if Portainer operations fail
|
||||
else:
|
||||
current_app.logger.info(f"No Portainer stack information found for instance {instance.name}, proceeding with database deletion only")
|
||||
|
||||
# Clean up NGINX proxy host if NGINX settings are configured
|
||||
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
||||
if nginx_settings and instance.main_url:
|
||||
current_app.logger.info(f"Cleaning up NGINX proxy host for instance {instance.name}")
|
||||
try:
|
||||
# Extract domain from main_url
|
||||
parsed_url = urlparse(instance.main_url)
|
||||
domain = parsed_url.netloc
|
||||
|
||||
if domain:
|
||||
# Get NGINX JWT token
|
||||
token_response = requests.post(
|
||||
f"{nginx_settings['url'].rstrip('/')}/api/tokens",
|
||||
json={
|
||||
'identity': nginx_settings['username'],
|
||||
'secret': nginx_settings['password']
|
||||
},
|
||||
headers={'Content-Type': 'application/json'},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if token_response.ok:
|
||||
token_data = token_response.json()
|
||||
token = token_data.get('token')
|
||||
|
||||
if token:
|
||||
# Get all proxy hosts to find the one matching this domain
|
||||
proxy_hosts_response = requests.get(
|
||||
f"{nginx_settings['url'].rstrip('/')}/api/nginx/proxy-hosts",
|
||||
headers={
|
||||
'Authorization': f'Bearer {token}',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if proxy_hosts_response.ok:
|
||||
proxy_hosts = proxy_hosts_response.json()
|
||||
|
||||
# Find proxy host with matching domain
|
||||
matching_proxy = None
|
||||
for proxy_host in proxy_hosts:
|
||||
if proxy_host.get('domain_names') and domain in proxy_host['domain_names']:
|
||||
matching_proxy = proxy_host
|
||||
break
|
||||
|
||||
if matching_proxy:
|
||||
# Delete the proxy host
|
||||
delete_response = requests.delete(
|
||||
f"{nginx_settings['url'].rstrip('/')}/api/nginx/proxy-hosts/{matching_proxy['id']}",
|
||||
headers={
|
||||
'Authorization': f'Bearer {token}',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if delete_response.ok:
|
||||
current_app.logger.info(f"Successfully deleted NGINX proxy host for domain: {domain}")
|
||||
else:
|
||||
current_app.logger.warning(f"Failed to delete NGINX proxy host: {delete_response.status_code} - {delete_response.text}")
|
||||
else:
|
||||
current_app.logger.info(f"No NGINX proxy host found for domain: {domain}")
|
||||
else:
|
||||
current_app.logger.warning(f"Failed to get NGINX proxy hosts: {proxy_hosts_response.status_code}")
|
||||
else:
|
||||
current_app.logger.warning("No NGINX token received")
|
||||
else:
|
||||
current_app.logger.warning(f"Failed to authenticate with NGINX: {token_response.status_code}")
|
||||
except Exception as nginx_error:
|
||||
current_app.logger.warning(f"Error cleaning up NGINX proxy host: {str(nginx_error)}")
|
||||
# Continue with database deletion even if NGINX cleanup fails
|
||||
else:
|
||||
current_app.logger.info(f"No NGINX settings configured or no main_url for instance {instance.name}, skipping NGINX cleanup")
|
||||
|
||||
# Now delete the instance from the database
|
||||
db.session.delete(instance)
|
||||
db.session.commit()
|
||||
return jsonify({'message': 'Instance deleted successfully'})
|
||||
|
||||
current_app.logger.info(f"Successfully deleted instance: {instance.name}")
|
||||
return jsonify({'message': 'Instance and all associated resources (Portainer stack, volumes, NGINX proxy host) deleted successfully'})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 400
|
||||
current_app.logger.error(f"Error deleting instance {instance.name}: {str(e)}")
|
||||
return jsonify({'error': f'Failed to delete instance: {str(e)}'}), 500
|
||||
|
||||
@main_bp.route('/instances/<int:instance_id>/status')
|
||||
@login_required
|
||||
|
||||
Reference in New Issue
Block a user