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

Per environment lamba configuration JSON files #304

Open
wants to merge 5 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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ Add a few lines to your `package.json`. Your [account id](https://console.aws.am

Environments for a shep project are defined by the aliases on the functions associated with a project. Environments are created through `shep deploy --env new_env` and managed by using the `shep config` commands. Shep takes a strong stance against having different environments for different functions within a project. If you attempt a command which requires the listing of environments and there is a mismatch detected, then shep will throw a `EnvironmentMistmach` error until you remedy the issue. Most issues can be automatically fixed by using `shep config sync`, the only issues this can't solve are conflicting environment variable values. Conflicting value issues can be solved by using `shep config set my_env CONFLICT_VARIABLE=value`.

Additionally, environment specific configuration for the lambda deployment itself can be defined in both the project's and function's `lambda.{env}.json` files. This is particularly useful in specifying which VPC a lambda function should be placed in when you have a VPC per environment setup. Additionally if you are still using the `nodejs4.3` runtime in production and are looking to upgrade to the `nodejs6.10` runtime, you can test this in only for your development stage.

```json
{
"VpcConfig": {
"SecurityGroupIds": [ "sg-12345678" ],
"SubnetIds": [ "subnet-12345678", "subnet-abcdef12", "subnet-abcd1234" ]
},
"Runtime": "nodejs6.10"
}
```
### Custom Builds Commands

By default shep builds all your functions using webpack. If your project requires a different build process, then edit your `package.json`. Before running your build command, shep populates the `PATTERN` environment variable which can be accessed as `process.env.PATTERN` in your build command. Be aware that using your own build process will break pattern matching for `shep build` unless your build command respects the `PATTERN` variable.
Expand Down
2 changes: 1 addition & 1 deletion src/config-sync/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import uploadEnvironment from '../util/upload-environment'
import { funcs, lambdaConfig } from '../util/load'

export default async function ({ env }) {
const configs = await Promise.map(funcs(), lambdaConfig)
const configs = await Promise.map(funcs(), (func) => lambdaConfig(func, env))
const environments = await getFunctionEnvs(env, configs)
const { common, differences, conflicts } = environmentCheck(environments)

Expand Down
2 changes: 1 addition & 1 deletion src/logs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import getLogs from '../util/get-logs'
import { lambdaConfig } from '../util/load'

export default async function ({ name, env, time = Infinity, logger = () => {} }) {
const { FunctionName } = await lambdaConfig(name)
const { FunctionName } = await lambdaConfig(name, env)
const aliasName = env

const [logGroupName, functionVersion] = await Promise.all([getLogGroup({ FunctionName }), getAliasVersion({ functionName: FunctionName, aliasName })])
Expand Down
2 changes: 1 addition & 1 deletion src/run/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function runFunction (opts) {
return async (name) => {
const env = opts.environment || 'development'
const performBuild = opts.build
const lambdaConfig = await load.lambdaConfig(name)
const lambdaConfig = await load.lambdaConfig(name, env)
const events = await load.events(name, opts.event)
const [ fileName, handler ] = lambdaConfig.Handler.split('.')

Expand Down
2 changes: 1 addition & 1 deletion src/util/get-environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { getEnvironment } from './aws/lambda'
import { lambdaConfig, funcs } from './load'

export default async function (env, name) {
const configs = await Promise.map(funcs(name), lambdaConfig)
const configs = await Promise.map(funcs(name), (func) => lambdaConfig(func, env))
return Promise.map(configs, (config) => getEnvironment(env, config))
}
10 changes: 7 additions & 3 deletions src/util/load.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import isEqual from 'lodash.isequal'
import path from 'path'
import Promise from 'bluebird'
import { readdir, readJSON } from './modules/fs'
import { readdir, readJSON, exists } from './modules/fs'
import minimatch from 'minimatch'
import { listAliases, isFunctionDeployed } from './aws/lambda'

Expand Down Expand Up @@ -53,11 +53,15 @@ export async function funcs (pattern = '*') {
return funcs
}

export async function lambdaConfig (name) {
export async function lambdaConfig (name, env = null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would you call this without specifying the env? If we never call it that way, might as well throw if either of these arguments are missing

const functionConfig = await readJSON(`functions/${name}/lambda.json`)
const functionEnvConfigExists = env ? await exists(`functions/${name}/lambda.${env}.json`) : false
const functionEnvConfig = functionEnvConfigExists ? await readJSON(`functions/${name}/lambda.${env}.json`) : {}
const projectConfig = await readJSON(`lambda.json`)
const projectEnvConfigExists = env ? await exists(`lambda.${env}.json`) : false
const projectEnvConfig = projectEnvConfigExists ? await readJSON(`lambda.${env}.json`) : {}

return Object.assign({}, projectConfig, functionConfig)
return Object.assign({}, projectConfig, projectEnvConfig, functionConfig, functionEnvConfig)
}

export async function pkg () {
Expand Down
2 changes: 1 addition & 1 deletion src/util/remove-environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { AWSEnvironmentVariableNotFound } from './errors'
const pattern = '*'

export default async function (env, vars) {
const configs = await Promise.map(funcs(pattern), async (name) => { return { name, config: await lambdaConfig(name) } })
const configs = await Promise.map(funcs(pattern), async (name) => { return { name, config: await lambdaConfig(name, env) } })
return Promise.map(configs, async ({ name, config }) => {
const oldFunc = await getFunction({ FunctionName: config.FunctionName, Qualifier: env })
const wantedFunc = merge({}, oldFunc)
Expand Down
2 changes: 1 addition & 1 deletion src/util/upload-environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { lambdaConfig, funcs } from './load'
const pattern = '*'

export default async function (env, vars) {
const configs = await Promise.map(funcs(pattern), async (name) => { return { name, config: await lambdaConfig(name) } })
const configs = await Promise.map(funcs(pattern), async (name) => { return { name, config: await lambdaConfig(name, env) } })
return Promise.map(configs, async ({name, config}) => {
const aliasExists = await doesAliasExist({ FunctionName: config.FunctionName, Alias: env })
const oldFunc = await getFunction({ FunctionName: config.FunctionName, Qualifier: (aliasExists ? env : undefined) })
Expand Down
2 changes: 1 addition & 1 deletion src/util/upload-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import zipDir from './zip-dir'

export default async function (fns, env) {
return Promise.map(fns, async ({ name, key, bucket }) => {
const config = await lambdaConfig(name)
const config = await lambdaConfig(name, env)
let wantedFunc = { Config: config, Code: {}, Identifier: {} }

if (bucket && key) {
Expand Down
8 changes: 4 additions & 4 deletions test-ava/logs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const pkg = {
region: 'us-east-1'
}
}
const stage = 'development'
const env = 'development'
const functionName = 'bar'
const callback = td.function('callback')
const getLogResponse = {
Expand All @@ -17,19 +17,19 @@ const getLogResponse = {

const load = td.replace('../../src/util/load')
td.when(load.pkg()).thenResolve(pkg)
td.when(load.lambdaConfig(functionName)).thenResolve({ FunctionName: functionName })
td.when(load.lambdaConfig(functionName, env)).thenResolve({ FunctionName: functionName })

const cloudwatchLogs = td.replace('../../src/util/aws/cloudwatch-logs')
td.when(cloudwatchLogs.getLogGroup(td.matchers.contains({ FunctionName: functionName }))).thenResolve('/aws/log/group')

const lambda = td.replace('../../src/util/aws/lambda')
td.when(lambda.getAliasVersion(td.matchers.contains({ aliasName: stage }))).thenResolve('1')
td.when(lambda.getAliasVersion(td.matchers.contains({ aliasName: env }))).thenResolve('1')

const getLogs = td.replace('../../src/util/get-logs')
td.when(getLogs(td.matchers.isA(Object))).thenResolve(getLogResponse)

test('Continues loop', async (t) => {
const shep = require('../../src/index')
await t.throws(shep.logs({ stage, name: functionName, stream: true }))
await t.throws(shep.logs({ env, name: functionName, stream: true }))
td.verify(callback(td.matchers.isA(Number)))
})
3 changes: 2 additions & 1 deletion test-ava/run/index-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import td from '../helpers/testdouble'
import path from 'path'

const funcName = 'foo'
const env = 'development'
const handler = 'handler'
const config = { Handler: `index.${handler}` }
const events = ['event']
Expand All @@ -12,7 +13,7 @@ td.when(lambdaFunc[handler](td.matchers.anything(), td.matchers.isA(Object))).th
const load = td.replace('../../src/util/load')
load.distPath = async (joinPath) => joinPath ? path.join('dist', joinPath) : 'dist'
td.when(load.funcs(funcName)).thenResolve([funcName])
td.when(load.lambdaConfig(funcName)).thenResolve(config)
td.when(load.lambdaConfig(funcName, env)).thenResolve(config)
td.when(load.events(funcName, td.matchers.anything())).thenResolve(events)

const build = td.replace('../../src/util/build-functions')
Expand Down
3 changes: 2 additions & 1 deletion test-ava/run/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import test from 'ava'
import td from '../helpers/testdouble'

const funcName = 'foo'
const environment = 'development'
const handler = 'handler'
const config = { Handler: `index.${handler}` }
const events = ['event']
Expand All @@ -12,7 +13,7 @@ td.when(lambdaFunc[handler](td.matchers.anything(), td.matchers.isA(Object))).th
const load = td.replace('../../src/util/load')
load.distPath = async (joinPath) => joinPath ? path.join('dist', joinPath) : 'dist'
td.when(load.funcs(funcName)).thenResolve([funcName])
td.when(load.lambdaConfig(funcName)).thenResolve(config)
td.when(load.lambdaConfig(funcName, environment)).thenResolve(config)
td.when(load.events(funcName, td.matchers.anything())).thenResolve(events)

const requireProject = td.replace('../../src/util/require-project')
Expand Down