src/index.js
- import _ from 'lodash'
- import AWS from 'aws-sdk'
- import childProcess from 'child_process'
- import winston from 'winston'
- import Credstash from 'credstash'
- import deasyncPromise from 'deasync-promise'
-
- const CREDSTASH_PREFIX = 'credstash'
-
- function _fetchCred(name, credstash) {
- return new Promise((resolve, reject) => {
- credstash.get(name, (err, secret) => {
- if (err) {
- reject(err)
- } else {
- resolve(secret)
- }
- })
- })
- }
-
- class ServerlessDeployEnvironment {
- constructor(serverless, options) {
- this.serverless = serverless
- this.credstash = new Credstash()
-
- // Only meaningful for AWS
- this.provider = 'aws'
- this.config = serverless.service.custom.deployEnvironment
- this.options = options
- this.commands = {
- runWithEnvironment: {
- usage: 'Runs the specified command with the serverless environment variables set',
- lifecycleEvents: ['run'],
- options: {
- command: {
- usage: 'The command to run',
- shortcut: 'c',
- type: 'string'
- },
- stage: {
- usage: 'The stage to use for stage-specific variables',
- shortcut: 's',
- type: 'string'
- },
- args: {
- usage: 'Extra arguments to pass through to the subprocess',
- shortcut: 'a',
- type: 'multiple'
- }
- }
- }
- }
- // Run automatically as part of the deploy
- this.hooks = {
- // Hook before deploying the function
- 'before:deploy:createDeploymentArtifacts': () => this._addDeployEnvironment(),
- // Hook before running sls offline
- 'before:offline:start:init': () => this._addDeployEnvironment(),
- // Hook before running sls webpack invoke
- 'before:webpack:invoke:invoke': () => this._addDeployEnvironment(),
- // Command hook
- 'runWithEnvironment:run': () => this._runWithEnvironment()
- }
-
- const stage = options.stage || _.get(serverless, 'service.custom.defaults.stage')
- if (!stage) {
- throw new Error('No stage found for serverless-plugin-deploy-environment')
- }
- winston.debug(`Getting deploy variables for stage ${stage}`)
-
- // TODO: This doesn't belong here, but we need to set the options before populating the new properties.
- serverless.variables.options = options // eslint-disable-line
-
- // Allow credstash variables to be resolved
- // TODO(msills): Break into a separate plugin
- const delegate = serverless.variables.getValueFromSource.bind(serverless.variables)
- const credstash = this.credstash
- serverless.variables.getValueFromSource = function getValueFromSource(variableString) { // eslint-disable-line no-param-reassign, max-len
- if (variableString.startsWith(`${CREDSTASH_PREFIX}:`)) {
- // If we are not to resolve credstash variables here, just write the variable through unchanged
- if (options.credstash && options.credstash !== 'true') {
- winston.info(`Skipping credstash resolution for variable '${variableString}'`)
- return Promise.resolve(variableString)
- }
-
- // Configure the AWS region
- const region = serverless.service.provider.region
- if (!region) {
- return Promise.reject(new Error('Cannot hydrate Credstash variables without a region'))
- }
- AWS.config.update({ region })
-
- const key = variableString.split(`${CREDSTASH_PREFIX}:`)[1]
- return _fetchCred(key, credstash)
- }
-
- return delegate(variableString)
- }
-
- if (!serverless.service.custom.deploy) {
- winston.warn('No deploy object found in custom, even though the serverless-deploy-environment plugin is loaded.')
- }
-
- const deployVariables = _.get(serverless, 'service.custom.deploy.variables', { })
- const deployEnvironment = _.get(serverless, 'service.custom.deploy.environments', { })
-
- // Explicitly load the variable syntax, so that calls to populateProperty work
- // TODO(msills): Figure out how to avoid this. For now, it seems safe.
- serverless.variables.loadVariableSyntax()
- // Explicitly resolve these here, so that we can apply any transformations that we want
- const vars = deasyncPromise(serverless.variables.populateProperty(deployVariables, false))
- serverless.service.deployVariables = _.merge({}, vars.default || {}, vars[stage]) // eslint-disable-line
- const envs = deasyncPromise(serverless.variables.populateProperty(deployEnvironment, false)) // eslint-disable-line
- serverless.service.deployEnvironment = _.merge({}, envs.default || {}, envs[stage]) // eslint-disable-line
- }
-
- async _resolveDeployEnvironment() {
- return this.serverless.service.deployEnvironment
- }
-
- async _addDeployEnvironment() {
- const env = await this._resolveDeployEnvironment()
- // Make sure that the environment exists (if no environment is specified, it's undefined), and augment it with the
- // scoped environment
- this.serverless.service.provider.environment = _.extend(this.serverless.service.provider.environment, env)
- }
-
- async _runWithEnvironment() {
- const deployEnv = await this._resolveDeployEnvironment()
- const env = _.merge({}, process.env, deployEnv) // Merge the current environment, overridden with the deploy environment
- const args = this.options.args || ''
- const output = childProcess.execSync(`${this.options.command} ${args}`, { env, cwd: process.cwd() }).toString()
- for (const line of output.split('\n')) {
- winston.info(`[COMMAND OUTPUT]: ${line}`)
- }
- }
- }
-
- module.exports = ServerlessDeployEnvironment