UNPKG

serverless-plugin-arn-prefixer

Version:

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

369 lines (287 loc) 12.7 kB
# Serverless ARN Prefixer Plugin A Serverless Framework plugin that automatically injects custom ARN properties into your serverless configuration, making it easier to build AWS resource ARNs consistently across your application. Supports both `${arn:*}` variable resolvers and `${arn:*}` object references for maximum compatibility. ## Installation ### From npm (Recommended) ```bash npm install @hyperdrive.bot/serverless-arn-prefixer ``` ### From GitLab Package Registry (Development) ```bash # Configure npm to use GitLab registry for @hyperdrive.bot scope npm config set @hyperdrive.bot:registry https://gitlab.com/api/v4/projects/PROJECT_ID/packages/npm/ # Install the package npm install @hyperdrive.bot/serverless-arn-prefixer ``` ### Local Development If developing within the monorepo, no separate installation required. ## Usage Add the plugin to your `serverless.yml`: ### When installed from npm ```yaml plugins: - "@hyperdrive.bot/serverless-arn-prefixer" ``` ### When using locally in monorepo ```yaml plugins: - ./packages/serverless/arn-prefixer ``` Or if using from within the packages/serverless directory: ```yaml plugins: - ./arn-prefixer ``` ## Configuration ### Runtime Code Generation (Default: Enabled) Configure runtime file generation in your `serverless.yml`: ```yaml custom: arnPrefixer: generateRuntime: true # Default: true - Enable/disable runtime generation runtimePath: 'runtime.js' # Default: 'runtime.js' - Output filename ``` **Disable Runtime Generation:** ```yaml custom: arnPrefixer: generateRuntime: false ``` **Custom Runtime Path:** ```yaml custom: arnPrefixer: runtimePath: 'my-arn-values.js' # Will also generate my-arn-values.d.ts ``` ## What it does The plugin automatically injects a `custom.arn` object into your serverless configuration with the following properties: **NEW**: Runtime Code Generation (Default: Enabled) The plugin now generates runtime JavaScript and TypeScript files that allow you to import ARN values directly in your Lambda code without environment variables: ```javascript // Import specific values import { arnPrefix, globalPrefix, dynamodb } from 'arn-prefixer/runtime' // Use in your Lambda code const tableName = `${arnPrefix}-users` const tableArn = `${dynamodb}:table/${tableName}` ``` This provides **zero runtime overhead** - values are hardcoded strings at build time! ### Basic Properties ```yaml custom: arn: accountId: "123456789012" # AWS Account ID (from AWS_ACCOUNT_ID env var or STS) stage: "dev" # Serverless stage service: "my-service" # Serverless service name region: "us-east-1" # AWS region prefix: "my-service-dev" # service-stage regionalPrefix: "us-east-1-my-service-dev" # region-service-stage globalPrefix: "123456789012-us-east-1-my-service-dev" # accountId-region-service-stage ``` ### ARN Builder Properties ```yaml custom: arn: # Generic ARN components arnBase: "arn:aws" # AWS ARN base accountRegion: "123456789012:us-east-1" # accountId:region # Service-specific ARN bases dynamodb: "arn:aws:dynamodb:123456789012:us-east-1" # DynamoDB ARN base s3: "arn:aws:s3:123456789012:us-east-1" # S3 ARN base lambda: "arn:aws:lambda:123456789012:us-east-1" # Lambda ARN base iam: "arn:aws:iam:123456789012:us-east-1" # IAM ARN base sns: "arn:aws:sns:123456789012:us-east-1" # SNS ARN base sqs: "arn:aws:sqs:123456789012:us-east-1" # SQS ARN base apigateway: "arn:aws:apigateway:123456789012:us-east-1" # API Gateway ARN base events: "arn:aws:events:123456789012:us-east-1" # EventBridge ARN base logs: "arn:aws:logs:123456789012:us-east-1" # CloudWatch Logs ARN base kinesis: "arn:aws:kinesis:123456789012:us-east-1" # Kinesis ARN base firehose: "arn:aws:firehose:123456789012:us-east-1" # Firehose ARN base stepfunctions: "arn:aws:states:123456789012:us-east-1" # Step Functions ARN base ``` ## Using the injected values Once the plugin is loaded, you can reference these values anywhere in your serverless.yml using two methods: ### Method 1: Variable Resolver Use the `${arn:property}` syntax: ```yaml resources: Resources: MyDynamoDBTable: Type: AWS::DynamoDB::Table Properties: TableName: ${arn:prefix}-users # Results in: my-service-dev-users MyS3Bucket: Type: AWS::S3::Bucket Properties: BucketName: ${arn:globalPrefix}-assets # Results in: 123456789012-us-east-1-my-service-dev-assets functions: myFunction: name: ${arn:regionalPrefix}-handler # Results in: us-east-1-my-service-dev-handler ``` ### Method 2: Custom Object (Manual Definition Required) Use the `${arn:property}` syntax (requires manual property definition): ```yaml resources: Resources: MyDynamoDBTable: Type: AWS::DynamoDB::Table Properties: TableName: ${arn:prefix}-users # Results in: my-service-dev-users functions: myFunction: name: ${arn:regionalPrefix}-handler # Results in: us-east-1-my-service-dev-handler ``` **⚠️ Important Compatibility Notes:** - **Method 1** (`${arn:*}`): **Works with this plugin** - uses custom variable resolvers - **Method 2** (`${arn:*}`): **Cannot work with plugin-injected properties** in Serverless v2 due to variable resolution timing ### Why Method 2 Doesn't Work With Plugin Serverless v2 resolves ALL variables BEFORE loading plugins. This means: 1. `${arn:*}` tries to resolve during YAML parsing 2. Plugin hasn't loaded yet, so no `custom.arn` properties exist 3. Resolution fails and stops the process ### When Method 2 DOES Work `${arn:*}` works when properties are **statically defined** in `serverless.yml`: ```yaml custom: arn: accountId: ${env:AWS_ACCOUNT_ID, '123456789012'} prefix: ${self:service}-${self:provider.stage} # ... other properties defined manually ``` ### Recommendations - **With this plugin**: Use Method 1 (`${arn:*}`) syntax - **With serverless-plugin-composer**: Define `custom.arn` manually and use Method 2 (`${arn:*}`) - **Hybrid approach**: Define basic properties manually, let plugin handle complex ARN building ### Method 3: Runtime Imports (NEW! ⚡ Zero Runtime Overhead) Import ARN values directly in your Lambda code: ```javascript // ES6 imports import { arnPrefix, globalPrefix, dynamodb, s3 } from 'arn-prefixer/runtime' // CommonJS const { arnPrefix, globalPrefix, dynamodb, s3 } = require('arn-prefixer/runtime') // Use in your Lambda handlers export const handler = async (event) => { const tableName = `${arnPrefix}-users` const bucketName = `${globalPrefix}-assets` // Build complete ARNs const tableArn = `${dynamodb}:table/${tableName}` const bucketArn = `${s3}:bucket/${bucketName}` // Your Lambda logic here } ``` **Available Exports:** - `arnPrefix` - service-stage (e.g., "my-service-dev") - `globalPrefix` - accountId-region-service-stage (e.g., "123456789012-us-east-1-my-service-dev") - `regionalPrefix` - region-service-stage (e.g., "us-east-1-my-service-dev") - All AWS service ARN builders: `dynamodb`, `s3`, `lambda`, `sns`, `sqs`, etc. ### Method 4: ARN Builders (Serverless Config) Build complete AWS ARNs easily: ```yaml resources: Resources: # DynamoDB Table ARN MyDynamoTable: Type: AWS::DynamoDB::Table Properties: TableName: ${arn:prefix}-users Outputs: TableArn: Value: ${arn:dynamodb}:table/${arn:prefix}-users # Results in: arn:aws:dynamodb:123456789012:us-east-1:table/my-service-dev-users # S3 Bucket ARN MyBucket: Type: AWS::S3::Bucket Properties: BucketName: ${arn:globalPrefix}-assets Outputs: BucketArn: Value: ${arn:s3}:bucket/${arn:globalPrefix}-assets # Results in: arn:aws:s3:123456789012:us-east-1:bucket/123456789012-us-east-1-my-service-dev-assets # Lambda Function ARN MyFunction: Outputs: FunctionArn: Value: ${arn:lambda}:function:${arn:prefix}-processor # Results in: arn:aws:lambda:123456789012:us-east-1:function:my-service-dev-processor # IAM Policy using ARN builders user-role-statements: - Effect: Allow Action: - dynamodb:GetItem - dynamodb:PutItem Resource: - ${arn:dynamodb}:table/${arn:prefix}-* # All tables matching pattern - ${arn:dynamodb}:table/*/index/* # All indexes ``` ### Common ARN Patterns ```yaml # Your specific use cases: DynamoDBTableArn: ${arn:dynamodb}:table/resource # arn:aws:dynamodb:account:region:table/resource DynamoDBAllTablesArn: ${arn:dynamodb}:table/* # arn:aws:dynamodb:account:region:table/* S3BucketArn: ${arn:s3}:bucket/${arn:prefix}-bucket # arn:aws:s3:account:region:bucket/service-stage-bucket LambdaFunctionArn: ${arn:lambda}:function:${arn:prefix}-func # arn:aws:lambda:account:region:function:service-stage-func SNSTopicArn: ${arn:sns}:${arn:prefix}-topic # arn:aws:sns:account:region:service-stage-topic SQSQueueArn: ${arn:sqs}:${arn:prefix}-queue # arn:aws:sqs:account:region:service-stage-queue ``` ## Comparison with Serverless Framework Defaults ### Serverless Framework Default Patterns | Resource Type | Default Pattern | Example | |---------------|-----------------|---------| | **CloudFormation Stack** | `{service}-{stage}` | `my-service-dev` | | **Lambda Functions** | `{service}-{stage}-{functionName}` | `my-service-dev-hello` | | **API Gateway** | `{stage}-{service}` | `dev-my-service` | | **IAM Roles** | `{service}-{stage}-{region}-lambdaRole` | `my-service-dev-us-east-1-lambdaRole` | | **Custom Resources** | `{LogicalId}-{RandomSuffix}` | `MyTable-ABC123DEF456` | ### ARN-Prefixer Plugin Patterns | Plugin Variable | Pattern | Example | |----------------|---------|---------| | `${arn:prefix}` | `{service}-{stage}` | `my-service-dev` | | `${arn:regionalPrefix}` | `{region}-{service}-{stage}` | `us-east-1-my-service-dev` | | `${arn:globalPrefix}` | `{accountId}-{region}-{service}-{stage}` | `123456789012-us-east-1-my-service-dev` | ### Key Advantages - **Consistency**: All resources follow the same naming convention - **Predictability**: No random suffixes, deterministic resource names - **Uniqueness**: Account ID inclusion ensures cross-account uniqueness - **Multi-region**: Region awareness for global deployments - **Enterprise Ready**: Suitable for complex, multi-tenant architectures ## Account ID Resolution The plugin will attempt to get the AWS Account ID in the following order: 1. **Environment Variable**: If `AWS_ACCOUNT_ID` is set, it will use that value 2. **AWS STS**: If the environment variable is not set, it will call AWS STS `getCallerIdentity()` to retrieve the account ID 3. **Error**: If both methods fail, the plugin will throw an error and stop deployment **Important**: You must either set the `AWS_ACCOUNT_ID` environment variable or have valid AWS credentials configured. The plugin will not proceed with deployment if it cannot determine the AWS Account ID. ## Compatibility ### Tested Versions | Serverless Framework | Status | Features Tested | |---------------------|--------|----------------| | 2.72.4 | Fully Tested | Prefix properties + ARN builders + Error handling | | 3.38.0 | Fully Tested | Prefix properties + ARN builders + Error handling | | 4.18.2 | Fully Tested | Prefix properties + ARN builders + Error handling | **Requirements:** - **Serverless Framework**: 2.0.0 and above (supports v2, v3, and v4) - **Node.js**: 14.0.0 and above ## Plugin Execution The plugin runs during the `after:aws:common:validate:validate` hook, ensuring that all properties are available before your resources are processed. ## Example Output ### Successful execution: ``` ARN Prefixer: Injected custom.arn properties - accountId: 123456789012 - stage: dev - service: my-service - region: us-east-1 - prefix: my-service-dev - regionalPrefix: us-east-1-my-service-dev - globalPrefix: 123456789012-us-east-1-my-service-dev ``` ### Error when Account ID cannot be determined: ``` 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: The security token included in the request is expired ```