UNPKG

@hyperdrive.bot/serverless-arn-prefixer

Version:

A serverless plugin that injects custom ARN properties for building AWS resource ARNs

162 lines (137 loc) 6.72 kB
'use strict'; const AWS = require('aws-sdk'); class ArnPrefixerPlugin { constructor(serverless, options) { this.serverless = serverless; this.options = options; // Initialize arn properties early this.initializeArnProperties(); // Set up variable resolvers this.configurationVariablesSources = { arn: { resolve: async ({ address, params, resolveConfigurationProperty, options }) => { return this.resolveArnVariable(address); } } }; this.hooks = { 'before:package:initialize': this.logInjectedProperties.bind(this) }; } initializeArnProperties() { // Get basic properties from serverless configuration const service = this.serverless.service.service; const provider = this.serverless.service.provider || {}; const stage = provider.stage || 'dev'; const region = provider.region || 'us-east-1'; // Get account ID synchronously first from env var let accountId = process.env.AWS_ACCOUNT_ID; // If no account ID from env, we'll mark it for async resolution if (!accountId) { accountId = 'ASYNC_RESOLVE'; } // Build prefix values const prefix = `${service}-${stage}`; const regionalPrefix = `${region}-${service}-${stage}`; const globalPrefix = accountId === 'ASYNC_RESOLVE' ? 'ASYNC_RESOLVE' : `${accountId}-${region}-${service}-${stage}`; // Build ARN base values for AWS services const arnBase = 'arn:aws'; const accountRegionBase = accountId === 'ASYNC_RESOLVE' ? 'ASYNC_RESOLVE' : `${accountId}:${region}`; // Store the properties for resolution this.arnProperties = { accountId, stage, service, region, prefix, regionalPrefix, globalPrefix, // ARN builders arnBase, accountRegion: accountRegionBase, // Service-specific ARN bases dynamodb: accountId === 'ASYNC_RESOLVE' ? 'ASYNC_RESOLVE' : `${arnBase}:dynamodb:${accountRegionBase}`, s3: accountId === 'ASYNC_RESOLVE' ? 'ASYNC_RESOLVE' : `${arnBase}:s3:${accountRegionBase}`, lambda: accountId === 'ASYNC_RESOLVE' ? 'ASYNC_RESOLVE' : `${arnBase}:lambda:${accountRegionBase}`, iam: accountId === 'ASYNC_RESOLVE' ? 'ASYNC_RESOLVE' : `${arnBase}:iam:${accountRegionBase}`, sns: accountId === 'ASYNC_RESOLVE' ? 'ASYNC_RESOLVE' : `${arnBase}:sns:${accountRegionBase}`, sqs: accountId === 'ASYNC_RESOLVE' ? 'ASYNC_RESOLVE' : `${arnBase}:sqs:${accountRegionBase}`, apigateway: accountId === 'ASYNC_RESOLVE' ? 'ASYNC_RESOLVE' : `${arnBase}:apigateway:${accountRegionBase}`, events: accountId === 'ASYNC_RESOLVE' ? 'ASYNC_RESOLVE' : `${arnBase}:events:${accountRegionBase}`, logs: accountId === 'ASYNC_RESOLVE' ? 'ASYNC_RESOLVE' : `${arnBase}:logs:${accountRegionBase}`, kinesis: accountId === 'ASYNC_RESOLVE' ? 'ASYNC_RESOLVE' : `${arnBase}:kinesis:${accountRegionBase}`, firehose: accountId === 'ASYNC_RESOLVE' ? 'ASYNC_RESOLVE' : `${arnBase}:firehose:${accountRegionBase}`, stepfunctions: accountId === 'ASYNC_RESOLVE' ? 'ASYNC_RESOLVE' : `${arnBase}:states:${accountRegionBase}` }; // Also inject directly into custom for backward compatibility if (!this.serverless.service.custom) { this.serverless.service.custom = {}; } this.serverless.service.custom.arn = this.arnProperties; } async resolveArnVariable(address) { // First check if the property exists if (!this.arnProperties.hasOwnProperty(address)) { throw new Error(`Unknown arn property: ${address}`); } // If accountId needs async resolution, do it now if (this.arnProperties.accountId === 'ASYNC_RESOLVE') { try { const provider = this.serverless.service.provider || {}; const stage = provider.stage || 'dev'; const region = provider.region || 'us-east-1'; const sts = new AWS.STS({ region: region, credentials: provider.credentials }); const identity = await sts.getCallerIdentity().promise(); const accountId = identity.Account; // Update all properties that depend on accountId this.arnProperties.accountId = accountId; this.arnProperties.globalPrefix = `${accountId}-${region}-${this.arnProperties.service}-${stage}`; // Update ARN base properties const accountRegionBase = `${accountId}:${region}`; this.arnProperties.accountRegion = accountRegionBase; this.arnProperties.dynamodb = `arn:aws:dynamodb:${accountRegionBase}`; this.arnProperties.s3 = `arn:aws:s3:${accountRegionBase}`; this.arnProperties.lambda = `arn:aws:lambda:${accountRegionBase}`; this.arnProperties.iam = `arn:aws:iam:${accountRegionBase}`; this.arnProperties.sns = `arn:aws:sns:${accountRegionBase}`; this.arnProperties.sqs = `arn:aws:sqs:${accountRegionBase}`; this.arnProperties.apigateway = `arn:aws:apigateway:${accountRegionBase}`; this.arnProperties.events = `arn:aws:events:${accountRegionBase}`; this.arnProperties.logs = `arn:aws:logs:${accountRegionBase}`; this.arnProperties.kinesis = `arn:aws:kinesis:${accountRegionBase}`; this.arnProperties.firehose = `arn:aws:firehose:${accountRegionBase}`; this.arnProperties.stepfunctions = `arn:aws:states:${accountRegionBase}`; // Update the custom object too this.serverless.service.custom.arn = this.arnProperties; } catch (error) { const errorMessage = `ARN Prefixer Plugin Error: Cannot determine AWS Account ID. Please either: 1. Set the AWS_ACCOUNT_ID environment variable, or 2. Ensure you have valid AWS credentials configured AWS STS Error: ${error.message}`; this.serverless.cli.log(errorMessage); throw new Error(errorMessage); } } // Return the requested property return { value: this.arnProperties[address] }; } logInjectedProperties() { this.serverless.cli.log('ARN Prefixer: Injected custom.arn properties'); this.serverless.cli.log(` - accountId: ${this.arnProperties.accountId}`); this.serverless.cli.log(` - stage: ${this.arnProperties.stage}`); this.serverless.cli.log(` - service: ${this.arnProperties.service}`); this.serverless.cli.log(` - region: ${this.arnProperties.region}`); this.serverless.cli.log(` - prefix: ${this.arnProperties.prefix}`); this.serverless.cli.log(` - regionalPrefix: ${this.arnProperties.regionalPrefix}`); this.serverless.cli.log(` - globalPrefix: ${this.arnProperties.globalPrefix}`); } } module.exports = ArnPrefixerPlugin;