diff --git a/README.md b/README.md index 6ea2bc1..272628c 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ plugins: ## Usage -When executing your function locally, the `serverless-cloudside-plugin` will replace any environment variable that contains either a `!Ref` or a `!GetAtt` that references a CloudFormation resource within your `serverless.yml` file. +When executing your function locally, the `serverless-cloudside-plugin` will replace any environment variable that contains either a `!Ref` or a `!GetAtt` that references a CloudFormation resource within your `serverless.yml` file, or a `!Fn::ImportValue` that references a CloudFormation resources that was exported by another stack. In the example below, we are creating an SQS Queue named `myQueue` and referencing it (using a CloudFormation intrinsic function) in an environment variable named `QUEUE`. @@ -103,5 +103,7 @@ This plugin currently supports the `!Ref` function that returns the `PhysicalRes There is also initial (and limited) support for using `!GetAtt` to retrieve an **ARN**. For example, you may use `!GetAtt myQueue.Arn` to retrieve the ARN for `myQueue`. The plugin generates the ARN based on the service type. For supported types, it will return a properly formatted ARN. For others, it will replace the value with **"FUNCTION NOT SUPPORTED"**. In most cases, it should be possible to support generating an ARN for a resource, but the format will need to be added to the plugin. +There now is also support for `Fn::ImportValue` to allow referencing exported resources from other CloudFormation stacks. + ## Contributions Contributions, ideas and bug reports are welcome and greatly appreciated. Please add [issues](https://github.com/jeremydaly/serverless-cloudside-plugin/issues) for suggestions and bug reports or create a pull request. diff --git a/index.js b/index.js index 74b6b70..6dbacb5 100644 --- a/index.js +++ b/index.js @@ -132,7 +132,7 @@ class InvokeCloudside { } // Set environment variables for "invoke cloudside" - loadCloudsideEnvVars() { + async loadCloudsideEnvVars() { // Get the stage (use the cloudstage option first, then stage option, then provider) const stage = this.options.cloudStage ? this.options.cloudStage : @@ -143,7 +143,7 @@ class InvokeCloudside { const stackName = this.options.stackName ? this.options.stackName : this.serverless.service.provider.stackName ? this.serverless.service.provider.stackName : - `${this.serverless.service.service}-${stage}` + `${this.serverless.service.service}-${stage}` this.serverless.cli.log(`Loading cloudside resources for '${stackName}' stack.`) @@ -159,7 +159,7 @@ class InvokeCloudside { let functions = this.options.function ? { [this.options.function] : this.serverless.service.functions[this.options.function] } - : this.serverless.service.functions + : this.serverless.service.functions Object.keys(functions).map(fn => { if (this.serverless.service.functions[fn].environment) { @@ -173,73 +173,111 @@ class InvokeCloudside { // If references need resolving, call CF if (Object.keys(cloudsideVars).length > 0) { - const options = { useCache: true } + const sdkOptions = { useCache: true } + await this.applyStackResources(cloudsideVars, stackName, sdkOptions); + await this.applyExports(cloudsideVars, sdkOptions); - return this.serverless.getProvider('aws') - .request('CloudFormation', - 'describeStackResources', - { StackName: stackName }, - options) - .then(res => { - if (res.StackResources) { - // Loop through the returned StackResources - for (let i = 0; i < res.StackResources.length; i++) { + // Replace remaining variables with warning + Object.keys(cloudsideVars).map(x => { + for (let j = 0; j < cloudsideVars[x].length; j++) { + if (cloudsideVars[x][j].fn) { + this.serverless.service.functions[cloudsideVars[x][j].fn].environment[ + cloudsideVars[x][j].env + ] = '' + } else { + this.serverless.service.provider.environment[ + cloudsideVars[x][j].env + ] = '' + } + } + }) + + } else { + return BbPromise.resolve() + } + } + + async applyStackResources(cloudsideVars, stackName, sdkOptions) { + + try { - let resource = cloudsideVars[res.StackResources[i].LogicalResourceId] + const stackResources = (await this.serverless.getProvider('aws') + .request( + 'CloudFormation', + 'describeStackResources', + { StackName: stackName }, + sdkOptions) + ).StackResources - // If the logicial id exists, add the PhysicalResourceId to the ENV - if (resource) { - for (let j = 0; j < resource.length; j++) { + if (stackResources) { + // Loop through the returned StackResources + for (let i = 0; i < stackResources.length; i++) { + let resource = cloudsideVars[stackResources[i].LogicalResourceId] - let value = resource[j].type == 'Ref' ? res.StackResources[i].PhysicalResourceId - : buildCloudValue(res.StackResources[i],resource[j].type) + // If the logicial id exists, add the PhysicalResourceId to the ENV + if (resource) { + for (let j = 0; j < resource.length; j++) { - if (resource[j].fn) { - this.serverless.service.functions[resource[j].fn].environment[ - resource[j].env + let value = resource[j].type == 'Ref' ? stackResources[i].PhysicalResourceId + : buildCloudValue(stackResources[i],resource[j].type) + + if (resource[j].fn) { + this.serverless.service.functions[resource[j].fn].environment[ + resource[j].env ] = value - } else { - this.serverless.service.provider.environment[ - resource[j].env + } else { + this.serverless.service.provider.environment[ + resource[j].env ] = value - } + } - } // end for - // Remove the cloudside variable - delete(cloudsideVars[res.StackResources[i].LogicalResourceId]) - } // end if - } // end for - } // end if StackResources - - // Replace remaining variables with warning - Object.keys(cloudsideVars).map(x => { - for (let j = 0; j < cloudsideVars[x].length; j++) { - if (cloudsideVars[x][j].fn) { - this.serverless.service.functions[cloudsideVars[x][j].fn].environment[ - cloudsideVars[x][j].env - ] = '' - } else { - this.serverless.service.provider.environment[ - cloudsideVars[x][j].env - ] = '' - } - } - }) + } // end for + // Remove the cloudside variable + delete(cloudsideVars[stackResources[i].LogicalResourceId]) + } // end if + } // end for + } // end if StackResources + } catch (err) { + console.error(err); + } + } - return true - }).error(e => { - console.log(e) - }) + async applyExports(cloudsideVars, sdkOptions) { - } else { - return BbPromise.resolve() + try { + const exports = (await this.serverless.getProvider('aws') + .request('CloudFormation', 'listExports', {}, sdkOptions)).Exports; + + if (exports) { + for (let i = 0; i < exports.length; i++) { + let resource = cloudsideVars[exports[i].Name]; + if (resource) { + + for (let j = 0; j < resource.length; j++) { + let value = exports[i].Value; + + if (resource[j].fn) { + this.serverless.service.functions[resource[j].fn].environment[ + resource[j].env + ] = value + } else { + this.serverless.service.provider.environment[ + resource[j].env + ] = value + } + } + delete(cloudsideVars[exports[i].Name]) + } + } + } + } catch (err) { + console.error(err); } } } - // Parse the environment variables and return formatted mappings const parseEnvs = (envs = {},fn) => Object.keys(envs).reduce((vars,key) => { let logicalId,ref @@ -250,6 +288,9 @@ const parseEnvs = (envs = {},fn) => Object.keys(envs).reduce((vars,key) => { } else if (envs[key]['Fn::GetAtt']) { logicalId = envs[key]['Fn::GetAtt'][0] ref = { type: envs[key]['Fn::GetAtt'][1], env: key, fn } + } else if (envs[key]['Fn::ImportValue']) { + logicalId = envs[key]['Fn::ImportValue'] + ref = { type: 'ImportValue', env: key, fn } } else { return vars } diff --git a/package-lock.json b/package-lock.json index 39c81a1..669317f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverless-cloudside-plugin", - "version": "0.0.0", + "version": "1.0.3", "lockfileVersion": 1, "requires": true, "dependencies": {