UNPKG

cfn-forge

Version:

CloudFormation deployment automation tool with git-based workflows

371 lines (326 loc) • 9.59 kB
/** * AWS Error Parser * Comprehensive AWS-specific error parsing and recovery suggestions */ const { logger } = require('./logger'); /** * CloudFormation Error Patterns */ const CF_ERROR_PATTERNS = { ValidationError: { patterns: [ /Template format error: (.*)/, /Invalid template property or properties (.*)/, /Template validation error: (.*)/, ], category: 'template', suggestion: 'Check CloudFormation template syntax and fix validation errors', }, AccessDenied: { patterns: [ /User: .* is not authorized to perform: (.*)/, /Access Denied/, ], category: 'permissions', suggestion: 'Check IAM permissions for CloudFormation and related services', }, AlreadyExistsException: { patterns: [ /Stack with id .* already exists/, /already exists/, ], category: 'conflict', suggestion: 'Use a different stack name or delete the existing stack first', }, LimitExceededException: { patterns: [ /Request rate exceeded/, /Too Many Requests/, ], category: 'throttling', suggestion: 'Wait a moment and try again. AWS is rate limiting requests', }, InsufficientCapabilitiesException: { patterns: [ /Requires capabilities: \[(.*)\]/, /CAPABILITY_IAM/, /CAPABILITY_NAMED_IAM/, ], category: 'capabilities', suggestion: 'Add IAM capabilities to your CloudFormation deployment', }, StackNotFoundException: { patterns: [ /Stack with id .* does not exist/, /does not exist/, ], category: 'missing_resource', suggestion: 'The stack may not exist yet. Try deploying first', }, }; /** * S3 Error Patterns */ const S3_ERROR_PATTERNS = { NoSuchBucket: { patterns: [ /The specified bucket does not exist/, /NoSuchBucket/, ], category: 'missing_resource', suggestion: 'Create the S3 bucket or check the bucket name configuration', }, BucketAlreadyExists: { patterns: [ /The requested bucket name is not available/, /BucketAlreadyExists/, ], category: 'conflict', suggestion: 'Use a unique bucket name (S3 bucket names are globally unique)', }, AccessDenied: { patterns: [ /Access Denied.*S3/, /You do not have permission to perform this action/, ], category: 'permissions', suggestion: 'Check S3 bucket permissions and IAM policies', }, }; /** * Lambda Error Patterns */ const LAMBDA_ERROR_PATTERNS = { ResourceNotFoundException: { patterns: [ /Function not found/, /The resource you requested does not exist/, ], category: 'missing_resource', suggestion: 'Deploy the Lambda function first or check the function name', }, InvalidParameterValueException: { patterns: [ /The runtime parameter of .* is no longer supported/, /Invalid runtime/, ], category: 'configuration', suggestion: 'Update to a supported Lambda runtime version', }, }; /** * General AWS Error Patterns */ const GENERAL_ERROR_PATTERNS = { CredentialsError: { patterns: [ /Unable to locate credentials/, /The security token included in the request is invalid/, /Credentials/, ], category: 'authentication', suggestion: 'Configure AWS credentials using `aws configure` or `cfn-forge config --aws`', }, RegionError: { patterns: [ /Invalid region/, /Region .* not found/, ], category: 'configuration', suggestion: 'Set a valid AWS region in your configuration or environment variables', }, NetworkError: { patterns: [ /Connection timeout/, /Network error/, /ENOTFOUND/, /ECONNREFUSED/, ], category: 'network', suggestion: 'Check your internet connection and AWS endpoint accessibility', }, }; /** * Parse AWS CLI error output */ function parseAWSError(error, operation = null) { const errorText = error.message || error.toString(); const stderr = error.stderr?.toString() || ''; const fullText = `${errorText} ${stderr}`; // Check all error pattern categories const allPatterns = { ...CF_ERROR_PATTERNS, ...S3_ERROR_PATTERNS, ...LAMBDA_ERROR_PATTERNS, ...GENERAL_ERROR_PATTERNS, }; for (const [errorType, config] of Object.entries(allPatterns)) { for (const pattern of config.patterns) { if (pattern.test(fullText)) { return { type: errorType, category: config.category, suggestion: config.suggestion, originalMessage: errorText, operation, matches: fullText.match(pattern), }; } } } // Return generic error info if no pattern matches return { type: 'UnknownError', category: 'unknown', suggestion: 'Enable debug mode with DEBUG=1 for more details', originalMessage: errorText, operation, }; } /** * Get recovery actions for error category */ function getRecoveryActions(category, operation = null) { const actions = { template: [ 'Run `cfn-forge validate` to check template syntax', 'Verify CloudFormation template format (YAML/JSON)', 'Check for missing required parameters', 'Validate resource property names and values', ], permissions: [ 'Check IAM user/role permissions', 'Verify AWS profile has necessary access', 'Try using admin credentials temporarily', 'Check resource-based policies (S3, Lambda, etc.)', ], authentication: [ 'Run `aws sts get-caller-identity` to test credentials', 'Configure credentials: `aws configure`', 'Set environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY', 'Use `cfn-forge config --aws` for interactive setup', ], configuration: [ 'Check .cfn-forge.yaml configuration file', 'Verify AWS region settings', 'Update resource configurations', 'Check parameter values and formats', ], conflict: [ 'Use unique resource names', 'Delete conflicting resources if safe', 'Check existing deployments: `aws cloudformation list-stacks`', 'Consider using different environments (dev/staging/prod)', ], throttling: [ 'Wait 30-60 seconds and retry', 'Reduce deployment frequency', 'Check AWS service health dashboard', 'Consider deploying fewer resources at once', ], missing_resource: [ 'Deploy dependencies first', 'Check resource names and identifiers', 'Verify the resource exists in the correct region', 'Check if resources were manually deleted', ], network: [ 'Check internet connectivity', 'Verify VPN/proxy settings', 'Try different AWS region', 'Check corporate firewall settings', ], capabilities: [ 'Add CAPABILITY_IAM to deployment parameters', 'Add CAPABILITY_NAMED_IAM for custom IAM names', 'Review IAM resources in template', 'Update deployment scripts with --capabilities flag', ], }; return actions[category] || [ 'Enable debug mode with DEBUG=1', 'Check AWS service status', 'Review AWS documentation', 'Contact AWS support if issue persists', ]; } /** * Format comprehensive error report */ function formatErrorReport(parsedError) { const { type, category, suggestion, originalMessage, operation, matches, } = parsedError; let report = `\nšŸ” ${logger.constructor.chalk?.red?.bold('AWS Error Analysis') || 'AWS Error Analysis'}\n`; if (operation) { report += `šŸ“ Operation: ${operation}\n`; } report += `šŸ·ļø Error Type: ${type}\n`; report += `šŸ“‚ Category: ${category}\n`; report += `šŸ’¬ Message: ${originalMessage}\n`; if (suggestion) { report += `\nšŸ’” Primary Suggestion:\n ${suggestion}\n`; } const actions = getRecoveryActions(category, operation); if (actions.length > 0) { report += '\nšŸ› ļø Recovery Actions:\n'; actions.forEach((action, index) => { report += ` ${index + 1}. ${action}\n`; }); } // Add specific details if patterns captured groups if (matches && matches.length > 1) { report += `\nšŸ“‹ Details: ${matches[1]}\n`; } return report; } /** * Enhanced error handling for AWS operations */ function handleAWSError(error, operation = null) { const parsed = parseAWSError(error, operation); const report = formatErrorReport(parsed); // Log the formatted report console.error(report); // Add to error history if logger is available if (logger && logger.errorWithContext) { const enhancedError = new Error(parsed.originalMessage); enhancedError.code = parsed.type; enhancedError.category = parsed.category; enhancedError.suggestion = parsed.suggestion; enhancedError.originalError = error; logger.errorHistory.push({ timestamp: new Date().toISOString(), operation, type: parsed.type, category: parsed.category, suggestion: parsed.suggestion, message: parsed.originalMessage, }); } return parsed; } /** * Middleware for wrapping AWS operations with error handling */ function withAWSErrorHandling(operation, asyncFn) { return async (...args) => { try { return await asyncFn(...args); } catch (error) { handleAWSError(error, operation); throw error; } }; } module.exports = { parseAWSError, getRecoveryActions, formatErrorReport, handleAWSError, withAWSErrorHandling, CF_ERROR_PATTERNS, S3_ERROR_PATTERNS, LAMBDA_ERROR_PATTERNS, GENERAL_ERROR_PATTERNS, };