cfn-forge
Version:
CloudFormation deployment automation tool with git-based workflows
371 lines (326 loc) ⢠9.59 kB
JavaScript
/**
* 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,
};