Refactor DeployAccount to eliminate code duplication and improve error handling

- Remove duplicate code blocks that handled success and "already deployed" cases
- Consolidate deployment logic into single code path
- Add validation for backup plan state before deployment
- Add specific exception handling for API requests and JSON parsing
- Add timeout to API requests (30 seconds)
- Change API endpoint from HTTP to HTTPS for security
- Improve error messages with actionable guidance
- Add comprehensive logging for all error paths
- Clarify return status: status=1 only on full success, status=0 on any failure
- Add early validation for missing SSH public key
- Handle edge case where account is deployed but destination creation fails
This commit is contained in:
usmannasir
2025-10-14 15:35:02 +05:00
parent 4da45eebf1
commit 6381a9ee55

View File

@@ -2432,122 +2432,159 @@ class BackupManager:
return HttpResponse(json_data) return HttpResponse(json_data)
def DeployAccount(self, request=None, userID=None, data=None): def DeployAccount(self, request=None, userID=None, data=None):
user = Administrator.objects.get(pk=userID) """Deploy a One-Click Backup account by creating SFTP credentials on remote server"""
try:
user = Administrator.objects.get(pk=userID)
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
userID = request.session['userID'] # Parse request data
currentACL = ACLManager.loadedACL(userID) try:
import json data = json.loads(request.body)
backup_id = data['id']
except (json.JSONDecodeError, KeyError) as e:
logging.CyberCPLogFileWriter.writeToFile(f"Invalid request data in DeployAccount: {str(e)}")
return HttpResponse(json.dumps({
'status': 0,
'error_message': 'Invalid request format. Missing required field: id'
}))
data = json.loads(request.body) # Get backup plan
id = data['id'] from IncBackups.models import OneClickBackups
try:
ocb = OneClickBackups.objects.get(pk=backup_id, owner=user)
except OneClickBackups.DoesNotExist:
logging.CyberCPLogFileWriter.writeToFile(f"OneClickBackup {backup_id} not found for user {userID} [DeployAccount]")
return HttpResponse(json.dumps({
'status': 0,
'error_message': 'Backup plan not found or you do not have permission to access it.'
}))
from IncBackups.models import OneClickBackups # Check if already deployed
ocb = OneClickBackups.objects.get(pk=id, owner=user) if ocb.state == 1:
logging.CyberCPLogFileWriter.writeToFile(f"Backup plan {backup_id} already deployed [DeployAccount]")
return HttpResponse(json.dumps({
'status': 1,
'error_message': 'This backup account is already deployed.'
}))
data = {} # Read SSH public key
try:
ssh_pub_key = ProcessUtilities.outputExecutioner('cat /root/.ssh/cyberpanel.pub').strip()
if not ssh_pub_key or ssh_pub_key.startswith('cat:'):
raise Exception("Failed to read SSH public key")
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Failed to read SSH public key: {str(e)} [DeployAccount]")
return HttpResponse(json.dumps({
'status': 0,
'error_message': 'SSH public key not found. Please ensure One-Click Backup is properly configured.'
}))
#### # Prepare API request
url = 'https://platform.cyberpersons.com/Billing/CreateSFTPAccount'
payload = {
'sub': ocb.subscription,
'key': ssh_pub_key,
'sftpUser': ocb.sftpUser,
'serverIP': ACLManager.fetchIP(),
'planName': ocb.planName
}
headers = {'Content-Type': 'application/json'}
import requests # Make API request
import json try:
response = requests.post(url, headers=headers, data=json.dumps(payload), timeout=30)
except requests.exceptions.RequestException as e:
logging.CyberCPLogFileWriter.writeToFile(f"API request failed: {str(e)} [DeployAccount]")
return HttpResponse(json.dumps({
'status': 0,
'error_message': f'Failed to connect to backup platform: {str(e)}'
}))
# Define the URL of the endpoint # Handle non-200 responses
url = 'http://platform.cyberpersons.com/Billing/CreateSFTPAccount' # Replace with your actual endpoint URL if response.status_code != 200:
logging.CyberCPLogFileWriter.writeToFile(f"API returned status {response.status_code}: {response.text} [DeployAccount]")
return HttpResponse(json.dumps({
'status': 0,
'error_message': f'Backup platform returned error (HTTP {response.status_code}). Please try again later.'
}))
# Define the payload to send in the POST request # Parse API response
payload = { try:
'sub': ocb.subscription, response_data = response.json()
'key': ProcessUtilities.outputExecutioner(f'cat /root/.ssh/cyberpanel.pub'), except json.JSONDecodeError:
# Replace with the actual SSH public key logging.CyberCPLogFileWriter.writeToFile(f"Invalid JSON response from API: {response.text} [DeployAccount]")
'sftpUser': ocb.sftpUser, return HttpResponse(json.dumps({
'serverIP': ACLManager.fetchIP(), # Replace with the actual server IP 'status': 0,
'planName': ocb.planName 'error_message': 'Received invalid response from backup platform.'
} }))
# Convert the payload to JSON format # Check if deployment was successful or already deployed
headers = {'Content-Type': 'application/json'} api_status = response_data.get('status')
dataRet = json.dumps(payload) api_error = response_data.get('error_message', '')
# Make the POST request if api_status == 1 or api_error == "Already deployed.":
response = requests.post(url, headers=headers, data=dataRet) # Both cases are success - account exists and is ready
deployment_status = "created" if api_status == 1 else "already deployed"
# Handle the response logging.CyberCPLogFileWriter.writeToFile(f"SFTP account {deployment_status} for {ocb.sftpUser} [DeployAccount]")
# Handle the response
if response.status_code == 200:
response_data = response.json()
if response_data.get('status') == 1:
# Update backup plan state
ocb.state = 1 ocb.state = 1
ocb.save() ocb.save()
print("SFTP account created successfully.") # Create local backup destination
finalDic = {
finalDic = {} 'IPAddress': response_data.get('ipAddress'),
'password': 'NOT-NEEDED',
finalDic['IPAddress'] = response_data.get('ipAddress') 'backupSSHPort': '22',
finalDic['password'] = 'NOT-NEEDED' 'userName': ocb.sftpUser,
finalDic['backupSSHPort'] = '22' 'type': 'SFTP',
finalDic['userName'] = ocb.sftpUser 'path': 'cpbackups',
finalDic['type'] = 'SFTP' 'name': ocb.sftpUser
finalDic['path'] = 'cpbackups' }
finalDic['name'] = ocb.sftpUser
wm = BackupManager()
response_inner = wm.submitDestinationCreation(userID, finalDic)
response_data_inner = json.loads(response_inner.content.decode('utf-8'))
# Extract the value of 'status'
if response_data_inner.get('status') == 0:
data_ret = {'status': 1, 'error_message': response_data_inner.get('error_message')}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
else:
data_ret = {'status': 1,}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
else:
if response_data.get('error_message') == "Already deployed.":
ocb.state = 1
ocb.save()
print("SFTP account created successfully.")
finalDic = {}
finalDic['IPAddress'] = response_data.get('ipAddress')
finalDic['password'] = 'NOT-NEEDED'
finalDic['backupSSHPort'] = '22'
finalDic['userName'] = ocb.sftpUser
finalDic['type'] = 'SFTP'
finalDic['path'] = 'cpbackups'
finalDic['name'] = ocb.sftpUser
try:
wm = BackupManager() wm = BackupManager()
response_inner = wm.submitDestinationCreation(userID, finalDic) response_inner = wm.submitDestinationCreation(userID, finalDic)
response_data_inner = json.loads(response_inner.content.decode('utf-8')) response_data_inner = json.loads(response_inner.content.decode('utf-8'))
# Extract the value of 'status'
if response_data_inner.get('status') == 0: if response_data_inner.get('status') == 0:
data_ret = {'status': 1, 'error_message': response_data_inner.get('error_message')} # Destination creation failed, but account is deployed
json_data = json.dumps(data_ret) logging.CyberCPLogFileWriter.writeToFile(
return HttpResponse(json_data) f"Destination creation failed: {response_data_inner.get('error_message')} [DeployAccount]"
else: )
data_ret = {'status': 1, } return HttpResponse(json.dumps({
json_data = json.dumps(data_ret) 'status': 0,
return HttpResponse(json_data) 'error_message': f"Account deployed but failed to create local destination: {response_data_inner.get('error_message')}"
}))
data_ret = {'status': 0, 'error_message': response_data.get('error_message')} # Full success
json_data = json.dumps(data_ret) return HttpResponse(json.dumps({
return HttpResponse(json_data) 'status': 1,
else: 'message': f'Backup account {deployment_status} successfully.'
data['message'] = f"[1991] Failed to create sftp account {response.text}" }))
data_ret = {'status': 0, 'error_message': response.text}
json_data = json.dumps(data_ret) except Exception as e:
return HttpResponse(json_data) logging.CyberCPLogFileWriter.writeToFile(f"Failed to create destination: {str(e)} [DeployAccount]")
return HttpResponse(json.dumps({
'status': 0,
'error_message': f'Account deployed but failed to create local destination: {str(e)}'
}))
else:
# API returned an error
logging.CyberCPLogFileWriter.writeToFile(f"API returned error: {api_error} [DeployAccount]")
return HttpResponse(json.dumps({
'status': 0,
'error_message': api_error or 'Unknown error occurred during deployment.'
}))
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Unexpected error in DeployAccount: {str(e)}")
return HttpResponse(json.dumps({
'status': 0,
'error_message': f'An unexpected error occurred: {str(e)}'
}))
def ReconfigureSubscription(self, request=None, userID=None, data=None): def ReconfigureSubscription(self, request=None, userID=None, data=None):
try: try: