Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for Fn::ImportValue #8

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down Expand Up @@ -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.
149 changes: 95 additions & 54 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 :
Expand All @@ -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.`)

Expand All @@ -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) {
Expand All @@ -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
] = '<RESOURCE NOT PUBLISHED>'
} else {
this.serverless.service.provider.environment[
cloudsideVars[x][j].env
] = '<RESOURCE NOT PUBLISHED>'
}
}
})

} 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
] = '<RESOURCE NOT PUBLISHED>'
} else {
this.serverless.service.provider.environment[
cloudsideVars[x][j].env
] = '<RESOURCE NOT PUBLISHED>'
}
}
})
} // 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
Expand All @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.