├── .eslintrc.yml
├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
└── src
├── index.js
└── lib
├── aws-helper.js
├── collectFunctionEnvVariables.js
├── resolveCloudFormationEnvVariables.js
└── transformEnvVarsToString.js
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | ---
2 | root: true
3 | env:
4 | es6: true
5 | node: true
6 | mocha: true
7 | plugins:
8 | - prettier
9 | extends:
10 | - eslint:recommended
11 | rules:
12 | prettier/prettier: error
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .history
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Jimdo GmbH
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ⚡️ Serverless Export Env Plugin
2 |
3 | [](https://www.serverless.com)
4 | [](https://nodejs.org/)
5 | [](https://www.npmjs.com/package/serverless-export-env)
6 | [](https://github.com/arabold/serverless-export-env/blob/master/LICENSE)
7 | [](https://www.npmjs.com/package/serverless-export-env)
8 | [](https://prettier.io/)
9 |
10 | ## About
11 |
12 | The [Serverless Framework](https://www.serverless.com/) offers a very powerful feature: You can reference AWS resources anywhere from within your `serverless.yml` and it will automatically resolve them to their respective values during deployment. However, this only works properly once your code is deployed to AWS. The _Serverless Export Env Plugin_ extends the Serverless Framework's built-in variable solution capabilities by adding support many additional CloudFormation intrinsic functions (`Fn::GetAtt`, `Fn::Join`, `Fn::Sub`, etc.) as well as variable references (`AWS::Region`, `AWS::StackId`, etc.).
13 |
14 | The _Serverless Export Env Plugin_ helps solve two main use cases:
15 |
16 | 1. It will automatically hook into the `sls invoke local` and `sls offline start` (see [Serverless Offline Plugin](https://github.com/dherault/serverless-offline)) and help resolve your environment variables. This is fully transparent to your application and other plugins.
17 | 2. Invoke `sls export-env` from the command line to generate a `.env` file on your local filesystem. Then use a library such as [dotenv](https://www.npmjs.com/package/dotenv) to import it into your code, e.g. during local integration tests.
18 |
19 | ## Usage
20 |
21 | Add the npm package to your project:
22 |
23 | ```sh
24 | # Via yarn
25 | $ yarn add arabold/serverless-export-env --dev
26 |
27 | # Via npm
28 | $ npm install arabold/serverless-export-env --save-dev
29 | ```
30 |
31 | Add the plugin to your `serverless.yml`. It should be listed first to ensure it can resolve your environment variables before other plugins see them:
32 |
33 | ```yaml
34 | plugins:
35 | - serverless-export-env
36 | ```
37 |
38 | That's it! You can now call `sls export-env` in your terminal to generate the `.env` file. Or, you can run `sls invoke local -f FUNCTION` or `sls offline start` to run your code locally as usual.
39 |
40 | ### Examples
41 |
42 | ```sh
43 | sls export-env
44 | ```
45 |
46 | This will export all project-wide environment variables into a `.env` file in your project root folder.
47 |
48 | ```sh
49 | sls export-env --function MyFunction --filename .env-MyFunction
50 | ```
51 |
52 | This will export environment variables of the `MyFunction` Lambda function into a `.env-MyFunction` file in your project root folder.
53 |
54 | ## Referencing CloudFormation resources
55 |
56 | As mentioned before, the Serverless Framework allows you to reference AWS resources anywhere from within your `serverless.yml` and it will automatically resolve them to their respective values during deployment. However, Serverless' built-in variable resolution is limited and will not always work when run locally. The _Serverless Export Env Plugin_ extends this functionality and automatically resolves commonly used [intrinsic functions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html) and initializes your local environment properly.
57 |
58 | ### Supported instrinsic functions
59 |
60 | - Condition Functions
61 | - `Fn::And`
62 | - `Fn::Equals`
63 | - `Fn::If`
64 | - `Fn::Not`
65 | - `Fn::Or`
66 | - `Fn::FindInMap`
67 | - `Fn::GetAtt`
68 | - `Fn::GetAZs`
69 | - `Fn::Join`
70 | - `Fn::Select`
71 | - `Fn::Split`
72 | - `Fn::Sub` (at the moment only key-value map subtitution is supported)
73 | - `Fn::ImportValue`
74 | - `Ref`
75 |
76 | ### Examples
77 |
78 | ```yaml
79 | provider:
80 | environment:
81 | S3_BUCKET_URL:
82 | Fn::Join:
83 | - ""
84 | - - https://s3.amazonaws.com/
85 | - Ref: MyBucket
86 | ```
87 |
88 | Or the short version:
89 |
90 | ```yaml
91 | provider:
92 | environment:
93 | S3_BUCKET_URL: !Join ["", [https://s3.amazonaws.com/, Ref: MyBucket]]
94 | ```
95 |
96 | You can then access the environment variable in your code the usual way (e.g. `process.env.S3_BUCKET_URL`).
97 |
98 | ## Configuration
99 |
100 | The plugin supports various configuration options under `custom.export-env` in your `serverless.yml` file:
101 |
102 | ```yaml
103 | custom:
104 | export-env:
105 | filename: .env
106 | overwrite: false
107 | enableOffline: true
108 | ```
109 |
110 | ### Configuration Options
111 |
112 | | Option | Default | Description |
113 | | -------------- | ------- | ------------------------------------------------------------------------------------------------- |
114 | | filename | `.env` | Target file name where to write the environment variables to, relative to the project root. |
115 | | enableOffline | `true` | Evaluate the environment variables when running `sls invoke local` or `sls offline start`. |
116 | | overwrite | `false` | Overwrite the file even if it exists already. |
117 | | refMap | `{}` | Mapping of [resource resolutions](#Custom-Resource-Resolution) for the `Ref` function |
118 | | getAttMap | `{}` | Mapping of [resource resolutions](#Custom-Resource-Resolution) for the `Fn::GetAtt` function |
119 | | importValueMap | `{}` | Mapping of [resource resolutions](#Custom-Resource-Resolution) for the `Fn::ImportValue` function |
120 |
121 | ### Custom Resource Resolution
122 |
123 | The plugin will try its best to resolve resource references like `Ref`, `Fn::GetAtt`, and `Fn::ImportValue` for you. However, sometimes this might fail, or you might want to use mocked values instead. In those cases, you can override those values using the `refMap`, `getAttMap`, and `importValueMap` options.
124 |
125 | - `refMap` takes a mapping of _resource name_ to _value_ pairs.
126 | - `getAttMap` takes a mapping of _resource name_ to _attribute/value_ pairs.
127 | - `importValueMap` takes a mapping of _import name_ to _value_ pairs.
128 |
129 | ```yaml
130 | custom:
131 | export-env:
132 | refMap:
133 | # Resolve `!Ref MyDynamoDbTable` as `mock-myTable`
134 | MyDynamoDbTable: "mock-myTable"
135 | getAttMap:
136 | # Resolve `!GetAtt MyElasticSearchInstance.DomainEndpoint` as `localhost:9200`
137 | MyElasticSearchInstance:
138 | DomainEndpoint: "localhost:9200"
139 | importValueMap:
140 | # Resolve `!ImportValue MyLambdaFunction` as `arn:aws:lambda:us-east-2::function:my-lambda-function`
141 | MyLambdaFunction: "arn:aws:lambda:us-east-2::function:my-lambda-function"
142 | ```
143 |
144 | > 👉 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.
145 |
146 | ## Command-Line Options
147 |
148 | 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:
149 |
150 | ```sh
151 | sls export-env --function hello --filename .env-hello
152 | ```
153 |
154 | | Option | Description |
155 | | --------- | ------------------------------------------------------------------------------------------ |
156 | | filename | Target filename where to write the environment variables to, relative to the project root. |
157 | | overwrite | Overwrite the file even if it exists already. |
158 | | function | Name of a function for which to generate the .env file. |
159 | | all | Merge environment variables of all functions into a single .env file. |
160 |
161 | ## Provided lifecycle events
162 |
163 | - `export-env:collect` - Collect environment variables from Serverless
164 | - `export-env:resolve` - Resolve CloudFormation references and import variables
165 | - `export-env:apply` - Set environment variables when testing Lambda functions locally
166 | - `export-env:write` - Write environment variables to file
167 |
168 | ## Migrating from 1.x to 2.x
169 |
170 | - Running `sls invoke local` or `sls offline start` will no longer create or update your `.env` file. If you want to create an `.env` file, simply run `sls export-env` instead.
171 | - By default, the plugin will no longer overwrite any existing `.env` file. To enable overwriting existing files, either specify `--overwrite` in the command-line or set the `custom.export-env.overwrite` configuration option.
172 | - Resource `Outputs` values (`resources.Resources.Outputs.*`) are no longer getting exported automatically. This has always been a workaround and causes more problems than it solved. The plugin will try its best to resolve `Fn::GetAtt` and other references for you now, so there should be little need for the old behavior anymore. Add the desired value as an environment variable to `provider.environment` instead.
173 | - Running `sls export-env` will no longer merge the environment variables of all functions into a single `.env` file. Instead, pass the name of the desired function as `--function` argument to the command line. If no function name is specified, only project-wide environment variables will get exported. To bring back the old behavior, pass `--all` in command line and it will generate a file including all environment variables of all functions. However, please be aware that the behavior is undefined if functions use conflicting values for the same environment variable name.
174 | - The configuration options `filename` and `pathFromRoot` have been merged to `filename` now. You can specify relative paths in `filename` such as `./dist/.env` now. Make sure the target folder exists!
175 |
176 | ## Releases
177 |
178 | ### 2.2.0
179 |
180 | - Fixed error with latest `cfn-resolver-lib`. Thanks to [estahn](https://github.com/estahn) for the fix.
181 | - Updated dependencies to latest versions.
182 |
183 | ### 2.1.0
184 |
185 | - Compatibility with Serverless v3.x
186 | - Updated dependencies' minor versions
187 |
188 | ### 2.0.0
189 |
190 | - Removed optimistic variable resolution for `Fn::GetAtt` as it was not working properly and caused hard to solve issues. If you rely on `Fn::GetAtt` in your environment variables, define a custom resolution using the `getAttMap` [configuration option](#Configuration-Options).
191 |
192 | ### alpha.1
193 |
194 | - 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.
195 |
196 | ### alpha.0
197 |
198 | - Complete rewrite of the variable resolver. We use the amazing [cfn-resolver-lib](https://github.com/robessog/cfn-resolver-lib) lib now. This allows us to support not only `Ref` and `Fn::ImportValue` as in previous releases, but we're able to resolve the most commonly used intrinsic functions automatically now.
199 |
200 |
201 | 1.x Releases
202 |
203 | ### 1.4.4
204 |
205 | - Reverted changes in 1.4.1. Unfortunately, we broke the semver contract by introducing a breaking feature in a patch update. This feature needs to be rethought and added back in a 1.5.x release as optional. Until then, I had to remove it again.
206 |
207 | #### 1.4.3
208 |
209 | - Internal version (not published)
210 |
211 | ### 1.4.2
212 |
213 | - Fixed some compatibility issues with the latest Serverless framework release. Thanks to [pgrzesik](https://github.com/pgrzesik) for the necessary updates.
214 |
215 | ### 1.4.1
216 |
217 | - Disabled calls to the real aws infrastructure when running with Serverless Offline. Thanks to marooned071 for the contribution.
218 |
219 | ### 1.4.0
220 |
221 | - Collect and set resource values from actual Cloud Formation stack output. Thanks to [andersquist](https://github.com/andersquist) for his contribution!
222 | - Fix error when serverless.yml doesn't contain a custom section. Thanks to [michael-wolfenden](https://github.com/michael-wolfenden)!
223 |
224 | ### 1.3.1
225 |
226 | - Explicitly set environment variables during local invocation of the Lambda (`sls invoke local`)
227 |
228 | ### 1.3.0
229 |
230 | - Support different output filename and path. Thanks to [philiiiiiipp](https://github.com/philiiiiiipp).
231 | - Export `Outputs` as environment variables. Thanks to [lielran](https://github.com/lielran).
232 | - Updated to latest dependencies
233 |
234 | ### 1.2.0
235 |
236 | - Use operating-system-specific end-of-line when creating `.env` file
237 |
238 | ### 1.1.3
239 |
240 | - Fixed an issue with `AWS::AccountId` being resolved as `[Object Promise]` instead of the actual value.
241 |
242 | ### 1.1.2
243 |
244 | - Fixed an issue with CloudFormation resources not being resolved properly if the stack has more than 100 resources or exports.
245 |
246 | ### 1.1.1
247 |
248 | - Fix issue with multiple environment variables for function (thanks to [@Nevon](https://github.com/Nevon)).
249 |
250 | ### 1.1.0
251 |
252 | - Support `Fn::Join` operation (contribution by [@jonasho](https://github.com/jonasho))
253 | - Support pseudo parameters `AWS::Region`, `AWS::AccountId`, `AWS::StackId` and `AWS::StackName`.
254 |
255 | ### 1.0.2
256 |
257 | - The plugin now properly resolves and sets the environment variables if a Lambda function is invoked locally (`sls invoke local -f FUNCTION`). This allows seamless as if the function would be deployed on AWS.
258 |
259 | ### 1.0.1
260 |
261 | - Corrected plugin naming
262 | - Improved documentation
263 |
264 | ### 1.0.0
265 |
266 | - This is the initial release with all basic functionality
267 |
268 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "serverless-export-env",
3 | "version": "2.2.0",
4 | "description": "Serverless plugin to export environment variables into a .env file",
5 | "main": "src/index.js",
6 | "author": "Andre Rabold",
7 | "license": "MIT",
8 | "scripts": {
9 | "lint": "eslint 'src/**/*.js'"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/arabold/serverless-export-env.git"
14 | },
15 | "bugs": {
16 | "url": "https://github.com/arabold/serverless-export-env/issues"
17 | },
18 | "engines": {
19 | "node": ">=12.0.0"
20 | },
21 | "homepage": "https://github.com/arabold/serverless-export-env",
22 | "dependencies": {
23 | "bluebird": "^3.7.2",
24 | "cfn-resolver-lib": "^1.1.7",
25 | "lodash": "^4.17.20"
26 | },
27 | "devDependencies": {
28 | "eslint": "^8.17.0",
29 | "eslint-plugin-prettier": "^4.0.0",
30 | "prettier": "^2.1.2"
31 | },
32 | "peerDependencies": {
33 | "serverless": ">=2"
34 | },
35 | "prettier": {
36 | "printWidth": 120,
37 | "tabWidth": 2,
38 | "useTabs": false,
39 | "semi": true,
40 | "singleQuote": false,
41 | "quoteProps": "as-needed",
42 | "trailingComma": "es5",
43 | "bracketSpacing": true,
44 | "jsxBracketSameLine": false,
45 | "arrowParens": "always"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const _ = require("lodash"),
4 | BbPromise = require("bluebird"),
5 | fs = require("fs"),
6 | path = require("path");
7 |
8 | const collectFunctionEnvVariables = require("./lib/collectFunctionEnvVariables");
9 | const resolveCloudFormationEnvVariables = require("./lib/resolveCloudFormationEnvVariables");
10 | const transformEnvVarsToString = require("./lib/transformEnvVarsToString");
11 | const { listExports, listStackResources, describeStack } = require("./lib/aws-helper");
12 |
13 | /**
14 | * Serverless Plugin to extract Serverless' Lambda environment variables into
15 | * a local .env file for integration testing.
16 | */
17 | class ExportEnv {
18 | constructor(serverless, options) {
19 | this.serverless = serverless;
20 | this.options = options;
21 |
22 | this.commands = {
23 | "export-env": {
24 | usage: "Exports your Serverless environment variables to a .env file ",
25 | lifecycleEvents: ["collect", "resolve", "apply", "write"],
26 | options: {
27 | function: {
28 | usage: 'Specify the function for which you want to generate the .env file (e.g. "--function myFunction")',
29 | shortcut: "f",
30 | required: false,
31 | type: "string",
32 | },
33 | all: {
34 | usage: 'Merge environment variables of all functions into a single .env file (e.g. "--all")',
35 | required: false,
36 | type: "boolean",
37 | },
38 | filename: {
39 | usage: 'Name of output file (e.g. "--filename .env")',
40 | shortcut: "p",
41 | required: false,
42 | type: "string",
43 | },
44 | overwrite: {
45 | usage: 'Overwrite existing file (e.g. "--overwrite")',
46 | required: false,
47 | type: "boolean",
48 | },
49 | },
50 | },
51 | };
52 |
53 | this.isOfflineHooked = false;
54 | this.hooks = {
55 | "before:offline:start:init": this.initOfflineHook.bind(this),
56 | "before:offline:start": this.initOfflineHook.bind(this),
57 | // We monkey-patch the AWS plugin instead; so the `invoke:local:invoke` is not needed anymore
58 | // "before:invoke:local:invoke": this.initOfflineHook.bind(this),
59 | "export-env:collect": this.collectEnvVars.bind(this),
60 | "export-env:resolve": this.resolveEnvVars.bind(this),
61 | "export-env:apply": this.setEnvVars.bind(this),
62 | "export-env:write": this.writeEnvVars.bind(this),
63 | };
64 |
65 | this.globalEnvironmentVariables = {};
66 | this.functionEnvironmentVariables = {};
67 | this.filename = _.get(options, "filename", ".env");
68 | this.enableOffline = true;
69 | this.overwrite = _.get(options, "overwrite", false);
70 | this.refMap = {};
71 | this.getAttMap = {};
72 | this.importValueMap = {};
73 | this.isEnabled = true; // will be set in `_loadConfig()` below
74 |
75 | // Monkey-patch `AwsInvokeLocal` plugin to support our custom variable resolution
76 | const AwsInvokeLocal = serverless.pluginManager.plugins.find(
77 | (plugin) => plugin.constructor.name === "AwsInvokeLocal"
78 | );
79 | if (AwsInvokeLocal) {
80 | const loadEnvVarsOriginal = AwsInvokeLocal.loadEnvVars.bind(AwsInvokeLocal);
81 | AwsInvokeLocal.loadEnvVars = () => {
82 | return this.initOfflineHook().then(() => loadEnvVarsOriginal());
83 | };
84 | }
85 | }
86 |
87 | _loadConfig() {
88 | const params = _.get(this.serverless, "service.custom.export-env");
89 | this.filename = _.get(params, "filename", this.filename);
90 | this.enableOffline = _.get(params, "enableOffline", this.enableOffline);
91 | this.overwrite = _.get(params, "overwrite", this.overwrite);
92 | this.refMap = _.get(params, "refMap", this.refMap);
93 | this.getAttMap = _.get(params, "getAttMap", this.getAttMap);
94 | this.importValueMap = _.get(params, "importValueMap", this.importValueMap);
95 |
96 | // `true` if the plugin should run, `false` otherwise
97 | this.isEnabled = !this.isOfflineHooked || this.enableOffline;
98 | }
99 |
100 | initOfflineHook() {
101 | if (!this.isOfflineHooked) {
102 | this.isOfflineHooked = true;
103 | return this.serverless.pluginManager.run(["export-env"]);
104 | }
105 | return BbPromise.resolve();
106 | }
107 |
108 | collectEnvVars() {
109 | return BbPromise.try(() => {
110 | this._loadConfig();
111 | if (!this.isEnabled) {
112 | return BbPromise.resolve();
113 | }
114 |
115 | // collect environment variables
116 | this.globalEnvironmentVariables = this.serverless.service.provider.environment || {};
117 | this.functionEnvironmentVariables = collectFunctionEnvVariables(this.serverless);
118 |
119 | return BbPromise.resolve();
120 | });
121 | }
122 |
123 | resolveEnvVars() {
124 | return BbPromise.try(() => {
125 | this._loadConfig();
126 | if (!this.isEnabled) {
127 | return BbPromise.resolve();
128 | }
129 |
130 | const sls = this.serverless;
131 | const AWS = this.serverless.providers.aws;
132 | const globalEnv = this.globalEnvironmentVariables;
133 | const maps = {
134 | refMap: this.refMap,
135 | getAttMap: this.getAttMap,
136 | importValueMap: this.importValueMap,
137 | };
138 | return BbPromise.all([describeStack(AWS), listStackResources(AWS), listExports(AWS)]).then(
139 | ([stack, resources, exports]) => {
140 | // Resolve global and function environment variables
141 | return BbPromise.all([
142 | resolveCloudFormationEnvVariables(sls, globalEnv, stack, resources, exports, maps).then(
143 | (resolved) => (this.globalEnvironmentVariables = resolved)
144 | ),
145 | BbPromise.all(
146 | _.map(this.functionEnvironmentVariables, (funcEnv, funcName) =>
147 | resolveCloudFormationEnvVariables(sls, funcEnv, stack, resources, exports, maps).then(
148 | (resolved) => (this.functionEnvironmentVariables[funcName] = resolved)
149 | )
150 | )
151 | ),
152 | ]).return();
153 | }
154 | );
155 | });
156 | }
157 |
158 | setEnvVars() {
159 | return BbPromise.try(() => {
160 | this._loadConfig();
161 | if (!this.isEnabled || !this.isOfflineHooked) {
162 | // This code does only run when offline but not when executed via `export-env`
163 | return BbPromise.resolve();
164 | }
165 |
166 | // If this is a local lambda invoke, replace the service environment with the resolved one
167 | process.env.SLS_DEBUG && this.serverless.cli.log(`Updating serverless environment variable(s)`, "export-env");
168 | this.serverless.service.provider.environment = this.globalEnvironmentVariables;
169 | if (_.has(this.serverless, "service.functions")) {
170 | _.forEach(
171 | this.serverless.service.functions,
172 | (func, key) => (func.environment = this.functionEnvironmentVariables[key])
173 | );
174 | }
175 | });
176 | }
177 |
178 | writeEnvVars() {
179 | return BbPromise.try(() => {
180 | this._loadConfig();
181 | if (!this.isEnabled || this.isOfflineHooked) {
182 | // This code does not run when offline but only when executed via `export-env`
183 | return BbPromise.resolve();
184 | }
185 |
186 | const envFilePath = path.resolve(this.serverless.config.servicePath, this.filename);
187 | if (!fs.existsSync(envFilePath) || this.overwrite) {
188 | process.env.SLS_DEBUG && this.serverless.cli.log(`Writing ${this.filename} file`, "export-env");
189 |
190 | const envVars = _.clone(this.globalEnvironmentVariables);
191 | if (this.options.all) {
192 | _.forEach(this.functionEnvironmentVariables, (vars) => _.assign(envVars, vars));
193 | } else if (this.options.function) {
194 | _.assign(envVars, this.functionEnvironmentVariables[this.options.function]);
195 | }
196 | const envDocument = transformEnvVarsToString(envVars);
197 |
198 | fs.writeFileSync(envFilePath, envDocument);
199 | } else {
200 | process.env.SLS_DEBUG &&
201 | this.serverless.cli.log(`${this.filename} already exists. Leaving it untouched.`, "export-env");
202 | }
203 | });
204 | }
205 | }
206 |
207 | module.exports = ExportEnv;
208 |
--------------------------------------------------------------------------------
/src/lib/aws-helper.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const BbPromise = require("bluebird");
4 |
5 | function listExports(AWS, exports, nextToken) {
6 | exports = exports || [];
7 | return BbPromise.resolve(AWS.request("CloudFormation", "listExports", { NextToken: nextToken }))
8 | .then((response) => {
9 | exports.push.apply(exports, response.Exports);
10 | if (response.NextToken) {
11 | // Query next page
12 | return listExports(AWS, exports, response.NextToken);
13 | }
14 | })
15 | .then(() => exports);
16 | }
17 |
18 | function listStackResources(AWS, resources, nextToken) {
19 | resources = resources || [];
20 | return BbPromise.resolve(
21 | AWS.request("CloudFormation", "listStackResources", {
22 | StackName: AWS.naming.getStackName(),
23 | NextToken: nextToken,
24 | })
25 | )
26 | .then((response) => {
27 | resources.push.apply(resources, response.StackResourceSummaries);
28 | if (response.NextToken) {
29 | // Query next page
30 | return listStackResources(AWS, resources, response.NextToken);
31 | }
32 | })
33 | .then(() => resources);
34 | }
35 |
36 | function describeStack(AWS) {
37 | return BbPromise.resolve(
38 | AWS.request("CloudFormation", "describeStacks", {
39 | StackName: AWS.naming.getStackName(),
40 | })
41 | ).then((response) => {
42 | return response.Stacks[0];
43 | });
44 | }
45 |
46 | module.exports = {
47 | listExports,
48 | listStackResources,
49 | describeStack,
50 | };
51 |
--------------------------------------------------------------------------------
/src/lib/collectFunctionEnvVariables.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const _ = require("lodash");
4 |
5 | /**
6 | * Collects Serverless function specific environment variables
7 | *
8 | * @param {Serverless} serverless - Serverless Instance
9 | * @returns {String[]} Returns a list of environment variables
10 | */
11 | function collectFunctionEnvVariables(serverless) {
12 | const functions = _.get(serverless, "service.functions", {});
13 | const envVars = _.mapValues(functions, (func) => func.environment);
14 | return envVars;
15 | }
16 |
17 | module.exports = collectFunctionEnvVariables;
18 |
--------------------------------------------------------------------------------
/src/lib/resolveCloudFormationEnvVariables.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const BbPromise = require("bluebird"),
4 | _ = require("lodash"),
5 | NodeEvaluator = require("cfn-resolver-lib");
6 |
7 | function resolveGetAtt(refs, resource) {
8 | // TODO: While this code was created in good intention (isn't it all?), it doesn't work in the current form.
9 | // There's no AWS API that can help resolve !GetAtt automatically and some attributes are impossible to
10 | // determine without retrieving additional details of the resource, e.g. using an additional API call.
11 | // So, for now, we completely disable this variable resolution mechanism and rely of hardcoding the `getAttMap`
12 | // in the config instead.
13 | // Please note that the code below doesn't work properly.
14 |
15 | // const Partition = refs["AWS::Partition"];
16 | // const Region = refs["AWS::Region"];
17 | // const AccountId = refs["AWS::AccountId"];
18 | // switch (resource.ResourceType) {
19 | // case "AWS::Lambda::Function":
20 | // return {
21 | // Arn: `arn:${Partition}:lambda:${Region}:${AccountId}:function:${resource.PhysicalResourceId}`,
22 | // FunctionName: resource.PhysicalResourceId,
23 | // };
24 | // case "AWS::SNS::Topic":
25 | // return { TopicName: _.last(_.split(resource.PhysicalResourceId, ":")) };
26 | // case "AWS::SQS::Queue":
27 | // return { QueueName: _.last(_.split(resource.PhysicalResourceId, ":")) };
28 | // case "AWS::CloudWatch::Alarm":
29 | // return { AlarmName: _.last(_.split(resource.PhysicalResourceId, ":")) };
30 | // case "AWS::EC2::Subnet":
31 | // return { SubnetId: _.last(_.split(resource.PhysicalResourceId, ":")) };
32 | // case "AWS::EC2::VPC":
33 | // return { VpcId: _.last(_.split(resource.PhysicalResourceId, ":")) };
34 | // case "AWS::S3::Bucket":
35 | // return { BucketName: _.last(_.split(resource.PhysicalResourceId, ":")) };
36 | // case "AWS::EC2::SecurityGroup":
37 | // return { SecurityGroupId: _.last(_.split(resource.PhysicalResourceId, ":")) };
38 | // case "AWS::DynamoDB::Table":
39 | // return { TableName: _.last(_.split(resource.PhysicalResourceId, ":")) };
40 | // case "AWS::IAM::Role":
41 | // return { Arn: `arn:${Partition}:iam::${AccountId}:role/${resource.PhysicalResourceId}` };
42 | // case "AWS::ApiGateway::RestApi":
43 | // return { RootResourceId: resource.PhysicalResourceId };
44 | // }
45 |
46 | return resource;
47 | }
48 |
49 | function resolveResources(AWS, stack, resources, exports, toBeResolved, maps) {
50 | return BbPromise.all([AWS.getRegion(), AWS.getAccountId()]).spread((region, accountId) => {
51 | const node = _.assign({}, { toBeResolved, Parameters: [] });
52 | const refResolvers = _.merge(
53 | {
54 | "AWS::Partition": "aws", // FIXME How to determine correct value?
55 | "AWS::Region": region,
56 | "AWS::AccountId": accountId,
57 | "AWS::StackId": stack.StackId,
58 | "AWS::StackName": stack.StackName,
59 | },
60 | _.reduce(
61 | resources,
62 | (values, resource) => {
63 | values[resource.LogicalResourceId] = resource.PhysicalResourceId;
64 | return values;
65 | },
66 | {}
67 | ),
68 | maps.refMap
69 | );
70 | const getAttResolvers = _.merge(
71 | _.reduce(
72 | resources,
73 | (values, resource) => {
74 | values[resource.LogicalResourceId] = resolveGetAtt(refResolvers, resource);
75 | return values;
76 | },
77 | {}
78 | ),
79 | maps.getAttMap
80 | );
81 | const importValueResolvers = _.merge(
82 | _.reduce(
83 | exports,
84 | (values, resource) => {
85 | values[resource.Name] = resource.Value;
86 | return values;
87 | },
88 | {}
89 | ),
90 | maps.importValueMap
91 | );
92 | // Pass all resources to allow Fn::GetAtt and Conditions resolution
93 | const evaluator = new NodeEvaluator(node, {
94 | RefResolvers: refResolvers,
95 | "Fn::GetAttResolvers": getAttResolvers,
96 | "Fn::ImportValueResolvers": importValueResolvers,
97 | });
98 | const result = evaluator.evaluateNodes();
99 | if (result && result.toBeResolved) {
100 | return result.toBeResolved;
101 | }
102 |
103 | return {};
104 | });
105 | }
106 |
107 | /**
108 | * Resolves CloudFormation references and import variables
109 | *
110 | * @param {Serverless} serverless - Serverless Instance
111 | * @param {Object[]} envVars - Environment Variables to resolve
112 | * @returns {Promise} Resolves with the list of resolves environment variables
113 | */
114 | function resolveCloudFormationEnvVars(serverless, envVars, stack, resources, exports, maps) {
115 | const AWS = serverless.providers.aws;
116 | return resolveResources(AWS, stack, resources, exports, envVars, maps).then((resolved) => {
117 | process.env.SLS_DEBUG &&
118 | _.map(resolved, (value, key) =>
119 | serverless.cli.log(`Resolved environment variable ${key}: ${JSON.stringify(value)}`, "export-env")
120 | );
121 | return BbPromise.resolve(resolved);
122 | });
123 | }
124 |
125 | module.exports = resolveCloudFormationEnvVars;
126 |
--------------------------------------------------------------------------------
/src/lib/transformEnvVarsToString.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const _ = require("lodash");
4 | const os = require("os");
5 |
6 | /**
7 | * Copies environment variables into a string ready for wrtiting to a file
8 | *
9 | * @param {String[]} envVars - Environment Variables
10 | * @returns {String}
11 | */
12 | function transformEnvVarsToString(envVars) {
13 | const output = _.map(envVars, (value, key) => {
14 | return `${key}=${value}`;
15 | });
16 |
17 | return output.join(os.EOL);
18 | }
19 |
20 | module.exports = transformEnvVarsToString;
21 |
--------------------------------------------------------------------------------