New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

serverless-dynamodb-autoscaling

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

serverless-dynamodb-autoscaling - npm Package Compare versions

Comparing version 0.2.0 to 0.3.0

4

package.json
{
"name": "serverless-dynamodb-autoscaling",
"description": "Serverless Plugin for Amazon DynamoDB Auto Scaling configuration.",
"version": "0.2.0",
"description": "Serverless Plugin for Amazon DynamoDB Auto Scaling.",
"version": "0.3.0",
"main": "src/plugin.js",

@@ -6,0 +6,0 @@ "scripts": {

# ⚡️ Serverless Plugin for DynamoDB Auto Scaling
[![npm](https://img.shields.io/npm/v/serverless-dynamodb-autoscaling.svg)](https://www.npmjs.com/package/serverless-dynamodb-autoscaling)
[![CircleCI](https://img.shields.io/circleci/project/github/sbstjn/serverless-dynamodb-autoscaling.svg)](https://circleci.com/gh/sbstjn/serverless-dynamodb-autoscaling)
[![CircleCI](https://img.shields.io/circleci/project/github/sbstjn/serverless-dynamodb-autoscaling/master.svg)](https://circleci.com/gh/sbstjn/serverless-dynamodb-autoscaling)
[![license](https://img.shields.io/github/license/sbstjn/serverless-dynamodb-autoscaling.svg)](https://github.com/sbstjn/serverless-dynamodb-autoscaling/blob/master/LICENSE.md)
[![Coveralls](https://img.shields.io/coveralls/sbstjn/serverless-dynamodb-autoscaling.svg)](https://coveralls.io/github/sbstjn/serverless-dynamodb-autoscaling)
With this plugin for [serverless](https://serverless.com) you can set DynamoDB Auto Scaling configuratin in your `serverless.yml` file. The plugin supports multiple tables and separate configuration sets for `read` and `write` capacities using AWS [native DynamoDB Auto Scaling](https://aws.amazon.com/blogs/aws/new-auto-scaling-for-amazon-dynamodb/).
With this plugin for [serverless](https://serverless.com), you can enable DynamoDB Auto Scaling for tables and **Global Secondary Indexes** easily in your `serverless.yml` configuration file. The plugin supports multiple tables and indexes, as well as separate configuration for `read` and `write` capacities using Amazon's [native DynamoDB Auto Scaling](https://aws.amazon.com/blogs/aws/new-auto-scaling-for-amazon-dynamodb/).

@@ -19,7 +19,5 @@ ## Usage

# Via npm
$ npm install serverless-dynamodb-autoscaling --save-dev
$ npm install serverless-dynamodb-autoscaling
```
## Configuration
Add the plugin to your `serverless.yml`:

@@ -32,4 +30,6 @@

Configure DynamoDB Auto Scaling in `serverless.yml` with references to your DynamoDB CloudFormation resources for the `table` property:
## Configuration
Configure DynamoDB Auto Scaling in `serverless.yml` with references to your DynamoDB CloudFormation resources for the `table` property. The `index` configuration is optional to apply Auto Scaling *Global Secondary Index*.
```yaml

@@ -39,2 +39,4 @@ custom:

- table: CustomTable # DynamoDB Resource
index: # List or single index name
- custom-index-name
read:

@@ -48,18 +50,32 @@ minimum: 5 # Minimum read capacity

usage: 0.5 # Targeted usage percentage
- table: AnotherTable
read:
minimum: 5
maximum: 1000
# usage: 0.75 is the default
```
That's it! With the next deployment (`sls deploy`) serverless will add a CloudFormation configuration to enable Auto Scaling for the DynamoDB resources `CustomTable` and `AnotherTable`.
That's it! With the next deployment, [serverless](https://serverless.com) will add a CloudFormation configuration to enable Auto Scaling for the DynamoDB resources `CustomTable` and its *Global Secondary Index* called `custom-index-name`.
You must of course provide at least a configuration for `read` or `write` to enable Auto Scaling. The value for `usage` has a default of 75 percent.
You must provide at least a configuration for `read` or `write` to enable Auto Scaling!
**Notice:** *With the relese of `v0.2.x` the plugin introduced a breaking change. Starting with `v0.2.0` you need to provide the CloudFormation reference for the `table` property. In `v0.1.x` the plugin used a `name` property with the DynamoDB table name.*
### Defaults
```yaml
maximum: 200
minimum: 5
usage: 0.75
```
### Index
If you only want to enable Auto Scaling for the index, use `indexOnly: true` to skip Auto Scaling for the general DynamoDB table.
### API Throtteling
CloudWatch has very strict [API rate limits](http://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_limits.html)! If you plan to configure Auto Scaling for multiple DynamoDB tables or *Global Secondary Indexes*, request an increase of the rate limits first! Otherwise, you might run into an error like this:
```
An error occurred while provisioning your stack: XYZ - Unable to create alarms for scaling policy XYZ due to reason:
Rate exceeded (Service: AmazonCloudWatch; Status Code: 400; Error Code: Throttling; Request ID: XYZ).
```
## DynamoDB
The configuration above works fine for a default DynamoDB table configuration.
The example serverless configuration above works fine for a DynamoDB table CloudFormation resource like this:

@@ -82,2 +98,12 @@ ```yaml

WriteCapacityUnits: 5
GlobalSecondaryIndexes:
- IndexName: custom-index-name
KeySchema:
- AttributeName: key
KeyType: HASH
Projection:
ProjectionType: ALL
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
```

@@ -98,2 +124,2 @@

To make sure you have a pleasant experience, please read the [code of conduct](CODE_OF_CONDUCT.md). It outlines core values and believes and will make working together a happier experience.
To make sure you have a pleasant experience, please read the [code of conduct](CODE_OF_CONDUCT.md). It outlines core values and beliefs and will make working together a happier experience.
const util = require('util')
const clean = (input) => input.replace(/[^a-z0-9+]+/gi, '')
function clean (input) {
return input.replace(/[^a-z0-9+]+/gi, '')
}
const policyScale = (table, read) => clean(util.format('Table%sScalingPolicy-%s', read ? 'Read' : 'Write', table))
const policyRole = (table) => clean(util.format('DynamoDBAutoscalePolicy-%s', table))
const dimension = (read) => util.format('dynamodb:table:%sCapacityUnits', read ? 'Read' : 'Write')
const target = (table, read) => clean(util.format('AutoScalingTarget%s-%s', read ? 'Read' : 'Write', table))
const metric = (read) => clean(util.format('DynamoDB%sCapacityUtilization', read ? 'Read' : 'Write'))
const role = (table) => clean(util.format('DynamoDBAutoscaleRole-%s', table))
function policyScale (table, read, index, stage) {
return clean(
util.format(
'Table%sScalingPolicy-%s%s%s',
read ? 'Read' : 'Write',
table,
index || '',
stage || ''
)
)
}
module.exports = { dimension, metric, policyScale, policyRole, role, target, clean }
function policyRole (table, index, stage) {
return clean(
util.format(
'DynamoDBAutoscalePolicy-%s%s%s',
table,
index || '',
stage || ''
)
)
}
function dimension (read, index) {
return util.format(
'dynamodb:%s:%sCapacityUnits',
index ? 'index' : 'table',
read ? 'Read' : 'Write'
)
}
function target (table, read, index, stage) {
return clean(
util.format(
'AutoScalingTarget%s-%s%s%s',
read ? 'Read' : 'Write',
table,
index || '',
stage || ''
)
)
}
function metric (read) {
return clean(
util.format(
'DynamoDB%sCapacityUtilization',
read ? 'Read' : 'Write'
)
)
}
function role (table, index, stage) {
return clean(
util.format(
'DynamoDBAutoscaleRole-%s%s%s',
table,
index || '',
stage || ''
)
)
}
module.exports = {
clean,
dimension,
metric,
policyRole,
policyScale,
role,
target
}
const names = require('./names')
class Policy {
constructor (table, value, read, scaleIn, scaleOut) {
constructor (table, value, read, scaleIn, scaleOut, index, stage) {
this.table = table
this.index = index
this.stage = stage
this.value = parseFloat(value, 10) * 100

@@ -10,16 +12,20 @@ this.read = !!read

this.scaleOut = parseInt(scaleOut, 10)
this.dependencies = []
}
setDependencies (list) {
this.dependencies = list
return this
}
toJSON () {
return {
[names.policyScale(this.table, this.read)]: {
[names.policyScale(this.table, this.read, this.index, this.stage)]: {
'Type': 'AWS::ApplicationAutoScaling::ScalingPolicy',
'DependsOn': [
this.table,
names.target(this.table, this.read)
],
'DependsOn': [ this.table, names.target(this.table, this.read, this.index, this.stage) ].concat(this.dependencies),
'Properties': {
'PolicyName': names.policyScale(this.table, this.read),
'PolicyName': names.policyScale(this.table, this.read, this.index, this.stage),
'PolicyType': 'TargetTrackingScaling',
'ScalingTargetId': { 'Ref': names.target(this.table, this.read) },
'ScalingTargetId': { 'Ref': names.target(this.table, this.read, this.index, this.stage) },
'TargetTrackingScalingPolicyConfiguration': {

@@ -26,0 +32,0 @@ 'PredefinedMetricSpecification': {

const names = require('./names')
class Role {
constructor (table) {
constructor (table, index, stage) {
this.table = table
this.index = index
this.stage = stage
this.dependencies = []
}
setDependencies (list) {
this.dependencies = list
return this
}
toJSON () {
return {
[names.role(this.table)]: {
[names.role(this.table, this.index, this.stage)]: {
'Type': 'AWS::IAM::Role',
'DependsOn': [
this.table
],
'DependsOn': [ this.table ].concat(this.dependencies),
'Properties': {
'RoleName': names.role(this.table),
'RoleName': names.role(this.table, this.index, this.stage),
'AssumeRolePolicyDocument': {

@@ -31,3 +38,3 @@ 'Version': '2012-10-17',

{
'PolicyName': names.policyRole(this.table),
'PolicyName': names.policyRole(this.table, this.index, this.stage),
'PolicyDocument': {

@@ -34,0 +41,0 @@ 'Version': '2012-10-17',

const names = require('./names')
class Target {
constructor (table, min, max, read) {
constructor (table, min, max, read, index, stage) {
this.table = table
this.index = index
this.stage = stage
this.min = parseInt(min, 10)
this.max = parseInt(max, 10)
this.read = !!read
this.dependencies = []
}
setDependencies (list) {
this.dependencies = list
return this
}
toJSON () {
const resource = [ 'table/', { 'Ref': this.table } ]
if (this.index) {
resource.push('/index/', this.index)
}
return {
[names.target(this.table, this.read)]: {
[names.target(this.table, this.read, this.index, this.stage)]: {
'Type': 'AWS::ApplicationAutoScaling::ScalableTarget',
'DependsOn': [
this.table,
names.role(this.table)
],
'DependsOn': [ this.table, names.role(this.table, this.index, this.stage) ].concat(this.dependencies),
'Properties': {
'MaxCapacity': this.max,
'MinCapacity': this.min,
'ResourceId': { 'Fn::Join': [ '', [ 'table/', { 'Ref': this.table } ] ] },
'RoleARN': { 'Fn::GetAtt': [ names.role(this.table), 'Arn' ] },
'ScalableDimension': names.dimension(this.read),
'ResourceId': { 'Fn::Join': [ '', resource ] },
'RoleARN': { 'Fn::GetAtt': [ names.role(this.table, this.index, this.stage), 'Arn' ] },
'ScalableDimension': names.dimension(this.read, this.index),
'ServiceNamespace': 'dynamodb'

@@ -26,0 +38,0 @@ }

@@ -1,3 +0,1 @@

'use strict'
const _ = require('lodash')

@@ -11,4 +9,15 @@ const util = require('util')

const text = {
INVALID_CONFIGURATION: 'Invalid serverless configuration',
ONLY_AWS_SUPPORT: 'Only supported for AWS provicer',
NO_AUTOSCALING_CONFIG: 'Not Auto Scaling configuration found'
}
class Plugin {
constructor (serverless, options) {
/**
* Constructur
*
* @param {object} serverless
*/
constructor (serverless) {
this.serverless = serverless

@@ -20,15 +29,26 @@ this.hooks = {

/**
* Validate the request and check if configuration is available
*/
validate () {
assert(this.serverless, 'Invalid serverless configuration')
assert(this.serverless.service, 'Invalid serverless configuration')
assert(this.serverless.service.provider, 'Invalid serverless configuration')
assert(this.serverless.service.provider.name, 'Invalid serverless configuration')
assert(this.serverless.service.provider.name === 'aws', 'Only supported for AWS provider')
assert(this.serverless, text.INVALID_CONFIGURATION)
assert(this.serverless.service, text.INVALID_CONFIGURATION)
assert(this.serverless.service.provider, text.INVALID_CONFIGURATION)
assert(this.serverless.service.provider.name, text.INVALID_CONFIGURATION)
assert(this.serverless.service.provider.name === 'aws', text.ONLY_AWS_SUPPORT)
assert(this.serverless.service.custom.capacities, 'No Auto Scaling configuration found')
assert(this.serverless.service.provider.stage, text.INVALID_CONFIGURATION)
assert(this.serverless.service.custom, text.NO_AUTOSCALING_CONFIG)
assert(this.serverless.service.custom.capacities, text.NO_AUTOSCALING_CONFIG)
}
/**
* Parse configuration and fill up with default values when needed
*
* @param {object} config
* @return {object}
*/
defaults (config) {
return {
table: config.table,
read: {

@@ -47,51 +67,104 @@ usage: config.read && config.read.usage ? config.read.usage : 0.75,

process () {
this.serverless.service.custom.capacities.map(
config => {
// Skip set if no read or write scaling configuration is available
if (!config.read && !config.write) {
return this.serverless.cli.log(
util.format(' - Skipping configuration for resource "%s"', config.table)
)
}
/**
* Create CloudFormation resources for table (and optional index)
*
* @param {string} table
* @param {string} index
* @param {object} config
*/
resources (table, index, config) {
const resources = []
const stage = this.serverless.service.provider.stage
const data = this.defaults(config)
// Fill configuration with defaults for missing values
const table = this.defaults(config)
const resources = []
// Start processing configuration
this.serverless.cli.log(
util.format(' - Building configuration for resource "table/%s%s"', table, (index ? ('/index/' + index) : ''))
)
// Start processing configuration
this.serverless.cli.log(
util.format(' - Adding configuration for resource "%s"', table.table)
// Add role to manage Auto Scaling policies
resources.push(new Role(table, index, stage))
// Only add Auto Scaling for read capacity if configuration set is available
if (config.read) {
resources.push(
// ScaleIn/ScaleOut values are fix to 60% usage
new Policy(table, data.read.usage, true, 60, 60, index, stage),
new Target(table, data.read.minimum, data.read.maximum, true, index, stage)
)
}
// Only add Auto Scaling for write capacity if configuration set is available
if (config.write) {
resources.push(
// ScaleIn/ScaleOut values are fix to 60% usage
new Policy(table, data.write.usage, false, 60, 60, index, stage),
new Target(table, data.write.minimum, data.write.maximum, false, index, stage)
)
}
return resources
}
/**
* Generate CloudFormation resources for DynamoDB table and indexes
*
* @param {string} table
* @param {object} config
*/
generate (table, config) {
let resources = []
let lastRessources = []
const indexes = this.normalize(config.index)
if (!config.indexOnly) {
indexes.unshift(null) // Horrible solution
}
indexes.forEach(
index => {
const current = this.resources(table, index, config).map(
resource => resource.setDependencies(lastRessources).toJSON()
)
// Add role to manage Auto Scaling policies
resources.push(new Role(table.table))
resources = resources.concat(current)
lastRessources = current.map(item => Object.keys(item).pop())
}
)
// Only add Auto Scaling for read capacity if configuration set is available
if (config.read) {
resources.push(
new Policy(table.table, table.read.usage, true, 60, 60),
new Target(table.table, table.read.minimum, table.read.maximum, true)
)
}
return resources
}
// Only add Auto Scaling for write capacity if configuration set is available
if (config.write) {
resources.push(
new Policy(table.table, table.write.usage, false, 60, 60),
new Target(table.table, table.write.minimum, table.write.maximum, false)
)
}
/**
* Check if parameter is defined and return as array if only a string is provided
*
* @param {array|string} data
* @return {array}
*/
normalize (data) {
if (data && data.constructor !== Array) {
return [ data.toString() ]
}
// Inject templates in serverless CloudFormation template
resources.forEach(
return (data || []).slice(0)
}
/**
* Process the provided configuration
*
* @return {Promise}
*/
process () {
this.serverless.service.custom.capacities.filter(
config => !!config.read || !!config.write
).forEach(
config => this.normalize(config.table).forEach(
table => this.generate(table, config).forEach(
resource => _.merge(
this.serverless.service.provider.compiledCloudFormationTemplate.Resources,
resource.toJSON()
resource
)
)
}
)
)
return Promise.resolve()
}

@@ -101,17 +174,11 @@

return Promise.resolve().then(
this.validate.bind(this)
() => this.validate()
).then(
() => this.serverless.cli.log(
util.format('Configure DynamoDB Auto Scaling …')
)
() => this.serverless.cli.log(util.format('Configure DynamoDB Auto Scaling …'))
).then(
this.process.bind(this)
() => this.process()
).then(
() => this.serverless.cli.log(
util.format('Added DynamoDB Auto Scaling to CloudFormation!')
)
() => this.serverless.cli.log(util.format('Added DynamoDB Auto Scaling to CloudFormation!'))
).catch(
err => this.serverless.cli.log(
util.format('Skipping DynamoDB Auto Scaling: %s!', err.message)
)
er => this.serverless.cli.log(util.format('Skipping DynamoDB Auto Scaling: %s!', er.message))
)

@@ -118,0 +185,0 @@ }

@@ -18,2 +18,6 @@ const names = require('../../src/aws/names')

it('creates name for Role with index and stage', () => {
expect(names.role('test-with-invalid-characters', 'index', 'stage')).toBe('DynamoDBAutoscaleRoletestwithinvalidcharactersindexstage')
})
it('creates name for Metric (read)', () => {

@@ -20,0 +24,0 @@ expect(names.metric(true)).toBe('DynamoDBReadCapacityUtilization')

@@ -5,25 +5,32 @@ 'use strict'

it('creates CloudFormation configuration', () => {
let config = {
service: {
custom: {
'dynamodb-autoscaling': [
{ name: 'table-name' }
]
},
provider: {
region: 'test-region',
compiledCloudFormationTemplate: {
Resources: {}
}
}
describe('Normalize', () => {
it('converts everything to an array', () => {
const test = new Plugin()
expect(test.normalize('test')).toEqual(['test'])
expect(test.normalize(['test'])).toEqual(['test'])
expect(test.normalize(['test', 'foo'])).toEqual(['test', 'foo'])
expect(test.normalize([])).toEqual([])
expect(test.normalize()).toEqual([])
})
})
describe('Defaults', () => {
it('creates object with defaults', () => {
let config = {
read: { maximum: 100, usage: 1 },
write: { minimum: 20 }
}
}
const test = new Plugin(config)
test.beforeDeployResources()
const test = new Plugin()
const data = test.defaults(config)
// const data = config.service.provider.compiledCloudFormationTemplate.Resources
expect(data.read.minimum).toBe(5)
expect(data.read.maximum).toBe(100)
expect(data.read.usage).toBe(1)
expect(true).toBe(true)
expect(data.write.minimum).toBe(20)
expect(data.write.maximum).toBe(200)
expect(data.write.usage).toBe(0.75)
})
})
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc