From d08ec6c2403937706ab34b277396cb1649b0c090 Mon Sep 17 00:00:00 2001 From: Andre Rabold Date: Fri, 4 Jun 2021 08:12:24 -0700 Subject: [PATCH] Prepare 2.0.0 - Removed optimistic variable resolution for `Fn::GetAtt` as it was not working properly and caused more issues than it solved. If you rely on `Fn::GetAtt` in your environment variables, define a custom resolution using the `getAttMap` [configuration option](#Configuration-Options). --- README.md | 32 ++++++---- src/lib/collectFunctionEnvVariables.js | 4 -- src/lib/resolveCloudFormationEnvVariables.js | 67 +++++++++++--------- 3 files changed, 55 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 62fa275..6140d03 100644 --- a/README.md +++ b/README.md @@ -107,14 +107,14 @@ custom: ### Configuration Options -| Option | Default | Description | -| -------------- | ------- | ---------------------------------------------------------------------------------------------------- | -| filename | `.env` | Target file name where to write the environment variables to, relative to the project root. | -| enableOffline | `true` | Evaluate the environment variables when running `sls invoke local` or `sls offline start`. | -| overwrite | `false` | Overwrite the file even if it exists already. | -| refMap | `{}` | A mapping of [resource resolutions](#Custom-Resource-Resoluition) for the `Ref` function | -| getAttMap | `{}` | A mapping of [resource resolutions](#Custom-Resource-Resoluition) for the `Fn::GetAtt` function | -| importValueMap | `{}` | A mapping of [resource resolutions](#Custom-Resource-Resoluition) for the `Fn::ImportValue` function | +| Option | Default | Description | +| -------------- | ------- | ------------------------------------------------------------------------------------------------- | +| filename | `.env` | Target file name where to write the environment variables to, relative to the project root. | +| enableOffline | `true` | Evaluate the environment variables when running `sls invoke local` or `sls offline start`. | +| overwrite | `false` | Overwrite the file even if it exists already. | +| refMap | `{}` | Mapping of [resource resolutions](#Custom-Resource-Resolution) for the `Ref` function | +| getAttMap | `{}` | Mapping of [resource resolutions](#Custom-Resource-Resolution) for the `Fn::GetAtt` function | +| importValueMap | `{}` | Mapping of [resource resolutions](#Custom-Resource-Resolution) for the `Fn::ImportValue` function | ### Custom Resource Resolution @@ -128,17 +128,19 @@ The plugin will try its best to resolve resource references like `Ref`, `Fn::Get custom: export-env: refMap: - # Resolve `!Ref MyDbTable` as `mock-myTable` - MyDbTable: "mock-myTable" + # Resolve `!Ref MyDynamoDbTable` as `mock-myTable` + MyDynamoDbTable: "mock-myTable" getAttMap: - # Resolve `!GetAtt ElasticSearchInstance.DomainEndpoint` as `localhost:9200` - ElasticSearchInstance: + # Resolve `!GetAtt MyElasticSearchInstance.DomainEndpoint` as `localhost:9200` + MyElasticSearchInstance: DomainEndpoint: "localhost:9200" importValueMap: - # Resolve `!ImportValue OtherLambdaFunction` as `arn:aws:lambda:us-east-2::function:other-lambda-function` - OtherLambdaFunction: "arn:aws:lambda:us-east-2::function:other-lambda-function" + # Resolve `!ImportValue MyLambdaFunction` as `arn:aws:lambda:us-east-2::function:my-lambda-function` + MyLambdaFunction: "arn:aws:lambda:us-east-2::function:my-lambda-function" ``` +> 👉 Generally, it is recommended to avoid the use of intrinsic functions in your environment variables. Often, the same can be achieved by simply predefining a resource name and then manually construct the desired variable values. To share resources between different Serverless services, check out the `${cf:stackName.outputKey}` [variable resolution](https://www.serverless.com/framework/docs/providers/aws/guide/variables/) mechanism. + ## Command-Line Options Running `sls export-env` will, by default, only export _global_ environment variables into your `.env` file (those defined under `provider.environment` in your `serverless.yml`). If you want to generate the `.env` file for a specific function, pass the function name as a command-line argument as follows: @@ -173,6 +175,8 @@ sls export-env --function hello --filename .env-hello ### 2.0.0 +- Removed optimistic variable resolution for `Fn::GetAtt` as it was not working properly and caused more issues than it solved. If you rely on `Fn::GetAtt` in your environment variables, define a custom resolution using the `getAttMap` [configuration option](#Configuration-Options). + ### alpha.1 - Added `--all` command line parameter to merge the environment variables of all functions into a single `.env` file. Please note that the behavior is _undefined_ if functions use conflicting values for the same environment variable name. diff --git a/src/lib/collectFunctionEnvVariables.js b/src/lib/collectFunctionEnvVariables.js index 21b984a..856a4d5 100644 --- a/src/lib/collectFunctionEnvVariables.js +++ b/src/lib/collectFunctionEnvVariables.js @@ -10,10 +10,6 @@ const _ = require("lodash"); */ function collectFunctionEnvVariables(serverless) { const functions = _.get(serverless, "service.functions", {}); - // const envVars = _.mapValues( - // _.mapKeys(functions, (func) => func.name), - // (func) => func.environment - // ); const envVars = _.mapValues(functions, (func) => func.environment); return envVars; } diff --git a/src/lib/resolveCloudFormationEnvVariables.js b/src/lib/resolveCloudFormationEnvVariables.js index d6392ef..e72378e 100644 --- a/src/lib/resolveCloudFormationEnvVariables.js +++ b/src/lib/resolveCloudFormationEnvVariables.js @@ -5,36 +5,43 @@ const BbPromise = require("bluebird"), NodeEvaluator = require("cfn-resolver-lib"); function resolveGetAtt(refs, resource) { - const Partition = refs["AWS::Partition"]; - const Region = refs["AWS::Region"]; - const AccountId = refs["AWS::AccountId"]; - switch (resource.ResourceType) { - case "AWS::Lambda::Function": - return { - Arn: `arn:${Partition}:lambda:${Region}:${AccountId}:function:${resource.PhysicalResourceId}`, - FunctionName: resource.PhysicalResourceId, - }; - case "AWS::SNS::Topic": - return { TopicName: _.last(_.split(resource.PhysicalResourceId, ":")) }; - case "AWS::SQS::Queue": - return { QueueName: _.last(_.split(resource.PhysicalResourceId, ":")) }; - case "AWS::CloudWatch::Alarm": - return { AlarmName: _.last(_.split(resource.PhysicalResourceId, ":")) }; - case "AWS::EC2::Subnet": - return { SubnetId: _.last(_.split(resource.PhysicalResourceId, ":")) }; - case "AWS::EC2::VPC": - return { VpcId: _.last(_.split(resource.PhysicalResourceId, ":")) }; - case "AWS::S3::Bucket": - return { BucketName: _.last(_.split(resource.PhysicalResourceId, ":")) }; - case "AWS::EC2::SecurityGroup": - return { SecurityGroupId: _.last(_.split(resource.PhysicalResourceId, ":")) }; - case "AWS::DynamoDB::Table": - return { TableName: _.last(_.split(resource.PhysicalResourceId, ":")) }; - case "AWS::IAM::Role": - return { Arn: `arn:${Partition}:iam::${AccountId}:role/${resource.PhysicalResourceId}` }; - case "AWS::ApiGateway::RestApi": - return { RootResourceId: resource.PhysicalResourceId }; - } + // TODO: While this code was created in good intention (isn't it all?), it doesn't work in the current form. + // There's no AWS API that can help resolve !GetAtt automatically and some attributes are impossible to + // determine without retrieving additional details of the resource, e.g. using an additional API call. + // So, for now, we completely disable this variable resolution mechanism and rely of hardcoding the `getAttMap` + // in the config instead. + // Please note that the code below doesn't work properly. + + // const Partition = refs["AWS::Partition"]; + // const Region = refs["AWS::Region"]; + // const AccountId = refs["AWS::AccountId"]; + // switch (resource.ResourceType) { + // case "AWS::Lambda::Function": + // return { + // Arn: `arn:${Partition}:lambda:${Region}:${AccountId}:function:${resource.PhysicalResourceId}`, + // FunctionName: resource.PhysicalResourceId, + // }; + // case "AWS::SNS::Topic": + // return { TopicName: _.last(_.split(resource.PhysicalResourceId, ":")) }; + // case "AWS::SQS::Queue": + // return { QueueName: _.last(_.split(resource.PhysicalResourceId, ":")) }; + // case "AWS::CloudWatch::Alarm": + // return { AlarmName: _.last(_.split(resource.PhysicalResourceId, ":")) }; + // case "AWS::EC2::Subnet": + // return { SubnetId: _.last(_.split(resource.PhysicalResourceId, ":")) }; + // case "AWS::EC2::VPC": + // return { VpcId: _.last(_.split(resource.PhysicalResourceId, ":")) }; + // case "AWS::S3::Bucket": + // return { BucketName: _.last(_.split(resource.PhysicalResourceId, ":")) }; + // case "AWS::EC2::SecurityGroup": + // return { SecurityGroupId: _.last(_.split(resource.PhysicalResourceId, ":")) }; + // case "AWS::DynamoDB::Table": + // return { TableName: _.last(_.split(resource.PhysicalResourceId, ":")) }; + // case "AWS::IAM::Role": + // return { Arn: `arn:${Partition}:iam::${AccountId}:role/${resource.PhysicalResourceId}` }; + // case "AWS::ApiGateway::RestApi": + // return { RootResourceId: resource.PhysicalResourceId }; + // } return resource; }