├── .nvmrc
├── examples
└── serverless-example
│ ├── .nvmrc
│ ├── src
│ ├── backups.js
│ └── listBackups.js
│ ├── .babelrc
│ ├── webpack.config.js
│ ├── README.md
│ ├── package.json
│ └── serverless.yml
├── .github
└── FUNDING.yml
├── .eslintignore
├── .codeclimate.yml
├── .idea
├── markdown-navigator
│ └── profiles_settings.xml
├── encodings.xml
├── codeStyles
│ ├── codeStyleConfig.xml
│ └── Project.xml
├── jsLibraryMappings.xml
├── misc.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── modules.xml
├── serverless-plugin-db-backups.iml
├── vcs.xml
└── markdown-navigator.xml
├── .babelrc
├── .travis.yml
├── helpers
└── iamRole.js
├── .eslintrc.yml
├── LICENSE
├── test
├── __mocks__
│ └── serverlessMock.js
└── serverless-plugin-db-backups.test.js
├── package.json
├── CHANGELOG.md
├── .gitignore
├── lib
└── index.js
├── serverless-plugin-db-backups.js
└── README.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | v14.16.1
2 |
--------------------------------------------------------------------------------
/examples/serverless-example/.nvmrc:
--------------------------------------------------------------------------------
1 | v14.16.1
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | liberapay: unlyEd
2 | github: [UnlyEd, Vadorequest]
3 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/node_modules/**
2 | /build/**
3 | /coverage/**
4 | local-settings.js
5 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | engines:
2 | plugins:
3 | eslint:
4 | enabled: true
5 | channel: "eslint-4"
6 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "targets": {
7 | "node": "8.10"
8 | }
9 | }
10 | ]
11 | ]
12 | }
--------------------------------------------------------------------------------
/examples/serverless-example/src/backups.js:
--------------------------------------------------------------------------------
1 | import dynamodbAutoBackups from '@unly/serverless-plugin-dynamodb-backups/lib';
2 |
3 |
4 | export const handler = dynamodbAutoBackups;
5 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/examples/serverless-example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "targets": {
7 | "node": "8.10"
8 | }
9 | }
10 | ]
11 | ]
12 | }
--------------------------------------------------------------------------------
/.idea/jsLibraryMappings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "14"
4 | - "12"
5 | - "10"
6 | cache:
7 | directories:
8 | - node_modules
9 | install:
10 | - yarn install
11 | before_script:
12 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
13 | - chmod +x ./cc-test-reporter
14 | - ./cc-test-reporter before-build
15 | script:
16 | - yarn run test:once
17 | after_script:
18 | - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
19 |
--------------------------------------------------------------------------------
/.idea/serverless-plugin-db-backups.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/helpers/iamRole.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {
3 | Effect: 'Allow',
4 | Action:
5 | [
6 | 'dynamodb:ListTables',
7 | 'dynamodb:ListBackups',
8 | 'dynamodb:DeleteBackup',
9 | ],
10 | Resource: '*',
11 | },
12 | {
13 | Effect: 'Allow',
14 | Action: ['dynamodb:CreateBackup'],
15 | Resource: {
16 | 'Fn::Join': [
17 | ':', [
18 | 'arn:aws:dynamodb',
19 | { Ref: 'AWS::Region' },
20 | { Ref: 'AWS::AccountId' },
21 | 'table/*',
22 | ],
23 | ],
24 | },
25 | },
26 | ];
27 |
--------------------------------------------------------------------------------
/examples/serverless-example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const slsw = require('serverless-webpack');
2 | const nodeExternals = require('webpack-node-externals');
3 |
4 | module.exports = {
5 | entry: slsw.lib.entries,
6 | target: 'node',
7 | mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
8 | externals: [nodeExternals()],
9 | module: {
10 | rules: [
11 | {
12 | test: /\.js$/,
13 | exclude: /node_modules/,
14 | include: __dirname,
15 | use: [
16 | {
17 | loader: 'babel-loader',
18 | },
19 | ],
20 | },
21 | ],
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | parserOptions:
2 | ecmaVersion: 2017
3 | extends: [ 'airbnb-base' ]
4 | rules:
5 | semi: [ "error", "always"]
6 | quotes: [ "error", "single"]
7 | comma-spacing: [ "error", { before: false, after: true }]
8 | indent: [ 'error', 2 ]
9 | arrow-parens: [ "error", "always"]
10 | max-len: [ 'warn', 160 ]
11 | strict: off
12 | no-console: off
13 | no-unused-vars: off
14 | import/prefer-default-export: false
15 | import/no-extraneous-dependencies: ["error", {"peerDependencies": true}] # XXX https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-extraneous-dependencies.md
16 | env:
17 | jest: true
18 | node: true
19 | es6: true
20 |
21 |
--------------------------------------------------------------------------------
/examples/serverless-example/README.md:
--------------------------------------------------------------------------------
1 | # serverless-plugin-dynamodb-backups-serverless-example
2 |
3 | ## AWS Credentials with serverless
4 |
5 | If you already have a proper Serverless environment configured, then you can skip this.
6 |
7 | See https://serverless.com/framework/docs/providers/aws/guide/credentials/
8 |
9 | ## Usage
10 |
11 | 1. Clone this repository
12 | 1. `yarn install`
13 | 1. Update the `serverless.yml` and look out for `TODO`, that's where you're likely to have things to change to match your serverless configuration
14 | 1. `yarn run deploy`
15 | 1. `yarn run logs:backups` will display the logs when a backup is made
16 | 1. `yarn run invoke:listBackups` will display the list of backups that have been made
17 |
18 | > Don't forget to `sls remove` your stack once you've done playing with it, or it's gonna make backups indefinitely!
19 |
--------------------------------------------------------------------------------
/examples/serverless-example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "serverless-plugin-db-examples",
3 | "version": "1.0.0",
4 | "description": "Usage example of the serverless-plugin-dynamodb-backups plugin",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "sls offline start",
8 | "deploy": "sls deploy",
9 | "invoke:listBackups": "sls invoke -f listBackups -l",
10 | "logs:backups": "sls logs -f example-dynamodbAutoBackups -t"
11 | },
12 | "author": "unlyEd",
13 | "license": "MIT",
14 | "devDependencies": {
15 | "aws-sdk": "2.918.0",
16 | "@babel/core": "7.14.3",
17 | "babel-loader": "8.2.2",
18 | "@babel/preset-env": "7.14.4",
19 | "serverless": "2.43.1",
20 | "serverless-offline": "7.0.0",
21 | "serverless-webpack": "5.5.0",
22 | "webpack": "5.38.1",
23 | "webpack-node-externals": "3.0.0"
24 | },
25 | "dependencies": {
26 | "@unly/serverless-plugin-dynamodb-backups": "1.2.0-alpha.1",
27 | "lodash.filter": "4.6.0",
28 | "moment": "2.29.1"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/examples/serverless-example/src/listBackups.js:
--------------------------------------------------------------------------------
1 | const filter = require('lodash.filter');
2 | const moment = require('moment');
3 | const AWS = require('aws-sdk');
4 |
5 | /**
6 | * @param TableName
7 | * @returns {Promise<*|Array>}
8 | */
9 | const listBackups = async (TableName) => {
10 | const Dynamodb = new AWS.DynamoDB({ apiVersion: '2012-08-10' });
11 | const TimeRangeLowerBound = moment().subtract(2, 'days').toISOString();
12 | const params = {
13 | TableName,
14 | BackupType: 'USER',
15 | TimeRangeLowerBound,
16 | };
17 | const data = await Dynamodb.listBackups(params).promise();
18 |
19 | return data.BackupSummaries || [];
20 | };
21 |
22 | export const handler = async (event, context) => {
23 | const data = await listBackups(process.env.TABLE_NAME);
24 |
25 | const backupsFiltered = filter(data, ['BackupType', 'USER']);
26 |
27 | return {
28 | statusCode: 200,
29 | body: JSON.stringify({ backupsFiltered }),
30 | headers: {
31 | 'Access-Control-Allow-Origin': '*',
32 | 'Access-Control-Allow-Credentials': true,
33 | },
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 UnlyEd
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/test/__mocks__/serverlessMock.js:
--------------------------------------------------------------------------------
1 | const Serverless = require('serverless');
2 |
3 | const scenarios = {
4 | classic: {
5 | dynamodbAutoBackups: {
6 | backupRate: 'rate(5 minutes)',
7 | source:
8 | 'src/backups.handler',
9 | },
10 | },
11 | empty: {
12 | dynamodbAutoBackups: {}
13 | ,
14 | },
15 | missed: {
16 | dynamodbAutoBackups: {
17 | source: 'src/backups.handler',
18 | backupRemovalEnabled:
19 | true,
20 | }
21 | ,
22 | },
23 | all: {
24 | dynamodbAutoBackups: {
25 | backupRate: 'rate(5 minutes)',
26 | source:
27 | 'src/backups.handler',
28 | backupRemovalEnabled:
29 | true,
30 | backupRetentionDays:
31 | 15,
32 | }
33 | ,
34 | },
35 | disabled: {
36 | dynamodbAutoBackups: {
37 | backupRate: 'rate(5 minutes)',
38 | source:
39 | 'src/backups.handler',
40 | backupRemovalEnabled:
41 | true,
42 | backupRetentionDays:
43 | 15,
44 | active: false,
45 | },
46 | },
47 | };
48 |
49 | const serverless = (scenarioName) => {
50 | const sls = new Serverless();
51 | sls.service.custom = scenarios[scenarioName];
52 | return sls;
53 | };
54 |
55 | module.exports = serverless;
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@unly/serverless-plugin-dynamodb-backups",
3 | "version": "2.0.0-alpha1",
4 | "description": "Serverless plugin - Automatically creates DynamoDB backups",
5 | "main": "serverless-plugin-db-backups.js",
6 | "repository": "https://github.com/UnlyEd/serverless-plugin-db-backups.git",
7 | "bugs": {
8 | "url": "https://github.com/UnlyEd/serverless-plugin-db-backups/issues"
9 | },
10 | "homepage": "https://github.com/UnlyEd/serverless-plugin-db-backups",
11 | "keywords": [
12 | "serverless",
13 | "framework",
14 | "plugin",
15 | "backups",
16 | "aws",
17 | "dynamodb"
18 | ],
19 | "jest": {
20 | "verbose": true,
21 | "testEnvironment": "node",
22 | "collectCoverage": true,
23 | "coverageReporters": [
24 | "lcov"
25 | ]
26 | },
27 | "scripts": {
28 | "lint": "eslint . --cache --fix",
29 | "test:once": "jest",
30 | "test": "jest --watch",
31 | "coverage": "jest --coverage",
32 | "release:alpha": "npm publish --tag alpha --access=public",
33 | "release:latest": "npm publish --tag latest --access=public"
34 | },
35 | "author": "unlyEd",
36 | "license": "MIT",
37 | "dependencies": {
38 | "axios": "0.21.1",
39 | "bluebird": "3.5.3",
40 | "chalk": "2.4.1",
41 | "lodash.assign": "4.2.0",
42 | "lodash.clone": "4.5.0",
43 | "lodash.filter": "4.6.0",
44 | "lodash.has": "4.5.2",
45 | "lodash.isequal": "4.5.0",
46 | "lodash.isplainobject": "4.0.6",
47 | "lodash.isstring": "4.0.1",
48 | "lodash.set": "4.3.2",
49 | "lodash.uniqwith": "4.5.0",
50 | "moment": "2.22.2",
51 | "semver": "5.6.0"
52 | },
53 | "devDependencies": {
54 | "aws-sdk": "2.352.0",
55 | "@babel/cli": "7.14.3",
56 | "@babel/core": "7.14.3",
57 | "babel-preset-env": "1.7.0",
58 | "eslint": "4.19.1",
59 | "eslint-config-airbnb-base": "13.1.0",
60 | "eslint-plugin-import": "2.14",
61 | "jest": "27.0.3",
62 | "serverless": "2.43.1"
63 | },
64 | "peerDependencies": {
65 | "aws-sdk": ">= 2.x.x"
66 | },
67 | "files": [
68 | "helpers",
69 | "serverless-plugin-db-backups",
70 | "lib",
71 | "README"
72 | ]
73 | }
74 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## [Unreleased](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/tree/HEAD)
4 |
5 | [Full Changelog](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/compare/v1.1.1...HEAD)
6 |
7 | **Merged pull requests:**
8 |
9 | - V1.1.1 issue variables [\#4](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/pull/4) ([Vadorequest](https://github.com/Vadorequest))
10 |
11 | ## [v1.1.1](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/tree/v1.1.1) (2018-12-14)
12 | [Full Changelog](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/compare/v1.1.0...v1.1.1)
13 |
14 | **Merged pull requests:**
15 |
16 | - V1.1.0 toogle active plugin option [\#3](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/pull/3) ([Fukoyamashisu](https://github.com/Fukoyamashisu))
17 |
18 | ## [v1.1.0](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/tree/v1.1.0) (2018-12-11)
19 | [Full Changelog](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/compare/v1.0.1...v1.1.0)
20 |
21 | **Merged pull requests:**
22 |
23 | - Releasev1.0.1 [\#2](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/pull/2) ([Fukoyamashisu](https://github.com/Fukoyamashisu))
24 |
25 | ## [v1.0.1](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/tree/v1.0.1) (2018-12-06)
26 | [Full Changelog](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/compare/v1.0.0...v1.0.1)
27 |
28 | **Merged pull requests:**
29 |
30 | - V1 review [\#1](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/pull/1) ([Vadorequest](https://github.com/Vadorequest))
31 |
32 | ## [v1.0.0](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/tree/v1.0.0) (2018-12-05)
33 | [Full Changelog](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/compare/v1.0.0-alpha.7...v1.0.0)
34 |
35 | ## [v1.0.0-alpha.7](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/tree/v1.0.0-alpha.7) (2018-12-05)
36 | [Full Changelog](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/compare/v1.0.0-alpha.1...v1.0.0-alpha.7)
37 |
38 | ## [v1.0.0-alpha.1](https://github.com/UnlyEd/serverless-plugin-dynamodb-backups/tree/v1.0.0-alpha.1) (2018-12-05)
39 |
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/webstorm
2 |
3 | ### WebStorm ###
4 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
5 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
6 |
7 | # User-specific stuff
8 | .idea/**/workspace.xml
9 | .idea/**/tasks.xml
10 | .idea/**/usage.statistics.xml
11 | .idea/**/dictionaries
12 | .idea/**/shelf
13 |
14 | # Sensitive or high-churn files
15 | .idea/**/dataSources/
16 | .idea/**/dataSources.ids
17 | .idea/**/dataSources.local.xml
18 | .idea/**/sqlDataSources.xml
19 | .idea/**/dynamic.xml
20 | .idea/**/uiDesigner.xml
21 | .idea/**/dbnavigator.xml
22 |
23 | # Gradle
24 | .idea/**/gradle.xml
25 | .idea/**/libraries
26 |
27 | # Gradle and Maven with auto-import
28 | # When using Gradle or Maven with auto-import, you should exclude module files,
29 | # since they will be recreated, and may cause churn. Uncomment if using
30 | # auto-import.
31 | # .idea/modules.xml
32 | # .idea/*.iml
33 | # .idea/modules
34 |
35 | # CMake
36 | cmake-build-*/
37 |
38 | # Mongo Explorer plugin
39 | .idea/**/mongoSettings.xml
40 |
41 | # File-based project format
42 | *.iws
43 |
44 | # IntelliJ
45 | out/
46 |
47 | # mpeltonen/sbt-idea plugin
48 | .idea_modules/
49 |
50 | # JIRA plugin
51 | atlassian-ide-plugin.xml
52 |
53 | # Cursive Clojure plugin
54 | .idea/replstate.xml
55 |
56 | # Crashlytics plugin (for Android Studio and IntelliJ)
57 | com_crashlytics_export_strings.xml
58 | crashlytics.properties
59 | crashlytics-build.properties
60 | fabric.properties
61 |
62 | # Editor-based Rest Client
63 | .idea/httpRequests
64 |
65 | ### WebStorm Patch ###
66 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
67 |
68 | # *.iml
69 | # modules.xml
70 | # .idea/misc.xml
71 | # *.ipr
72 |
73 | # Sonarlint plugin
74 | .idea/sonarlint
75 |
76 |
77 | # End of https://www.gitignore.io/api/webstorm
78 |
79 | ######################### CUSTOM/MANUAL #############################
80 |
81 | # See https://help.github.com/ignore-files/ for more about ignoring files.
82 |
83 | # package directories
84 | node_modules
85 | jspm_packages
86 |
87 | # Forgotten tmp files
88 | yarn-error.log
89 |
90 | # Serverless directories
91 | .serverless
92 | .webpack
93 | .next
94 | dist
95 | .DS_Store
96 | .sls-simulate-registry
97 | coverage
98 |
99 | # Optional eslint cache
100 | .eslintcache
101 |
102 | local-settings.js
103 |
--------------------------------------------------------------------------------
/examples/serverless-example/serverless.yml:
--------------------------------------------------------------------------------
1 | # For full config options, check the docs:
2 | # docs.serverless.com
3 |
4 | service: example-dynamodbAutoBackups
5 |
6 | frameworkVersion: "2"
7 | configValidationMode: error
8 |
9 | plugins:
10 | - '@unly/serverless-plugin-dynamodb-backups' # Must be first, even before "serverless-webpack", see https://github.com/UnlyEd/serverless-plugin-dynamodb-backups
11 | - serverless-webpack # Must be second, see https://github.com/99xt/serverless-dynamodb-local#using-with-serverless-offline-and-serverless-webpack-plugin
12 | - serverless-offline # See https://github.com/dherault/serverless-offline
13 |
14 | custom:
15 | dynamodbAutoBackups: # @unly/serverless-plugin-dynamodb-backups configuration (see README for more)
16 | backupRate: rate(1 minute) # Set to 1mn in the example to see the result quickly
17 | source: src/backups.handler
18 | name: ${self:custom.name} # Using the service name as a base name may be a good practice (but it doesn't work at the time, due to a bug on our side)
19 | backupRemovalEnabled: true
20 | backupRetentionDays: 1 # Created backups will be removed the next day
21 | name: example-dynamodbAutoBackups
22 | serverless-offline:
23 | port: 3000
24 | webpack:
25 | webpackConfig: ./webpack.config.js
26 | includeModules: true
27 | packager: yarn
28 |
29 | provider:
30 | name: aws
31 | runtime: nodejs14.x
32 | lambdaHashingVersion: 20201221
33 | stage: development # TODO You may want to change this
34 | region: eu-west-1 # TODO You may want to change this
35 | profile: sandbox # TODO You need to either remove this or use your own profile
36 | environment:
37 | TABLE_NAME: Book # We specify the table name so we can check its backups list using the dedicated endpoint /listBackups
38 |
39 | functions:
40 | listBackups: # Endpoint example to see the list of all backups that have been made
41 | handler: src/listBackups.handler
42 | events:
43 | - http:
44 | method: GET
45 | path: /listBackups
46 |
47 | resources:
48 | Resources:
49 | BookTable: # Create dynamodb table on aws for testing
50 | Type: AWS::DynamoDB::Table # see https://docs.aws.amazon.com/fr_fr/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html
51 | Properties:
52 | TableName: Book
53 | AttributeDefinitions:
54 | - AttributeName: id
55 | AttributeType: S
56 | KeySchema:
57 | - AttributeName: id
58 | KeyType: HASH
59 | ProvisionedThroughput:
60 | ReadCapacityUnits: 1
61 | WriteCapacityUnits: 1
62 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/test/serverless-plugin-db-backups.test.js:
--------------------------------------------------------------------------------
1 | const serverless = require('./__mocks__/serverlessMock');
2 | const DynamodbBackups = require('../serverless-plugin-db-backups');
3 |
4 | describe('@unly/serverless-plugin-dynamodb-backups init', () => {
5 | global.console = {
6 | warn: jest.fn(),
7 | log: jest.fn(),
8 | error: jest.fn(),
9 | };
10 | let slsPlugin;
11 |
12 | test('check if serverless version is >= 1.12', () => {
13 | slsPlugin = new DynamodbBackups(serverless('classic'));
14 | try {
15 | slsPlugin.serverless.version = '1.9.0';
16 | slsPlugin.validate();
17 | } catch (err) {
18 | expect(err.message).toEqual('Serverless version must be >= 1.12.0');
19 | }
20 | });
21 |
22 | test('custom config with no key source should throw error', () => {
23 | slsPlugin = new DynamodbBackups(serverless('empty'));
24 | try {
25 | slsPlugin.checkConfigPlugin();
26 | } catch (err) {
27 | expect(err.message).toEqual('dynamodbAutoBackups source must be set !');
28 | }
29 | });
30 |
31 | test('custom config with no key backupRate should throw error', () => {
32 | slsPlugin = new DynamodbBackups(serverless('missed'));
33 | try {
34 | slsPlugin.checkConfigPlugin();
35 | } catch (err) {
36 | expect(err.message).toEqual('dynamodbAutoBackups backupRate must be set !');
37 | }
38 | });
39 |
40 | test('custom config with key backupRemovalEnabled === true and no key backupRetentionDays should throw error', () => {
41 | try {
42 | slsPlugin.checkConfigPlugin();
43 | } catch (err) {
44 | expect(err.message).toEqual('if backupRemovalEnabled, backupRetentionDays must be set !');
45 | }
46 | });
47 |
48 | test('should provide function dynamodbAutoBackups', () => {
49 | slsPlugin = new DynamodbBackups(serverless('all'));
50 |
51 | const afterPackageInit = slsPlugin.hooks['after:package:initialize'];
52 |
53 | afterPackageInit().then(() => {
54 | expect(slsPlugin.functionBackup).toHaveProperty('name');
55 | expect(slsPlugin.functionBackup).toHaveProperty('events');
56 | expect(slsPlugin.functionBackup).toHaveProperty('handler');
57 | expect(slsPlugin.functionBackup).toHaveProperty('environment');
58 | expect(slsPlugin.serverless.service.provider.iamRoleStatements.length).toEqual(2);
59 | expect(slsPlugin.serverless.service.functions.dynamodbAutoBackups).toMatchObject(slsPlugin.functionBackup);
60 | });
61 | });
62 |
63 | test('if iamRoleStatements in serverles should merge it', () => {
64 | slsPlugin = new DynamodbBackups(serverless('all'));
65 | slsPlugin.serverless.service.provider.iamRoleStatements = [
66 | {
67 | Effect: 'Allow',
68 | Action:
69 | [
70 | 'dynamodb:ListTables',
71 | 'dynamodb:ListBackups',
72 | 'dynamodb:DeleteBackup',
73 | ],
74 | Resource: '*',
75 | },
76 | ];
77 |
78 | const afterPackageInit = slsPlugin.hooks['after:package:initialize'];
79 |
80 | afterPackageInit().then(() => {
81 | expect(slsPlugin.functionBackup).toHaveProperty('name');
82 | expect(slsPlugin.functionBackup).toHaveProperty('events');
83 | expect(slsPlugin.functionBackup).toHaveProperty('handler');
84 | expect(slsPlugin.functionBackup).toHaveProperty('environment');
85 | expect(slsPlugin.serverless.service.provider.iamRoleStatements.length).toEqual(3);
86 | expect(global.console.log).toHaveBeenCalled();
87 | });
88 | });
89 |
90 | test('if iamRoleStatements in serverles should merge it', () => {
91 | slsPlugin = new DynamodbBackups(serverless('all'));
92 | slsPlugin.serverless.service.provider.iamRoleStatements = [
93 | {
94 | Effect: 'Allow',
95 | Action:
96 | [
97 | 'dynamodb:ListTables',
98 | 'dynamodb:ListBackups',
99 | 'dynamodb:DeleteBackup',
100 | ],
101 | Resource: '*',
102 | },
103 | ];
104 |
105 | const afterPackageInit = slsPlugin.hooks['after:package:initialize'];
106 |
107 | afterPackageInit().then(() => {
108 | expect(slsPlugin.functionBackup).toHaveProperty('name');
109 | expect(slsPlugin.functionBackup).toHaveProperty('events');
110 | expect(slsPlugin.functionBackup).toHaveProperty('handler');
111 | expect(slsPlugin.functionBackup).toHaveProperty('environment');
112 | expect(slsPlugin.serverless.service.provider.iamRoleStatements.length).toEqual(3);
113 | expect(global.console.log).toHaveBeenCalled();
114 | });
115 | });
116 |
117 | test('should disabled plugin and not provide function dynamodbAutoBackups', () => {
118 | slsPlugin = new DynamodbBackups(serverless('disabled'));
119 |
120 | const afterPackageInit = slsPlugin.hooks['after:package:initialize'];
121 |
122 | afterPackageInit().then(() => {
123 | expect(slsPlugin.serverless.service.functions.dynamodbAutoBackups).toBeUndefined();
124 | });
125 | });
126 | });
127 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | const AWS = require('aws-sdk');
2 | const filter = require('lodash.filter');
3 | const moment = require('moment');
4 | const axios = require('axios');
5 |
6 | const Dynamodb = new AWS.DynamoDB({ apiVersion: '2012-08-10' });
7 | const {
8 | SLACK_WEBHOOK,
9 | REGION,
10 | BACKUP_RETENTION_DAYS,
11 | BACKUP_REMOVAL_ENABLED,
12 | TABLE_REGEX,
13 | } = process.env;
14 | const CONSOLE_ENDPOINT = `https://console.aws.amazon.com/dynamodb/home?region=${REGION}#backups:`;
15 |
16 | /**
17 | *
18 | * @param message
19 | */
20 | const log = (message) => {
21 | console.log('@unly/serverless-plugin-dynamodb-backups:', message);
22 | };
23 |
24 | /**
25 | *
26 | * @param TableName
27 | * @returns {Promise>}
28 | */
29 | const createBackup = (TableName) => {
30 | const BackupName = TableName + moment().format('_YYYY_MM_DD_HH-mm-ss');
31 | const params = {
32 | BackupName,
33 | TableName,
34 | };
35 | return Dynamodb.createBackup(params).promise();
36 | };
37 |
38 | /**
39 | *
40 | * @returns {Promise<*>}
41 | */
42 | const getTablesToBackup = async () => {
43 | // Default return all of the tables associated with the current AWS account and endpoint.
44 | const data = await Dynamodb.listTables({}).promise();
45 |
46 | // If TABLE_REGEX environment variable is provide, return tables that match;
47 | return process.env.TABLE_REGEX ? filter(data.TableNames, (val) => new RegExp(TABLE_REGEX).test(val)) : data.TableNames;
48 | };
49 |
50 | /**
51 | *
52 | * @param TableName
53 | * @returns {Promise}
54 | */
55 | const listBackups = async (TableName) => {
56 | let list;
57 | const TimeRangeUpperBound = moment().subtract(BACKUP_RETENTION_DAYS, 'days').toISOString();
58 | const params = {
59 | TableName,
60 | BackupType: process.env.BACKUP_TYPE || 'ALL',
61 | TimeRangeUpperBound,
62 | };
63 |
64 | try {
65 | list = await Dynamodb.listBackups(params).promise();
66 | } catch (err) {
67 | log(`Error could not list backups on table ${TableName} \n ${JSON.stringify(err)}`);
68 | }
69 |
70 | return list.BackupSummaries || [];
71 | };
72 |
73 | /**
74 | *
75 | * @param tables
76 | * @returns {Promise<{success: Array, failure: Array} | never | void>}
77 | */
78 | const removeStaleBackup = async (tables) => {
79 | log(`Removing backups before the following date: ${moment().subtract(BACKUP_RETENTION_DAYS, 'days').format('LL')}`);
80 |
81 | const backupSummaries = tables.map((tableName) => listBackups(tableName));
82 |
83 | const resolveBackupSummaries = await Promise.all(backupSummaries);
84 |
85 | const deleteBackupsPromise = resolveBackupSummaries.map((backupLogs) => backupLogs.map((backup) => {
86 | const params = {
87 | BackupArn: backup.BackupArn,
88 | };
89 |
90 | return Dynamodb.deleteBackup(params).promise()
91 | .then(({ BackupDescription }) => ({
92 | name: BackupDescription.BackupDetails.BackupName,
93 | deleted: true,
94 | })).catch((err) => ({
95 | deleted: false,
96 | error: err,
97 | }));
98 | }));
99 |
100 | const results = deleteBackupsPromise.reduce((acc, el) => acc.concat(el), []);
101 |
102 | return Promise.all(results).then((res) => {
103 | const success = filter(res, 'deleted');
104 | const failure = filter(res, ['deleted', false]);
105 |
106 | return { success, failure };
107 | }).catch((err) => log(err));
108 | };
109 |
110 | /**
111 | *
112 | * @param results
113 | * @param action
114 | * @returns {string}
115 | */
116 | const formatMessage = (results, action = 'create') => {
117 | let msg = '';
118 | const success = results.success.map((el) => JSON.stringify(el));
119 | const failure = results.failure.map((el) => JSON.stringify(el));
120 |
121 | if (!success.length && !failure.length) {
122 | return '@unly/serverless-plugin-dynamodb-backups: Tried running DynamoDB backup, but no tables were specified.\nPlease check your configuration.';
123 | }
124 |
125 | if (failure.length) {
126 | msg += '\n@unly/serverless-plugin-dynamodb-backups:\n';
127 | msg += `\nThe following tables failed:\n - ${failure.join('\n - ')}`;
128 | msg += `\n\nTried to ${action} ${success.length + failure.length} backups. ${success.length} succeeded, and ${failure.length} failed.
129 | See all backups${`<${CONSOLE_ENDPOINT}|here>`}.`;
130 | }
131 |
132 | return msg;
133 | };
134 |
135 | /**
136 | *
137 | * @param message
138 | * @returns {Promise}
139 | */
140 | const sendToSlack = (message) => axios.post(SLACK_WEBHOOK, { text: message });
141 |
142 | /**
143 | *
144 | * @param tables
145 | * @returns {Promise<{success: Array, failure: Array} | never | void>}
146 | */
147 | const tablesToBackup = async (tables) => {
148 | const promises = tables.map(async (tableName) => createBackup(tableName).then(({ BackupDetails }) => ({
149 | name: BackupDetails.BackupName,
150 | created: true,
151 | status: BackupDetails.BackupStatus,
152 | })).catch((err) => ({
153 | created: false,
154 | error: err,
155 | })));
156 | return Promise.all(promises).then((res) => {
157 | const success = filter(res, 'created');
158 | const failure = filter(res, ['created', false]);
159 |
160 | return { success, failure };
161 | }).catch((err) => log(err));
162 | };
163 |
164 | const dynamodbAutoBackups = async (event, context) => {
165 | let removeStaleBackupResults = null;
166 |
167 | try {
168 | const tables = await getTablesToBackup();
169 |
170 | if (BACKUP_REMOVAL_ENABLED === 'true') {
171 | try {
172 | removeStaleBackupResults = await removeStaleBackup(tables);
173 | console.log(removeStaleBackupResults);
174 | } catch (err) {
175 | log(`Error removing stale backups. Error: ${JSON.stringify(err)}`);
176 | }
177 | }
178 |
179 | const tablesToBackupResults = await tablesToBackup(tables);
180 |
181 | if (SLACK_WEBHOOK && tablesToBackupResults.failure.length) {
182 | const message = formatMessage(tablesToBackupResults);
183 | await sendToSlack(message);
184 | } else if (SLACK_WEBHOOK && removeStaleBackupResults !== null) {
185 | if (removeStaleBackupResults.failure.length) {
186 | const message = formatMessage(removeStaleBackupResults, 'remove');
187 | await sendToSlack(message);
188 | }
189 | }
190 |
191 | log(tablesToBackupResults);
192 |
193 | if (BACKUP_REMOVAL_ENABLED === 'true' && removeStaleBackupResults !== null) {
194 | log(removeStaleBackupResults);
195 | }
196 | } catch (err) {
197 | log(err);
198 | }
199 | };
200 |
201 | module.exports = dynamodbAutoBackups;
202 |
--------------------------------------------------------------------------------
/serverless-plugin-db-backups.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const isString = require('lodash.isstring');
4 | const clone = require('lodash.clone');
5 | const has = require('lodash.has');
6 | const isPlainObject = require('lodash.isplainobject');
7 | const set = require('lodash.set');
8 | const assign = require('lodash.assign');
9 | const uniqWith = require('lodash.uniqwith');
10 | const isEqual = require('lodash.isequal');
11 | const BbPromise = require('bluebird');
12 | const SemVer = require('semver');
13 | const chalk = require('chalk');
14 |
15 | const hooks = [
16 | 'after:package:initialize',
17 | 'before:deploy:deploy',
18 | 'before:invoke:local:invoke',
19 | 'before:offline:start:init',
20 | 'before:remove:remove',
21 | 'before:logs:logs',
22 | ];
23 |
24 | // iamRole Helpers
25 | const iamRoleStatements = require('./helpers/iamRole');
26 |
27 | /**
28 | *
29 | */
30 | class DynamodbAutoBackup {
31 | constructor(serverless) {
32 | this.serverless = serverless;
33 |
34 | this.custom = this.serverless.service.custom;
35 |
36 | this.dynamodbAutoBackups = {};
37 |
38 | this.isInstantiate = false;
39 |
40 | this.chainPromises = () => BbPromise.bind(this)
41 | .then(this.validate)
42 | .then(this.checkConfigPlugin)
43 | .then(this.populateEnv)
44 | .then(this.setCronEvent)
45 | .then(this.generateBackupFunction)
46 | .then(this.manageIamRole);
47 |
48 | this.hooks = hooks.reduce((initialValue, hook) => assign(initialValue, { [hook]: () => this.init() }), {});
49 | }
50 |
51 | /**
52 | * Validate dynamodbAutoBackups options
53 | * @returns {Promise.resolve}
54 | */
55 | checkConfigPlugin() {
56 | if (!this.dynamodbAutoBackups.source) {
57 | return BbPromise.reject(new this.serverless.classes.Error('dynamodbAutoBackups source must be set !'));
58 | }
59 |
60 | if (!this.dynamodbAutoBackups.backupRate) {
61 | return BbPromise.reject(new this.serverless.classes.Error('dynamodbAutoBackups backupRate must be set !'));
62 | }
63 |
64 | if (has(this.dynamodbAutoBackups, 'backupRemovalEnabled') && !has(this.dynamodbAutoBackups, 'backupRetentionDays')) {
65 | return BbPromise.reject(
66 | new this.serverless.classes.Error('if backupRemovalEnabled, backupRetentionDays must be set !'),
67 | );
68 | }
69 |
70 | if (!has(this.dynamodbAutoBackups, 'slackWebhook') && this.dynamodbAutoBackups.active) {
71 | DynamodbAutoBackup.consoleLog('@unly/serverless-plugin-dynamodb-backups: -----------------------------------------------------------');
72 | DynamodbAutoBackup.consoleLog(' Warning: slackWebhook is not provide, you will not be notified of errors !');
73 | console.log();
74 | }
75 |
76 | this.functionBackup = {
77 | name: this.dynamodbAutoBackups.name || 'dynamodbAutoBackups',
78 | handler: this.dynamodbAutoBackups.source,
79 | events: [],
80 | environment: {},
81 | };
82 |
83 | return BbPromise.resolve();
84 | }
85 |
86 | /**
87 | *
88 | * check the compatibility of serverless version
89 | * @returns {Promise.resolve}
90 | */
91 | validate() {
92 | // Check required serverless version
93 | if (SemVer.gt('1.12.0', this.serverless.getVersion())) {
94 | return BbPromise.reject(new this.serverless.classes.Error('Serverless version must be >= 1.12.0'));
95 | }
96 |
97 | return BbPromise.resolve();
98 | }
99 |
100 | /**
101 | * add the schedule event
102 | * @returns {Promise.resolve}
103 | */
104 | setCronEvent() {
105 | const events = [];
106 |
107 | if (isString(this.dynamodbAutoBackups.backupRate)) {
108 | const cron = {
109 | schedule: this.dynamodbAutoBackups.backupRate,
110 | };
111 | events.push(cron);
112 | }
113 |
114 | console.log(this.functionBackup);
115 | this.functionBackup.events = events;
116 |
117 | return BbPromise.resolve();
118 | }
119 |
120 | /**
121 | * Check config for variables and set them to dynamodbBackup function
122 | * @returns {Promise.resolve}
123 | */
124 | populateEnv() {
125 | // Environment variables have to be a string in order to be processed properly
126 | if (has(this.dynamodbAutoBackups, 'backupRemovalEnabled') && has(this.dynamodbAutoBackups, 'backupRetentionDays')) {
127 | set(this.functionBackup, 'environment.BACKUP_REMOVAL_ENABLED', String(this.dynamodbAutoBackups.backupRemovalEnabled));
128 | set(this.functionBackup, 'environment.BACKUP_RETENTION_DAYS', String(this.dynamodbAutoBackups.backupRetentionDays));
129 | }
130 | if (has(this.dynamodbAutoBackups, 'slackWebhook')) {
131 | set(this.functionBackup, 'environment.SLACK_WEBHOOK', String(this.dynamodbAutoBackups.slackWebhook));
132 | }
133 | if (has(this.dynamodbAutoBackups, 'backupType')) {
134 | set(this.functionBackup, 'environment.BACKUP_TYPE', String(this.dynamodbAutoBackups.backupType).toUpperCase());
135 | }
136 | if (has(this.dynamodbAutoBackups, 'tableRegex')) {
137 | set(this.functionBackup, 'environment.TABLE_REGEX', String(this.dynamodbAutoBackups.tableRegex));
138 | }
139 | return BbPromise.resolve();
140 | }
141 |
142 | /**
143 | * Assign dynamodbBackup function to serverless service
144 | * @returns {Promise.resolve}
145 | */
146 | generateBackupFunction() {
147 | const dynamodbAutoBackups = clone(this.functionBackup);
148 | dynamodbAutoBackups.events = uniqWith(this.functionBackup.events, isEqual);
149 |
150 | if (isPlainObject(dynamodbAutoBackups)) {
151 | assign(this.serverless.service.functions, { [this.dynamodbAutoBackups.name || 'dynamodbAutoBackups']: dynamodbAutoBackups });
152 | }
153 |
154 | DynamodbAutoBackup.consoleLog('@unly/serverless-plugin-dynamodb-backups: -----------------------------------------------------------');
155 | DynamodbAutoBackup.consoleLog(` function: ${this.functionBackup.name}`);
156 | console.log();
157 | return BbPromise.resolve();
158 | }
159 |
160 | /**
161 | * Provide iam access
162 | * @returns {Promise.resolve}
163 | */
164 | manageIamRole() {
165 | if (this.serverless.service.provider.iamRoleStatements) {
166 | iamRoleStatements.map((role) => this.serverless.service.provider.iamRoleStatements.push(role));
167 | } else {
168 | this.serverless.service.provider.iamRoleStatements = iamRoleStatements;
169 | }
170 | this.isInstantiate = true;
171 | return BbPromise.resolve();
172 | }
173 |
174 | /**
175 | * check if an instance of the class is already running
176 | * @returns {function}
177 | */
178 | isAlreadyInInstance() {
179 | if (this.isInstantiate) {
180 | return BbPromise.resolve();
181 | }
182 | return this.isActivated(this.chainPromises);
183 | }
184 |
185 | /**
186 | * check if the plugin config is activated
187 | * Default to true
188 | * @returns {function}
189 | */
190 | isActivated(cb) {
191 | DynamodbAutoBackup.consoleLog(`@unly/serverless-plugin-dynamodb-backups is ${this.dynamodbAutoBackups.active ? 'enabled' : 'disabled'}`);
192 | console.log();
193 |
194 | if (!this.dynamodbAutoBackups.active) {
195 | return BbPromise.resolve();
196 | }
197 | return cb();
198 | }
199 |
200 | /**
201 | * check if dynamodbAutoBackups options is provide
202 | * @returns {function}
203 | */
204 | init() {
205 | // if no dynamodbAutoBackups key at custom in serverless.yml or invalid format (throw error)
206 | if (!has(this.custom, 'dynamodbAutoBackups') || !isPlainObject(this.custom.dynamodbAutoBackups)) {
207 | return BbPromise.reject(
208 | new this.serverless.classes.Error('Invalid configuration, see https://www.npmjs.com/package/@unly/serverless-plugin-dynamodb-backups'),
209 | );
210 | }
211 |
212 | assign(this.dynamodbAutoBackups, this.custom.dynamodbAutoBackups);
213 |
214 | // if dynamodbAutoBackups.active is not provide, default value to true
215 | if (!has(this.dynamodbAutoBackups, 'active')) {
216 | set(this.dynamodbAutoBackups, 'active', true);
217 | }
218 |
219 | return this.isAlreadyInInstance();
220 | }
221 |
222 | /**
223 | *
224 | * @param message
225 | */
226 | static consoleLog(message) {
227 | console.log(chalk.yellow.bold(message));
228 | }
229 | }
230 |
231 | module.exports = DynamodbAutoBackup;
232 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | [](https://travis-ci.com/UnlyEd/serverless-plugin-dynamodb-backups)
3 | [](https://codeclimate.com/github/UnlyEd/serverless-plugin-dynamodb-backups/maintainability)
4 | [](https://codeclimate.com/github/UnlyEd/serverless-plugin-dynamodb-backups/test_coverage)
5 | [](https://snyk.io/test/github/UnlyEd/serverless-plugin-dynamodb-backups?targetFile=package.json)
6 |
7 | # Serverless plugin DynamoDB backups
8 |
9 | ## Introduction
10 |
11 | > If you want to automate your AWS DynamoDB database backups, this plugin may be what you need.
12 |
13 | As we build various services on AWS using the "serverless" design, we need reusable backups services, both scalable and easy to implement.
14 | We therefore created this plugin, to make sure that each project can create its own DynamoDB automated backup solution.
15 |
16 | This is a plugin which simplifies **DynamoDB backups** creation automation for all the resources created in
17 | `serverless.yml` when using the [Serverless Framework](https://serverless.com) and AWS Cloud provider.
18 |
19 |
20 | This plugin officially supports Node.js **12.x**, **14.x** and **16.x**.
21 |
22 | This plugin officially supports Serverless Framework `>=2.0.0`.
23 |
24 | ## Benefits
25 |
26 | * Automated Backups on your configured resources (`serverless.yml`)
27 | * Report Error on slack channel _(see configuration)_
28 | * Delete old Backups automatically (AKA "managed backups retention") _(see configuration)_
29 |
30 | ## Installation
31 |
32 | Install the plugin using either Yarn or NPM. (we use Yarn)
33 |
34 | NPM:
35 | ```bash
36 | npm install @unly/serverless-plugin-dynamodb-backups
37 | ```
38 |
39 | YARN:
40 | ```bash
41 | yarn add @unly/serverless-plugin-dynamodb-backups
42 | ```
43 |
44 | ## Usage
45 |
46 | ### Step 1: Load the Plugin
47 |
48 | The plugin determines your environment during deployment and adds all environment variables to your Lambda function.
49 | All you need to do is to load the plugin:
50 |
51 | > Must be declared **before** `serverless-webpack`, despite what their officially doc says
52 |
53 | ```yaml
54 | plugins:
55 | - '@unly/serverless-plugin-dynamodb-backups' # Must be first, even before "serverless-webpack", see https://github.com/UnlyEd/serverless-plugin-dynamodb-backups
56 | - serverless-webpack # Must be second, see https://github.com/99xt/serverless-dynamodb-local#using-with-serverless-offline-and-serverless-webpack-plugin
57 | ```
58 |
59 | ### Step 2: Create the backups handler function:
60 |
61 | Create a file, which will be called when performing a DynamoDB backup _(we named it `src/backups.js` in our `examples` folder)_:
62 |
63 | ```javascript
64 | import dynamodbAutoBackups from '@unly/serverless-plugin-dynamodb-backups/lib';
65 |
66 | export const handler = dynamodbAutoBackups;
67 | ```
68 |
69 | ### Step 3: Configure your `serverless.yml`
70 |
71 | Set the `dynamodbAutoBackups` object configuration as follows (list of all available options below):
72 |
73 | ```yaml
74 | custom:
75 | dynamodbAutoBackups:
76 | backupRate: rate(40 minutes) # Every 5 minutes, from the time it was deployed
77 | source: src/backups.js # Path to the handler function we created in step #2
78 | active: true
79 | ```
80 |
81 | ## Configuration of `dynamodbAutoBackups` object:
82 |
83 | | Attributes | Type | Required | Default | Description |
84 | |----------------------|---------|----------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
85 | | source | String | True | | Path to your handler function |
86 | | backupRate | String | True | | The schedule on which you want to backup your table. You can use either `rate` syntax (`rate(1 hour)`) or `cron` syntax (`cron(0 12 * * ? *)`). See [here](https://serverless.com/framework/docs/providers/aws/events/schedule/) for more details on configuration. |
87 | | name | String | False | auto | Automatically set, but you could provide your own name for this lambda |
88 | | slackWebhook | String | False | | A HTTPS endpoint for an [incoming webhook](https://api.slack.com/incoming-webhooks) to Slack. If provided, it will send error messages to a Slack channel. |
89 | | backupRemovalEnabled | Boolean | False | false | Enables cleanup of old backups. See the below option "backupRetentionDays" to specify the retention period. By default, backup removal is disabled. |
90 | | backupRetentionDays | Integer | False | | Specify the number of days to retain old backups. For example, setting the value to 2 will remove all backups that are older than 2 days. Required if `backupRemovalEnabled` is `true`. |
91 | | backupType | String | False | "ALL" | * `USER` - On-demand backup created by you. * `SYSTEM` - On-demand backup automatically created by DynamoDB. * `ALL` - All types of on-demand backups (USER and SYSTEM). |
92 | | active | Boolean | False | true | You can disable this plugin, useful to disable the plugin on a non-production environment, for instance |
93 |
94 | _Generated by https://www.tablesgenerator.com/markdown_tables_
95 |
96 | ---
97 |
98 | ### Examples of configurations:
99 |
100 | #### 1. Creates backups every 40 minutes, delete all backups older than 15 days, send slack notifications if backups are not created.
101 |
102 | ```yaml
103 | custom:
104 | dynamodbAutoBackups:
105 | backupRate: rate(40 minutes) # Every 40 minutes, from the time it was deployed
106 | source: src/backups.js
107 | slackWebhook: https://hooks.slack.com/services/T4XHXX5C6/TT3XXXM0J/XXXXXSbhCXXXX77mFBr0ySAm
108 | backupRemovalEnabled: true # Enable backupRetentionDays
109 | backupRetentionDays: 15 # If backupRemovalEnabled is not provided, then backupRetentionDays is not used
110 | ```
111 |
112 | #### 2. Creates some backups every friday at 2:00 am, delete all backups created by USER longer than 3 days, be warned if backups are not created.
113 | ```yaml
114 | custom:
115 | dynamodbAutoBackups:
116 | backupRate: cron(0 2 ? * FRI *) # Every friday at 2:00 am
117 | source: src/backups.js
118 | slackWebhook: https://hooks.slack.com/services/T4XHXX5C6/TT3XXXM0J/XXXXXSbhCXXXX77mFBr0ySAm
119 | backupRemovalEnabled: true # Enable backupRetentionDays
120 | backupRetentionDays: 3 # If backupRemovalEnabled is not provided, then backupRetentionDays is not used
121 | backupType: USER # Delete all backups created by a user, not the system backups
122 | ```
123 |
124 | ### Try it out yourself
125 |
126 | To test this plugin, you can clone this repository.
127 | Go to `examples/serverless-example`, and follow the README.
128 |
129 | ---
130 |
131 | # Vulnerability disclosure
132 |
133 | [See our policy](https://github.com/UnlyEd/Unly).
134 |
135 | ---
136 |
137 | # Contributors and maintainers
138 |
139 | This project is being maintained by:
140 | - [Unly] Ambroise Dhenain ([Vadorequest](https://github.com/vadorequest)) **(active)**
141 |
142 | Thanks to our contributors:
143 | - Anthony Troupenat ([Fukoyamashisu](https://github.com/Fukoyamashisu))
144 |
145 | ---
146 |
147 | # **[ABOUT UNLY]**
148 |
149 | > [Unly](https://unly.org) is a socially responsible company, fighting inequality and facilitating access to higher education.
150 | > Unly is committed to making education more inclusive, through responsible funding for students.
151 | We provide technological solutions to help students find the necessary funding for their studies.
152 |
153 | We proudly participate in many TechForGood initiatives. To support and learn more about our actions to make education accessible, visit :
154 | - https://twitter.com/UnlyEd
155 | - https://www.facebook.com/UnlyEd/
156 | - https://www.linkedin.com/company/unly
157 | - [Interested to work with us?](https://jobs.zenploy.io/unly/about)
158 |
159 | Tech tips and tricks from our CTO on our [Medium page](https://medium.com/unly-org/tech/home)!
160 |
161 | #TECHFORGOOD #EDUCATIONFORALL
162 |
--------------------------------------------------------------------------------