├── Chapter02 ├── env-variable-service │ ├── handler.js │ ├── serverless.env.yml │ └── serverless.yml └── my-serverless-service │ ├── handler.js │ └── serverless.yml ├── Chapter03 ├── New folder │ ├── CreateThumbnails.js │ ├── aws-lambda-dynamodb-mytasks-master │ │ ├── Dockerfile │ │ ├── Jenkinsfile │ │ ├── README.md │ │ ├── package.json │ │ ├── serverless.yml │ │ ├── src │ │ │ ├── mytasks │ │ │ │ ├── create.js │ │ │ │ ├── delete.js │ │ │ │ ├── get.js │ │ │ │ ├── list.js │ │ │ │ └── update.js │ │ │ └── package.json │ │ └── test │ │ │ ├── createDelete.js │ │ │ ├── createListDelete.js │ │ │ ├── createReadDelete.js │ │ │ ├── data │ │ │ ├── newTask1.json │ │ │ └── newTask2.json │ │ │ └── newTodo1.json │ ├── aws-lambda-thumbnail-master │ │ ├── buildspec.yml │ │ ├── cloudformationpolicy.json │ │ ├── index.js │ │ ├── package.json │ │ └── template.yml │ ├── my-canary-deployment-master │ │ ├── handler.js │ │ ├── hooks.js │ │ ├── package.json │ │ └── serverless.yml │ ├── thumbnailEvent │ └── thumbnailEvent (2) ├── README.md ├── package.json ├── serverless.yml ├── src │ ├── mytasks │ │ ├── create.js │ │ ├── delete.js │ │ ├── get.js │ │ ├── list.js │ │ └── update.js │ └── package.json └── test │ ├── createDelete.js │ ├── createListDelete.js │ ├── createReadDelete.js │ ├── data │ ├── newTask1.json │ └── newTask2.json │ └── newTodo1.json ├── Chapter04 ├── Dockerfile ├── New folder │ ├── Dockerfile │ ├── README.md │ ├── coverage │ │ ├── azure-helloworld-ci │ │ │ ├── handler.js.html │ │ │ ├── index.html │ │ │ └── lib │ │ │ │ ├── hello-world.js.html │ │ │ │ └── index.html │ │ ├── base.css │ │ ├── coverage-final.json │ │ ├── index.html │ │ ├── lcov-report │ │ │ ├── azure-helloworld-ci │ │ │ │ ├── handler.js.html │ │ │ │ ├── index.html │ │ │ │ └── lib │ │ │ │ │ ├── hello-world.js.html │ │ │ │ │ └── index.html │ │ │ ├── base.css │ │ │ ├── index.html │ │ │ ├── prettify.css │ │ │ ├── prettify.js │ │ │ ├── sort-arrow-sprite.png │ │ │ └── sorter.js │ │ ├── lcov.info │ │ ├── prettify.css │ │ ├── prettify.js │ │ ├── sort-arrow-sprite.png │ │ └── sorter.js │ ├── handler.js │ ├── lib │ │ └── hello-world.js │ ├── package.json │ ├── serverless.yml │ └── tests │ │ └── hello-world.spec.js ├── README.md ├── coverage │ ├── azure-helloworld-ci │ │ ├── handler.js.html │ │ ├── index.html │ │ └── lib │ │ │ ├── hello-world.js.html │ │ │ └── index.html │ ├── base.css │ ├── coverage-final.json │ ├── index.html │ ├── lcov-report │ │ ├── azure-helloworld-ci │ │ │ ├── handler.js.html │ │ │ ├── index.html │ │ │ └── lib │ │ │ │ ├── hello-world.js.html │ │ │ │ └── index.html │ │ ├── base.css │ │ ├── index.html │ │ ├── prettify.css │ │ ├── prettify.js │ │ ├── sort-arrow-sprite.png │ │ └── sorter.js │ ├── lcov.info │ ├── prettify.css │ ├── prettify.js │ ├── sort-arrow-sprite.png │ └── sorter.js ├── handler.js ├── lib │ └── hello-world.js ├── package-lock.json ├── package.json ├── serverless.yml └── tests │ └── hello-world.spec.js ├── Chapter05 ├── Dockerfile ├── Jenkinsfile ├── New folder │ ├── Dockerfile │ ├── Jenkinsfile │ ├── README.md │ ├── handler.js │ ├── package-lock.json │ ├── package.json │ ├── serverless.yml │ └── test │ │ └── test.js ├── README.md ├── handler.js ├── package-lock.json ├── package.json ├── serverless.yml └── test │ └── test.js ├── Chapter06 ├── Dockerfile ├── Jenkinsfile ├── index.js ├── package.json └── test │ ├── integration.http.test.js │ ├── system.http.test.js │ └── unit.http.test.js ├── Chapter07 ├── README.md ├── handler.py ├── package.json └── serverless.yml ├── LICENSE └── README.md /Chapter02/env-variable-service/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.hello = (event, context, callback) => { 4 | 5 | const response = { 6 | statusCode: 200, 7 | body: JSON.stringify({ 8 | message: `my favourite animal is ${process.env.MY_VAR} and ${process.env.MYSECRET_VAR}`, 9 | input: event, 10 | }), 11 | }; 12 | 13 | callback(null, response); 14 | 15 | // Use this code if you don't use the http event with the LAMBDA-PROXY integration 16 | // callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event }); 17 | }; 18 | -------------------------------------------------------------------------------- /Chapter02/env-variable-service/serverless.env.yml: -------------------------------------------------------------------------------- 1 | dev: 2 | MYSECRET_VAR: 'It is at secret dev den' 3 | prod: 4 | MYSECRET_VAR: 'It is at secret Prod den' 5 | -------------------------------------------------------------------------------- /Chapter02/env-variable-service/serverless.yml: -------------------------------------------------------------------------------- 1 | service: env-variable-service 2 | 3 | # You can pin your service to only deploy with a specific Serverless version 4 | # Check out our docs for more details 5 | # frameworkVersion: "=X.X.X" 6 | 7 | provider: 8 | name: aws 9 | runtime: nodejs6.10 10 | 11 | # you can define service wide environment variables here 12 | environment: 13 | MY_VAR: Lion 14 | # MYSECRET_VAR: ${file(./serverless.env.yml):dev.MYSECRET_VAR} 15 | MYSECRET_VAR: ${file(./serverless.env.yml):${opt:stage}.MYSECRET_VAR} 16 | 17 | functions: 18 | hello: 19 | handler: handler.hello 20 | -------------------------------------------------------------------------------- /Chapter02/my-serverless-service/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.hello = (event, context, callback) => { 4 | const response = { 5 | statusCode: 200, 6 | body: JSON.stringify({ 7 | message: 'My Serverless World', 8 | input: event, 9 | }), 10 | }; 11 | 12 | callback(null, response); 13 | 14 | // Use this code if you don't use the http event with the LAMBDA-PROXY integration 15 | // callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event }); 16 | }; 17 | -------------------------------------------------------------------------------- /Chapter02/my-serverless-service/serverless.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Serverless! 2 | # 3 | # This file is the main config file for your service. 4 | # It's very minimal at this point and uses default values. 5 | # You can always add more config options for more control. 6 | # We've included some commented out config examples here. 7 | # Just uncomment any of them to get that config option. 8 | # 9 | # For full config options, check the docs: 10 | # docs.serverless.com 11 | # 12 | # Happy Coding! 13 | 14 | service: my-serverless-service 15 | 16 | # You can pin your service to only deploy with a specific Serverless version 17 | # Check out our docs for more details 18 | # frameworkVersion: "=X.X.X" 19 | 20 | provider: 21 | name: aws 22 | runtime: nodejs6.10 23 | stage: dev 24 | region: us-east-1 25 | 26 | functions: 27 | hello: 28 | handler: handler.hello 29 | memorySize: 128 30 | events: 31 | - http: 32 | path: handler 33 | method: get 34 | -------------------------------------------------------------------------------- /Chapter03/New folder/CreateThumbnails.js: -------------------------------------------------------------------------------- 1 | // dependencies 2 | var async = require('async'); 3 | var AWS = require('aws-sdk'); 4 | var gm = require('gm') 5 | .subClass({ imageMagick: true }); // Enable ImageMagick integration. 6 | var util = require('util'); 7 | 8 | // constants 9 | var MAX_WIDTH = 100; 10 | var MAX_HEIGHT = 100; 11 | 12 | // get reference to S3 client 13 | var s3 = new AWS.S3(); 14 | 15 | exports.handler = function(event, context, callback) { 16 | // Read options from the event. 17 | console.log("Reading options from event:\n", util.inspect(event, {depth: 5})); 18 | var srcBucket = event.Records[0].s3.bucket.name; 19 | // Object key may have spaces or unicode non-ASCII characters. 20 | var srcKey = 21 | decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " ")); 22 | var dstBucket = srcBucket + "resized"; 23 | var dstKey = "resized-" + srcKey; 24 | 25 | // Sanity check: validate that source and destination are different buckets. 26 | if (srcBucket == dstBucket) { 27 | callback("Source and destination buckets are the same."); 28 | return; 29 | } 30 | 31 | // Infer the image type. 32 | var typeMatch = srcKey.match(/\.([^.]*)$/); 33 | if (!typeMatch) { 34 | callback("Could not determine the image type."); 35 | return; 36 | } 37 | var imageType = typeMatch[1]; 38 | if (imageType != "jpg" && imageType != "png") { 39 | callback('Unsupported image type: ${imageType}'); 40 | return; 41 | } 42 | 43 | // Download the image from S3, transform, and upload to a different S3 bucket. 44 | async.waterfall([ 45 | function download(next) { 46 | // Download the image from S3 into a buffer. 47 | s3.getObject({ 48 | Bucket: srcBucket, 49 | Key: srcKey 50 | }, 51 | next); 52 | }, 53 | function transform(response, next) { 54 | gm(response.Body).size(function(err, size) { 55 | // Infer the scaling factor to avoid stretching the image unnaturally. 56 | var scalingFactor = Math.min( 57 | MAX_WIDTH / size.width, 58 | MAX_HEIGHT / size.height 59 | ); 60 | var width = scalingFactor * size.width; 61 | var height = scalingFactor * size.height; 62 | 63 | // Transform the image buffer in memory. 64 | this.resize(width, height) 65 | .toBuffer(imageType, function(err, buffer) { 66 | if (err) { 67 | next(err); 68 | } else { 69 | next(null, response.ContentType, buffer); 70 | } 71 | }); 72 | }); 73 | }, 74 | function upload(contentType, data, next) { 75 | // Stream the transformed image to a different S3 bucket. 76 | s3.putObject({ 77 | Bucket: dstBucket, 78 | Key: dstKey, 79 | Body: data, 80 | ContentType: contentType 81 | }, 82 | next); 83 | } 84 | ], function (err) { 85 | if (err) { 86 | console.error( 87 | 'Unable to resize ' + srcBucket + '/' + srcKey + 88 | ' and upload to ' + dstBucket + '/' + dstKey + 89 | ' due to an error: ' + err 90 | ); 91 | } else { 92 | console.log( 93 | 'Successfully resized ' + srcBucket + '/' + srcKey + 94 | ' and uploaded to ' + dstBucket + '/' + dstKey 95 | ); 96 | } 97 | 98 | callback(null, "message"); 99 | } 100 | ); 101 | }; -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jenkins:latest 2 | USER root 3 | 4 | # Install Node.js 5 | RUN apt-get update -y 6 | RUN apt-get install -y apt-utils 7 | RUN apt-get install -y sudo 8 | RUN apt-get install --yes curl 9 | RUN curl --silent --location https://deb.nodesource.com/setup_8.x | bash - 10 | RUN apt-get install --yes nodejs 11 | RUN apt-get install --yes build-essential 12 | RUN npm i -g npm 13 | 14 | RUN npm install serverless -g 15 | 16 | RUN npm config set prefix '~/.npm-global' 17 | RUN export PATH=~/.npm-global/bin:$PATH 18 | 19 | USER jenkins 20 | -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | environment { 5 | AWS_DEFAULT_REGION="us-east-1" 6 | } 7 | 8 | stages { 9 | 10 | stage ('Deploy to DEV') { 11 | 12 | steps { 13 | withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'dev-serverless', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { 14 | sh "serverless deploy --stage dev" 15 | } 16 | } 17 | } 18 | 19 | stage ('System Test on Dev') { 20 | 21 | steps { 22 | withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'dev-serverless', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { 23 | sh ''' 24 | export TASKS_ENDPOINT=6pgn5wuqeh.execute-api.us-east-1.amazonaws.com/dev 25 | ./node_modules/mocha/bin/mocha ./test/*.js 26 | ''' 27 | } 28 | } 29 | } 30 | 31 | stage ('Deploy to SIT') { 32 | 33 | steps { 34 | withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'sit-serverless', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { 35 | sh "serverless deploy --stage sit" 36 | } 37 | } 38 | } 39 | 40 | stage ('System Test on SIT') { 41 | 42 | steps { 43 | withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'sit-serverless', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { 44 | sh ''' 45 | export TASKS_ENDPOINT=ax1hlqv0vl.execute-api.us-east-1.amazonaws.com/sit 46 | ./node_modules/mocha/bin/mocha ./test/*.js 47 | ''' 48 | } 49 | } 50 | } 51 | 52 | stage('Promotion') { 53 | steps { 54 | timeout(time: 1, unit:'DAYS') { 55 | input 'Deploy to Production?' 56 | } 57 | } 58 | } 59 | 60 | stage ('Deploy to PROD') { 61 | 62 | steps { 63 | withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'prod-serverless', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { 64 | sh "serverless deploy --stage prod" 65 | } 66 | } 67 | } 68 | 69 | stage ('System Test on PROD') { 70 | 71 | steps { 72 | withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'prod-serverless', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { 73 | sh ''' 74 | export TASKS_ENDPOINT=valdnbrq3a.execute-api.us-east-1.amazonaws.com/prod 75 | ./node_modules/mocha/bin/mocha ./test/*.js 76 | ''' 77 | } 78 | } 79 | } 80 | 81 | } 82 | } -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-rest-with-dynamodb", 3 | "version": "1.0.0", 4 | "description": "Serverless CRUD service exposing a REST HTTP interface", 5 | "author": "Shashikant Bangera", 6 | "dependencies": { 7 | "uuid": "^3.0.1" 8 | }, 9 | "keywords": [ 10 | "aws", 11 | "Deployment", 12 | "CD/CI", 13 | "serverless", 14 | "task" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "" 19 | }, 20 | "bugs": { 21 | "url": "" 22 | }, 23 | "author": "Shashikant Bangera ", 24 | "license": "MIT", 25 | "devDependencies": { 26 | "aws-sdk": "^2.6.7", 27 | "request": "^2.79.0", 28 | "mocha": "^3.2.0", 29 | "serverless": "^1.7.0", 30 | "jshint": "^2.9.4" 31 | }, 32 | "scripts": { 33 | "test": "./node_modules/.bin/mocha ./test/*.js" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-rest-api-with-dynamodb 2 | 3 | frameworkVersion: ">=1.1.0 <2.0.0" 4 | 5 | provider: 6 | name: aws 7 | runtime: nodejs6.10 8 | environment: 9 | DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage} 10 | iamRoleStatements: 11 | - Effect: Allow 12 | Action: 13 | - dynamodb:Query 14 | - dynamodb:Scan 15 | - dynamodb:GetItem 16 | - dynamodb:PutItem 17 | - dynamodb:UpdateItem 18 | - dynamodb:DeleteItem 19 | Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" 20 | 21 | functions: 22 | create: 23 | handler: src/mytasks/create.create 24 | events: 25 | - http: 26 | path: mytasks 27 | method: post 28 | cors: true 29 | 30 | list: 31 | handler: src/mytasks/list.list 32 | events: 33 | - http: 34 | path: mytasks 35 | method: get 36 | cors: true 37 | 38 | get: 39 | handler: src/mytasks/get.get 40 | events: 41 | - http: 42 | path: mytasks/{id} 43 | method: get 44 | cors: true 45 | 46 | update: 47 | handler: src/mytasks/update.update 48 | events: 49 | - http: 50 | path: mytasks/{id} 51 | method: put 52 | cors: true 53 | 54 | delete: 55 | handler: src/mytasks/delete.delete 56 | events: 57 | - http: 58 | path: mytasks/{id} 59 | method: delete 60 | cors: true 61 | 62 | resources: 63 | Resources: 64 | mytasksDynamoDbTable: 65 | Type: 'AWS::DynamoDB::Table' 66 | DeletionPolicy: Retain 67 | Properties: 68 | AttributeDefinitions: 69 | - 70 | AttributeName: id 71 | AttributeType: S 72 | KeySchema: 73 | - 74 | AttributeName: id 75 | KeyType: HASH 76 | ProvisionedThroughput: 77 | ReadCapacityUnits: 1 78 | WriteCapacityUnits: 1 79 | TableName: ${self:provider.environment.DYNAMODB_TABLE} 80 | -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/src/mytasks/create.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const uuid = require('uuid'); 4 | const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies 5 | 6 | const dynamoDb = new AWS.DynamoDB.DocumentClient(); 7 | 8 | module.exports.create = (event, context, callback) => { 9 | const timestamp = new Date().getTime(); 10 | const data = JSON.parse(event.body); 11 | if (typeof data.text !== 'string') { 12 | console.error('Validation Failed'); 13 | callback(null, { 14 | statusCode: 400, 15 | headers: { 'Content-Type': 'text/plain' }, 16 | body: 'Couldn\'t create the task item.', 17 | }); 18 | return; 19 | } 20 | 21 | const params = { 22 | TableName: process.env.DYNAMODB_TABLE, 23 | Item: { 24 | id: uuid.v1(), 25 | text: data.text, 26 | checked: false, 27 | createdAt: timestamp, 28 | updatedAt: timestamp, 29 | }, 30 | }; 31 | 32 | // write the task to the database 33 | dynamoDb.put(params, (error) => { 34 | // handle potential errors 35 | if (error) { 36 | console.error(error); 37 | callback(null, { 38 | statusCode: error.statusCode || 501, 39 | headers: { 'Content-Type': 'text/plain' }, 40 | body: 'Couldn\'t create the task item.', 41 | }); 42 | return; 43 | } 44 | 45 | // create a response 46 | const response = { 47 | statusCode: 200, 48 | body: JSON.stringify(params.Item), 49 | }; 50 | callback(null, response); 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/src/mytasks/delete.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies 4 | 5 | const dynamoDb = new AWS.DynamoDB.DocumentClient(); 6 | 7 | module.exports.delete = (event, context, callback) => { 8 | const params = { 9 | TableName: process.env.DYNAMODB_TABLE, 10 | Key: { 11 | id: event.pathParameters.id, 12 | }, 13 | }; 14 | 15 | // delete the task from the database 16 | dynamoDb.delete(params, (error) => { 17 | // handle potential errors 18 | if (error) { 19 | console.error(error); 20 | callback(null, { 21 | statusCode: error.statusCode || 501, 22 | headers: { 'Content-Type': 'text/plain' }, 23 | body: 'Couldn\'t remove the task item.', 24 | }); 25 | return; 26 | } 27 | 28 | // create a response 29 | const response = { 30 | statusCode: 200, 31 | body: JSON.stringify({}), 32 | }; 33 | callback(null, response); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/src/mytasks/get.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies 4 | 5 | const dynamoDb = new AWS.DynamoDB.DocumentClient(); 6 | 7 | module.exports.get = (event, context, callback) => { 8 | const params = { 9 | TableName: process.env.DYNAMODB_TABLE, 10 | Key: { 11 | id: event.pathParameters.id, 12 | }, 13 | }; 14 | 15 | // fetch task from the database 16 | dynamoDb.get(params, (error, result) => { 17 | // handle potential errors 18 | if (error) { 19 | console.error(error); 20 | callback(null, { 21 | statusCode: error.statusCode || 501, 22 | headers: { 'Content-Type': 'text/plain' }, 23 | body: 'Couldn\'t fetch the task item.', 24 | }); 25 | return; 26 | } 27 | 28 | // create a response 29 | const response = { 30 | statusCode: 200, 31 | body: JSON.stringify(result.Item), 32 | }; 33 | callback(null, response); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/src/mytasks/list.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies 4 | 5 | const dynamoDb = new AWS.DynamoDB.DocumentClient(); 6 | const params = { 7 | TableName: process.env.DYNAMODB_TABLE, 8 | }; 9 | 10 | module.exports.list = (event, context, callback) => { 11 | // fetch all tasks from the database 12 | dynamoDb.scan(params, (error, result) => { 13 | // handle potential errors 14 | if (error) { 15 | console.error(error); 16 | callback(null, { 17 | statusCode: error.statusCode || 501, 18 | headers: { 'Content-Type': 'text/plain' }, 19 | body: 'Couldn\'t fetch the tasks.', 20 | }); 21 | return; 22 | } 23 | 24 | // create a response 25 | const response = { 26 | statusCode: 200, 27 | body: JSON.stringify(result.Items), 28 | }; 29 | callback(null, response); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/src/mytasks/update.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies 4 | 5 | const dynamoDb = new AWS.DynamoDB.DocumentClient(); 6 | 7 | module.exports.update = (event, context, callback) => { 8 | const timestamp = new Date().getTime(); 9 | const data = JSON.parse(event.body); 10 | 11 | // validation 12 | if (typeof data.text !== 'string' || typeof data.checked !== 'boolean') { 13 | console.error('Validation Failed'); 14 | callback(null, { 15 | statusCode: 400, 16 | headers: { 'Content-Type': 'text/plain' }, 17 | body: 'Couldn\'t update the task item.', 18 | }); 19 | return; 20 | } 21 | 22 | const params = { 23 | TableName: process.env.DYNAMODB_TABLE, 24 | Key: { 25 | id: event.pathParameters.id, 26 | }, 27 | ExpressionAttributeNames: { 28 | '#task_text': 'text', 29 | }, 30 | ExpressionAttributeValues: { 31 | ':text': data.text, 32 | ':checked': data.checked, 33 | ':updatedAt': timestamp, 34 | }, 35 | UpdateExpression: 'SET #task_text = :text, checked = :checked, updatedAt = :updatedAt', 36 | ReturnValues: 'ALL_NEW', 37 | }; 38 | 39 | // update the task in the database 40 | dynamoDb.update(params, (error, result) => { 41 | // handle potential errors 42 | if (error) { 43 | console.error(error); 44 | callback(null, { 45 | statusCode: error.statusCode || 501, 46 | headers: { 'Content-Type': 'text/plain' }, 47 | body: 'Couldn\'t fetch the task item.', 48 | }); 49 | return; 50 | } 51 | 52 | // create a response 53 | const response = { 54 | statusCode: 200, 55 | body: JSON.stringify(result.Attributes), 56 | }; 57 | callback(null, response); 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "src", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "uuid": "^2.0.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/test/createDelete.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | 5 | describe('Create, Delete', function() { 6 | this.timeout(5000); 7 | it('should create a new Task, & delete it', function(done) { 8 | // Build and log the path 9 | var path = "https://" + process.env.TASKS_ENDPOINT + "/mytasks"; 10 | 11 | // Fetch the comparison payload 12 | require.extensions['.txt'] = function (module, filename) { 13 | module.exports = fs.readFileSync(filename, 'utf8'); 14 | }; 15 | var desiredPayload = require("./data/newTask1.json"); 16 | 17 | // Create the new Task 18 | var options = {'url' : path, 'form': JSON.stringify(desiredPayload)}; 19 | request.post(options, function (err, res, body){ 20 | if(err){ 21 | throw new Error("Create call failed: " + err); 22 | } 23 | assert.equal(200, res.statusCode, "Create Status Code != 200 (" + res.statusCode + ")"); 24 | var task = JSON.parse(res.body); 25 | // Now delete the task 26 | var deletePath = path + "/" + task.id; 27 | request.del(deletePath, function (err, res, body){ 28 | if(err){ 29 | throw new Error("Delete call failed: " + err); 30 | } 31 | assert.equal(200, res.statusCode, "Delete Status Code != 200 (" + res.statusCode + ")"); 32 | done(); 33 | }); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/test/createListDelete.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | 5 | describe('Create, List, Delete', function() { 6 | this.timeout(5000); 7 | it('should create a new task, list it, & delete it', function(done) { 8 | // Build and log the path 9 | var path = "https://" + process.env.TASKS_ENDPOINT + "/mytasks"; 10 | 11 | // Fetch the comparison payload 12 | require.extensions['.txt'] = function (module, filename) { 13 | module.exports = fs.readFileSync(filename, 'utf8'); 14 | }; 15 | var desiredPayload = require("./data/newTask1.json"); 16 | 17 | // Create the new Task 18 | var options = {'url' : path, 'form': JSON.stringify(desiredPayload)}; 19 | request.post(options, function (err, res, body){ 20 | if(err){ 21 | throw new Error("Create call failed: " + err); 22 | } 23 | assert.equal(200, res.statusCode, "Create Status Code != 200 (" + res.statusCode + ")"); 24 | 25 | // Read the list, see if the new item is there at the end 26 | request.get(path, function (err, res, body){ 27 | if(err){ 28 | throw new Error("List call failed: " + err); 29 | } 30 | assert.equal(200, res.statusCode, "List Status Code != 200 (" + res.statusCode + ")"); 31 | 32 | var taskList = JSON.parse(res.body); 33 | if(taskList[taskList.length-1].text = desiredPayload.text) { 34 | // Item found, delete it 35 | var deletePath = path + "/" + taskList[taskList.length-1].id; 36 | request.del(deletePath, function (err, res, body){ 37 | if(err){ 38 | throw new Error("Delete call failed: " + err); 39 | } 40 | assert.equal(200, res.statusCode, "Delete Status Code != 200 (" + res.statusCode + ")"); 41 | done(); 42 | }); 43 | } else { 44 | // Item not found, fail test 45 | assert.equal(true, false, "New item not found in list."); 46 | done(); 47 | } 48 | }); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/test/createReadDelete.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | 5 | describe('Create, Read, Delete', function() { 6 | this.timeout(5000); 7 | it('should create a new Todo, read it, & delete it', function(done) { 8 | // Build and log the path 9 | var path = "https://" + process.env.TASKS_ENDPOINT + "/mytasks"; 10 | 11 | // Fetch the comparison payload 12 | require.extensions['.txt'] = function (module, filename) { 13 | module.exports = fs.readFileSync(filename, 'utf8'); 14 | }; 15 | var desiredPayload = require("./data/newTask1.json"); 16 | 17 | // Create the new todo 18 | var options = {'url' : path, 'form': JSON.stringify(desiredPayload)}; 19 | request.post(options, function (err, res, body){ 20 | if(err){ 21 | throw new Error("Create call failed: " + err); 22 | } 23 | assert.equal(200, res.statusCode, "Create Status Code != 200 (" + res.statusCode + ")"); 24 | var todo = JSON.parse(res.body); 25 | // Read the item 26 | var specificPath = path + "/" + todo.id; 27 | request.get(path, function (err, res, body){ 28 | if(err){ 29 | throw new Error("Read call failed: " + err); 30 | } 31 | assert.equal(200, res.statusCode, "Read Status Code != 200 (" + res.statusCode + ")"); 32 | 33 | var todoList = JSON.parse(res.body); 34 | if(todoList.text = desiredPayload.text) { 35 | // Item found, delete it 36 | request.del(specificPath, function (err, res, body){ 37 | if(err){ 38 | throw new Error("Delete call failed: " + err); 39 | } 40 | assert.equal(200, res.statusCode, "Delete Status Code != 200 (" + res.statusCode + ")"); 41 | done(); 42 | }); 43 | } else { 44 | // Item not found, fail test 45 | assert.equal(true, false, "New item not found in list."); 46 | done(); 47 | } 48 | }); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/test/data/newTask1.json: -------------------------------------------------------------------------------- 1 | { "text": "Learn Serverless" } -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/test/data/newTask2.json: -------------------------------------------------------------------------------- 1 | { "text": "Test Serverless" } -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-dynamodb-mytasks-master/test/newTodo1.json: -------------------------------------------------------------------------------- 1 | { "text": "Learn Serverless" } -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-thumbnail-master/buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | install: 5 | commands: 6 | # Install dependencies needed for running tests 7 | - npm install 8 | 9 | # Upgrade AWS CLI to the latest version 10 | - pip install --upgrade awscli 11 | pre_build: 12 | commands: 13 | # Discover and run unit tests in the 'tests' directory 14 | # - npm test 15 | build: 16 | commands: 17 | # Use AWS SAM to package the application by using AWS CloudFormation 18 | - aws cloudformation package --template template.yml --s3-bucket my-cloud-formation-bucket --output-template template-export.yml 19 | 20 | artifacts: 21 | type: zip 22 | files: 23 | - template-export.yml 24 | -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-thumbnail-master/cloudformationpolicy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "VisualEditor0", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "iam:*", 9 | "s3:*", 10 | "lambda:*", 11 | "cloudformation:*" 12 | ], 13 | "Resource": "*" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-thumbnail-master/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // dependencies 4 | var async = require('async'); 5 | var AWS = require('aws-sdk'); 6 | var gm = require('gm') 7 | .subClass({ imageMagick: true }); // Enable ImageMagick integration. 8 | var util = require('util'); 9 | 10 | // constants 11 | var MAX_WIDTH = 100; 12 | var MAX_HEIGHT = 100; 13 | 14 | // get reference to S3 client 15 | var s3 = new AWS.S3(); 16 | 17 | exports.handler = function(event, context, callback) { 18 | // Read options from the event. 19 | console.log("Reading options from event:\n", util.inspect(event, {depth: 5})); 20 | var srcBucket = event.Records[0].s3.bucket.name; 21 | // Object key may have spaces or unicode non-ASCII characters. 22 | var srcKey = 23 | decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " ")); 24 | var dstBucket = srcBucket + "resized"; 25 | var dstKey = "resized-" + srcKey; 26 | 27 | // Sanity check: validate that source and destination are different buckets. 28 | if (srcBucket == dstBucket) { 29 | callback("Source and destination buckets are the same."); 30 | return; 31 | } 32 | 33 | // Infer the image type. 34 | var typeMatch = srcKey.match(/\.([^.]*)$/); 35 | if (!typeMatch) { 36 | callback("Could not determine the image type."); 37 | return; 38 | } 39 | var imageType = typeMatch[1]; 40 | if (imageType != "jpg" && imageType != "png") { 41 | callback('Unsupported image type: ${imageType}'); 42 | return; 43 | } 44 | 45 | // Download the image from S3, transform, and upload to a different S3 bucket. 46 | async.waterfall([ 47 | function download(next) { 48 | // Download the image from S3 into a buffer. 49 | s3.getObject({ 50 | Bucket: srcBucket, 51 | Key: srcKey 52 | }, 53 | next); 54 | }, 55 | function transform(response, next) { 56 | gm(response.Body).size(function(err, size) { 57 | // Infer the scaling factor to avoid stretching the image unnaturally. 58 | var scalingFactor = Math.min( 59 | MAX_WIDTH / size.width, 60 | MAX_HEIGHT / size.height 61 | ); 62 | var width = scalingFactor * size.width; 63 | var height = scalingFactor * size.height; 64 | 65 | // Transform the image buffer in memory. 66 | this.resize(width, height) 67 | .toBuffer(imageType, function(err, buffer) { 68 | if (err) { 69 | next(err); 70 | } else { 71 | next(null, response.ContentType, buffer); 72 | } 73 | }); 74 | }); 75 | }, 76 | function upload(contentType, data, next) { 77 | // Stream the transformed image to a different S3 bucket. 78 | s3.putObject({ 79 | Bucket: dstBucket, 80 | Key: dstKey, 81 | Body: data, 82 | ContentType: contentType 83 | }, 84 | next); 85 | } 86 | ], function (err) { 87 | if (err) { 88 | console.error( 89 | 'Unable to resize ' + srcBucket + '/' + srcKey + 90 | ' and upload to ' + dstBucket + '/' + dstKey + 91 | ' due to an error: ' + err 92 | ); 93 | } else { 94 | console.log( 95 | 'Successfully resized ' + srcBucket + '/' + srcKey + 96 | ' and uploaded to ' + dstBucket + '/' + dstKey 97 | ); 98 | } 99 | 100 | callback(null, "message"); 101 | } 102 | ); 103 | }; -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-thumbnail-master/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbnail", 3 | "description": "Thumbnail NodeJs web application", 4 | "version": "0.0.1", 5 | "private": true, 6 | "devDependencies": { 7 | "mocha": "5.0.0", 8 | "unit.js": "2.0.0" 9 | }, 10 | "dependencies": { 11 | "async": "", 12 | "gm": "", 13 | "aws-sdk": "" 14 | }, 15 | "scripts": { 16 | "test": "mocha --recursive tests" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Chapter03/New folder/aws-lambda-thumbnail-master/template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Resources: 4 | CreateThumbnail: 5 | Type: AWS::Serverless::Function 6 | Properties: 7 | Handler: index.handler 8 | Runtime: nodejs6.10 9 | Timeout: 60 10 | Policies: AWSLambdaExecute 11 | Events: 12 | CreateThumbnailEvent: 13 | Type: S3 14 | Properties: 15 | Bucket: !Ref SrcBucket 16 | Events: s3:ObjectCreated:* 17 | 18 | SrcBucket: 19 | Type: AWS::S3::Bucket -------------------------------------------------------------------------------- /Chapter03/New folder/my-canary-deployment-master/handler.js: -------------------------------------------------------------------------------- 1 | module.exports.hello = (event, context, callback) => { 2 | const response = { 3 | statusCode: 200, 4 | body: 'Hey my version is running with the hooks!' 5 | }; 6 | 7 | callback(null, response); 8 | }; -------------------------------------------------------------------------------- /Chapter03/New folder/my-canary-deployment-master/hooks.js: -------------------------------------------------------------------------------- 1 | const aws = require('aws-sdk'); 2 | const codedeploy = new aws.CodeDeploy({apiVersion: '2014-10-06'}); 3 | 4 | module.exports.pre = (event, context, callback) => { 5 | var deploymentId = event.DeploymentId; 6 | var lifecycleEventHookExecutionId = event.LifecycleEventHookExecutionId; 7 | 8 | console.log('We are running some integration tests before we start shifting traffic...'); 9 | 10 | var params = { 11 | deploymentId: deploymentId, 12 | lifecycleEventHookExecutionId: lifecycleEventHookExecutionId, 13 | status: 'Succeeded' // status can be 'Succeeded' or 'Failed' 14 | }; 15 | 16 | return codedeploy.putLifecycleEventHookExecutionStatus(params).promise() 17 | .then(data => callback(null, 'Validation test succeeded')) 18 | .catch(err => callback('Validation test failed')); 19 | }; 20 | 21 | module.exports.post = (event, context, callback) => { 22 | var deploymentId = event.DeploymentId; 23 | var lifecycleEventHookExecutionId = event.LifecycleEventHookExecutionId; 24 | 25 | console.log('Check some stuff after traffic has been shifted...'); 26 | 27 | var params = { 28 | deploymentId: deploymentId, 29 | lifecycleEventHookExecutionId: lifecycleEventHookExecutionId, 30 | status: 'Succeeded' // status can be 'Succeeded' or 'Failed' 31 | }; 32 | 33 | return codedeploy.putLifecycleEventHookExecutionStatus(params).promise() 34 | .then(data => callback(null, 'Validation test succeeded')) 35 | .catch(err => callback('Validation test failed')); 36 | }; -------------------------------------------------------------------------------- /Chapter03/New folder/my-canary-deployment-master/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-canary-deployment", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "handler.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "shashikant bangera", 10 | "devDependencies": { 11 | "serverless-plugin-aws-alerts": "^1.2.4", 12 | "serverless-plugin-canary-deployments": "^0.4.0" 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter03/New folder/my-canary-deployment-master/serverless.yml: -------------------------------------------------------------------------------- 1 | service: my-canary-deployment 2 | 3 | provider: 4 | name: aws 5 | runtime: nodejs6.10 6 | iamRoleStatements: 7 | - Effect: Allow 8 | Action: 9 | - codedeploy:* 10 | Resource: 11 | - "*" 12 | 13 | plugins: 14 | - serverless-plugin-canary-deployments 15 | - serverless-plugin-aws-alerts 16 | 17 | custom: 18 | alerts: 19 | dashboards: true 20 | 21 | functions: 22 | hello: 23 | handler: handler.hello 24 | events: 25 | - http: get hello 26 | alarms: 27 | - name: test 28 | namespace: 'AWS/Lambda' 29 | metric: Errors 30 | threshold: 1 31 | statistic: Minimum 32 | period: 60 33 | evaluationPeriods: 1 34 | comparisonOperator: GreaterThanOrEqualToThreshold 35 | deploymentSettings: 36 | type: Linear10PercentEvery1Minute 37 | alias: Live 38 | preTrafficHook: preHook 39 | postTrafficHook: postHook 40 | alarms: 41 | - HelloTestAlarm 42 | preHook: 43 | handler: hooks.pre 44 | postHook: 45 | handler: hooks.post -------------------------------------------------------------------------------- /Chapter03/New folder/thumbnailEvent: -------------------------------------------------------------------------------- 1 | { 2 | "Records": [ 3 | { 4 | "eventVersion": "2.0", 5 | "eventSource": "aws:s3", 6 | "awsRegion": "us-west-2", 7 | "eventTime": "1970-01-01T00:00:00.000Z", 8 | "eventName": "ObjectCreated:Put", 9 | "userIdentity": { 10 | "principalId": "AIDAJDPLRKLG7UEXAMPLE" 11 | }, 12 | "requestParameters": { 13 | "sourceIPAddress": "127.0.0.1" 14 | }, 15 | "responseElements": { 16 | "x-amz-request-id": "C3D13FE58DE4C810", 17 | "x-amz-id-2": "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" 18 | }, 19 | "s3": { 20 | "s3SchemaVersion": "1.0", 21 | "configurationId": "testConfigRule", 22 | "bucket": { 23 | "name": "my-source-bucket76", 24 | "ownerIdentity": { 25 | "principalId": "A3NL1KOZZKExample" 26 | }, 27 | "arn": "arn:aws:s3:::my-source-bucket76" 28 | }, 29 | "object": { 30 | "key": "Baby.jpg", 31 | "size": 1024, 32 | "eTag": "d41d8cd98f00b204e9800998ecf8427e", 33 | "versionId": "096fKKXTRTtl3on89fVO.nfljtsv6qko" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /Chapter03/New folder/thumbnailEvent (2): -------------------------------------------------------------------------------- 1 | { 2 | "Records": [ 3 | { 4 | "eventVersion": "2.0", 5 | "eventSource": "aws:s3", 6 | "awsRegion": "us-west-2", 7 | "eventTime": "1970-01-01T00:00:00.000Z", 8 | "eventName": "ObjectCreated:Put", 9 | "userIdentity": { 10 | "principalId": "AIDAJDPLRKLG7UEXAMPLE" 11 | }, 12 | "requestParameters": { 13 | "sourceIPAddress": "127.0.0.1" 14 | }, 15 | "responseElements": { 16 | "x-amz-request-id": "C3D13FE58DE4C810", 17 | "x-amz-id-2": "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" 18 | }, 19 | "s3": { 20 | "s3SchemaVersion": "1.0", 21 | "configurationId": "testConfigRule", 22 | "bucket": { 23 | "name": "my-source-bucket76", 24 | "ownerIdentity": { 25 | "principalId": "A3NL1KOZZKExample" 26 | }, 27 | "arn": "arn:aws:s3:::my-source-bucket76" 28 | }, 29 | "object": { 30 | "key": "Baby.jpg", 31 | "size": 1024, 32 | "eTag": "d41d8cd98f00b204e9800998ecf8427e", 33 | "versionId": "096fKKXTRTtl3on89fVO.nfljtsv6qko" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /Chapter03/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-rest-with-dynamodb", 3 | "version": "1.0.0", 4 | "description": "Serverless CRUD service exposing a REST HTTP interface", 5 | "author": "Shashikant Bangera", 6 | "dependencies": { 7 | "uuid": "^3.0.1" 8 | }, 9 | "keywords": [ 10 | "aws", 11 | "Deployment", 12 | "CD/CI", 13 | "serverless", 14 | "task" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "" 19 | }, 20 | "bugs": { 21 | "url": "" 22 | }, 23 | "author": "Shashikant Bangera ", 24 | "license": "MIT", 25 | "devDependencies": { 26 | "aws-sdk": "^2.6.7", 27 | "request": "^2.79.0", 28 | "mocha": "^3.2.0", 29 | "serverless": "^1.7.0", 30 | "jshint": "^2.9.4" 31 | }, 32 | "scripts": { 33 | "test": "./node_modules/.bin/mocha ./test/*.js" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Chapter03/serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-rest-api-with-dynamodb 2 | 3 | frameworkVersion: ">=1.1.0 <2.0.0" 4 | 5 | provider: 6 | name: aws 7 | runtime: nodejs4.3 8 | environment: 9 | DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage} 10 | iamRoleStatements: 11 | - Effect: Allow 12 | Action: 13 | - dynamodb:Query 14 | - dynamodb:Scan 15 | - dynamodb:GetItem 16 | - dynamodb:PutItem 17 | - dynamodb:UpdateItem 18 | - dynamodb:DeleteItem 19 | Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" 20 | 21 | functions: 22 | create: 23 | handler: src/mytasks/create.create 24 | events: 25 | - http: 26 | path: mytasks 27 | method: post 28 | cors: true 29 | 30 | list: 31 | handler: src/mytasks/list.list 32 | events: 33 | - http: 34 | path: mytasks 35 | method: get 36 | cors: true 37 | 38 | get: 39 | handler: src/mytasks/get.get 40 | events: 41 | - http: 42 | path: mytasks/{id} 43 | method: get 44 | cors: true 45 | 46 | update: 47 | handler: src/mytasks/update.update 48 | events: 49 | - http: 50 | path: mytasks/{id} 51 | method: put 52 | cors: true 53 | 54 | delete: 55 | handler: src/mytasks/delete.delete 56 | events: 57 | - http: 58 | path: mytasks/{id} 59 | method: delete 60 | cors: true 61 | 62 | resources: 63 | Resources: 64 | mytasksDynamoDbTable: 65 | Type: 'AWS::DynamoDB::Table' 66 | DeletionPolicy: Retain 67 | Properties: 68 | AttributeDefinitions: 69 | - 70 | AttributeName: id 71 | AttributeType: S 72 | KeySchema: 73 | - 74 | AttributeName: id 75 | KeyType: HASH 76 | ProvisionedThroughput: 77 | ReadCapacityUnits: 1 78 | WriteCapacityUnits: 1 79 | TableName: ${self:provider.environment.DYNAMODB_TABLE} 80 | -------------------------------------------------------------------------------- /Chapter03/src/mytasks/create.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const uuid = require('uuid'); 4 | const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies 5 | 6 | const dynamoDb = new AWS.DynamoDB.DocumentClient(); 7 | 8 | module.exports.create = (event, context, callback) => { 9 | const timestamp = new Date().getTime(); 10 | const data = JSON.parse(event.body); 11 | if (typeof data.text !== 'string') { 12 | console.error('Validation Failed'); 13 | callback(null, { 14 | statusCode: 400, 15 | headers: { 'Content-Type': 'text/plain' }, 16 | body: 'Couldn\'t create the task item.', 17 | }); 18 | return; 19 | } 20 | 21 | const params = { 22 | TableName: process.env.DYNAMODB_TABLE, 23 | Item: { 24 | id: uuid.v1(), 25 | text: data.text, 26 | checked: false, 27 | createdAt: timestamp, 28 | updatedAt: timestamp, 29 | }, 30 | }; 31 | 32 | // write the task to the database 33 | dynamoDb.put(params, (error) => { 34 | // handle potential errors 35 | if (error) { 36 | console.error(error); 37 | callback(null, { 38 | statusCode: error.statusCode || 501, 39 | headers: { 'Content-Type': 'text/plain' }, 40 | body: 'Couldn\'t create the task item.', 41 | }); 42 | return; 43 | } 44 | 45 | // create a response 46 | const response = { 47 | statusCode: 200, 48 | body: JSON.stringify(params.Item), 49 | }; 50 | callback(null, response); 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /Chapter03/src/mytasks/delete.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies 4 | 5 | const dynamoDb = new AWS.DynamoDB.DocumentClient(); 6 | 7 | module.exports.delete = (event, context, callback) => { 8 | const params = { 9 | TableName: process.env.DYNAMODB_TABLE, 10 | Key: { 11 | id: event.pathParameters.id, 12 | }, 13 | }; 14 | 15 | // delete the task from the database 16 | dynamoDb.delete(params, (error) => { 17 | // handle potential errors 18 | if (error) { 19 | console.error(error); 20 | callback(null, { 21 | statusCode: error.statusCode || 501, 22 | headers: { 'Content-Type': 'text/plain' }, 23 | body: 'Couldn\'t remove the task item.', 24 | }); 25 | return; 26 | } 27 | 28 | // create a response 29 | const response = { 30 | statusCode: 200, 31 | body: JSON.stringify({}), 32 | }; 33 | callback(null, response); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /Chapter03/src/mytasks/get.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies 4 | 5 | const dynamoDb = new AWS.DynamoDB.DocumentClient(); 6 | 7 | module.exports.get = (event, context, callback) => { 8 | const params = { 9 | TableName: process.env.DYNAMODB_TABLE, 10 | Key: { 11 | id: event.pathParameters.id, 12 | }, 13 | }; 14 | 15 | // fetch task from the database 16 | dynamoDb.get(params, (error, result) => { 17 | // handle potential errors 18 | if (error) { 19 | console.error(error); 20 | callback(null, { 21 | statusCode: error.statusCode || 501, 22 | headers: { 'Content-Type': 'text/plain' }, 23 | body: 'Couldn\'t fetch the task item.', 24 | }); 25 | return; 26 | } 27 | 28 | // create a response 29 | const response = { 30 | statusCode: 200, 31 | body: JSON.stringify(result.Item), 32 | }; 33 | callback(null, response); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /Chapter03/src/mytasks/list.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies 4 | 5 | const dynamoDb = new AWS.DynamoDB.DocumentClient(); 6 | const params = { 7 | TableName: process.env.DYNAMODB_TABLE, 8 | }; 9 | 10 | module.exports.list = (event, context, callback) => { 11 | // fetch all tasks from the database 12 | dynamoDb.scan(params, (error, result) => { 13 | // handle potential errors 14 | if (error) { 15 | console.error(error); 16 | callback(null, { 17 | statusCode: error.statusCode || 501, 18 | headers: { 'Content-Type': 'text/plain' }, 19 | body: 'Couldn\'t fetch the tasks.', 20 | }); 21 | return; 22 | } 23 | 24 | // create a response 25 | const response = { 26 | statusCode: 200, 27 | body: JSON.stringify(result.Items), 28 | }; 29 | callback(null, response); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /Chapter03/src/mytasks/update.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies 4 | 5 | const dynamoDb = new AWS.DynamoDB.DocumentClient(); 6 | 7 | module.exports.update = (event, context, callback) => { 8 | const timestamp = new Date().getTime(); 9 | const data = JSON.parse(event.body); 10 | 11 | // validation 12 | if (typeof data.text !== 'string' || typeof data.checked !== 'boolean') { 13 | console.error('Validation Failed'); 14 | callback(null, { 15 | statusCode: 400, 16 | headers: { 'Content-Type': 'text/plain' }, 17 | body: 'Couldn\'t update the task item.', 18 | }); 19 | return; 20 | } 21 | 22 | const params = { 23 | TableName: process.env.DYNAMODB_TABLE, 24 | Key: { 25 | id: event.pathParameters.id, 26 | }, 27 | ExpressionAttributeNames: { 28 | '#task_text': 'text', 29 | }, 30 | ExpressionAttributeValues: { 31 | ':text': data.text, 32 | ':checked': data.checked, 33 | ':updatedAt': timestamp, 34 | }, 35 | UpdateExpression: 'SET #task_text = :text, checked = :checked, updatedAt = :updatedAt', 36 | ReturnValues: 'ALL_NEW', 37 | }; 38 | 39 | // update the task in the database 40 | dynamoDb.update(params, (error, result) => { 41 | // handle potential errors 42 | if (error) { 43 | console.error(error); 44 | callback(null, { 45 | statusCode: error.statusCode || 501, 46 | headers: { 'Content-Type': 'text/plain' }, 47 | body: 'Couldn\'t fetch the task item.', 48 | }); 49 | return; 50 | } 51 | 52 | // create a response 53 | const response = { 54 | statusCode: 200, 55 | body: JSON.stringify(result.Attributes), 56 | }; 57 | callback(null, response); 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /Chapter03/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "src", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "uuid": "^2.0.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter03/test/createDelete.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | 5 | describe('Create, Delete', function() { 6 | this.timeout(5000); 7 | it('should create a new Task, & delete it', function(done) { 8 | // Build and log the path 9 | var path = "https://" + process.env.TASKS_ENDPOINT + "/mytasks"; 10 | 11 | // Fetch the comparison payload 12 | require.extensions['.txt'] = function (module, filename) { 13 | module.exports = fs.readFileSync(filename, 'utf8'); 14 | }; 15 | var desiredPayload = require("./data/newTask1.json"); 16 | 17 | // Create the new Task 18 | var options = {'url' : path, 'form': JSON.stringify(desiredPayload)}; 19 | request.post(options, function (err, res, body){ 20 | if(err){ 21 | throw new Error("Create call failed: " + err); 22 | } 23 | assert.equal(200, res.statusCode, "Create Status Code != 200 (" + res.statusCode + ")"); 24 | var task = JSON.parse(res.body); 25 | // Now delete the task 26 | var deletePath = path + "/" + task.id; 27 | request.del(deletePath, function (err, res, body){ 28 | if(err){ 29 | throw new Error("Delete call failed: " + err); 30 | } 31 | assert.equal(200, res.statusCode, "Delete Status Code != 200 (" + res.statusCode + ")"); 32 | done(); 33 | }); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /Chapter03/test/createListDelete.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | 5 | describe('Create, List, Delete', function() { 6 | this.timeout(5000); 7 | it('should create a new task, list it, & delete it', function(done) { 8 | // Build and log the path 9 | var path = "https://" + process.env.TASKS_ENDPOINT + "/mytasks"; 10 | 11 | // Fetch the comparison payload 12 | require.extensions['.txt'] = function (module, filename) { 13 | module.exports = fs.readFileSync(filename, 'utf8'); 14 | }; 15 | var desiredPayload = require("./data/newTask1.json"); 16 | 17 | // Create the new Task 18 | var options = {'url' : path, 'form': JSON.stringify(desiredPayload)}; 19 | request.post(options, function (err, res, body){ 20 | if(err){ 21 | throw new Error("Create call failed: " + err); 22 | } 23 | assert.equal(200, res.statusCode, "Create Status Code != 200 (" + res.statusCode + ")"); 24 | 25 | // Read the list, see if the new item is there at the end 26 | request.get(path, function (err, res, body){ 27 | if(err){ 28 | throw new Error("List call failed: " + err); 29 | } 30 | assert.equal(200, res.statusCode, "List Status Code != 200 (" + res.statusCode + ")"); 31 | 32 | var taskList = JSON.parse(res.body); 33 | if(taskList[taskList.length-1].text = desiredPayload.text) { 34 | // Item found, delete it 35 | var deletePath = path + "/" + taskList[taskList.length-1].id; 36 | request.del(deletePath, function (err, res, body){ 37 | if(err){ 38 | throw new Error("Delete call failed: " + err); 39 | } 40 | assert.equal(200, res.statusCode, "Delete Status Code != 200 (" + res.statusCode + ")"); 41 | done(); 42 | }); 43 | } else { 44 | // Item not found, fail test 45 | assert.equal(true, false, "New item not found in list."); 46 | done(); 47 | } 48 | }); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /Chapter03/test/createReadDelete.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var request = require('request'); 3 | var fs = require('fs'); 4 | 5 | describe('Create, Read, Delete', function() { 6 | this.timeout(5000); 7 | it('should create a new Todo, read it, & delete it', function(done) { 8 | // Build and log the path 9 | var path = "https://" + process.env.TASKS_ENDPOINT + "/mytasks"; 10 | 11 | // Fetch the comparison payload 12 | require.extensions['.txt'] = function (module, filename) { 13 | module.exports = fs.readFileSync(filename, 'utf8'); 14 | }; 15 | var desiredPayload = require("./data/newTask1.json"); 16 | 17 | // Create the new todo 18 | var options = {'url' : path, 'form': JSON.stringify(desiredPayload)}; 19 | request.post(options, function (err, res, body){ 20 | if(err){ 21 | throw new Error("Create call failed: " + err); 22 | } 23 | assert.equal(200, res.statusCode, "Create Status Code != 200 (" + res.statusCode + ")"); 24 | var todo = JSON.parse(res.body); 25 | // Read the item 26 | var specificPath = path + "/" + todo.id; 27 | request.get(path, function (err, res, body){ 28 | if(err){ 29 | throw new Error("Read call failed: " + err); 30 | } 31 | assert.equal(200, res.statusCode, "Read Status Code != 200 (" + res.statusCode + ")"); 32 | 33 | var todoList = JSON.parse(res.body); 34 | if(todoList.text = desiredPayload.text) { 35 | // Item found, delete it 36 | request.del(specificPath, function (err, res, body){ 37 | if(err){ 38 | throw new Error("Delete call failed: " + err); 39 | } 40 | assert.equal(200, res.statusCode, "Delete Status Code != 200 (" + res.statusCode + ")"); 41 | done(); 42 | }); 43 | } else { 44 | // Item not found, fail test 45 | assert.equal(true, false, "New item not found in list."); 46 | done(); 47 | } 48 | }); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /Chapter03/test/data/newTask1.json: -------------------------------------------------------------------------------- 1 | { "text": "Learn Serverless" } -------------------------------------------------------------------------------- /Chapter03/test/data/newTask2.json: -------------------------------------------------------------------------------- 1 | { "text": "Test Serverless" } -------------------------------------------------------------------------------- /Chapter03/test/newTodo1.json: -------------------------------------------------------------------------------- 1 | { "text": "Learn Serverless" } -------------------------------------------------------------------------------- /Chapter04/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jenkins:latest 2 | USER root 3 | 4 | # Install Node.js 5 | RUN apt-get update -y 6 | RUN apt-get install -y apt-utils 7 | RUN apt-get install -y sudo 8 | RUN apt-get install --yes curl 9 | RUN curl --silent --location https://deb.nodesource.com/setup_8.x | bash - 10 | RUN apt-get install --yes nodejs 11 | RUN apt-get install --yes build-essential 12 | RUN npm i -g npm 13 | 14 | RUN npm install serverless -g 15 | RUN npm config set prefix '~/.npm-global' 16 | RUN export PATH=~/.npm-global/bin:$PATH 17 | 18 | USER jenkins 19 | -------------------------------------------------------------------------------- /Chapter04/New folder/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jenkins:latest 2 | USER root 3 | 4 | # Install Node.js 5 | RUN apt-get update -y 6 | RUN apt-get install -y apt-utils 7 | RUN apt-get install -y sudo 8 | RUN apt-get install --yes curl 9 | RUN curl --silent --location https://deb.nodesource.com/setup_8.x | bash - 10 | RUN apt-get install --yes nodejs 11 | RUN apt-get install --yes build-essential 12 | RUN apt-get install --yes libsecret-1-dev 13 | RUN npm i -g npm 14 | 15 | RUN npm install serverless -g 16 | RUN npm config set prefix '~/.npm-global' 17 | RUN export PATH=~/.npm-global/bin:$PATH 18 | 19 | USER jenkins 20 | -------------------------------------------------------------------------------- /Chapter04/New folder/README.md: -------------------------------------------------------------------------------- 1 | # Hello World with CI/CD 2 | 3 | A serverless hello world app built by CI/CD process. 4 | 5 | [![CircleCI](https://circleci.com/gh/rupakg/hello-world-ci/tree/master.svg?style=svg)](https://circleci.com/gh/rupakg/hello-world-ci/tree/master) 6 | 7 | ## Install Jest 8 | 9 | ``` npm install -g jest ``` 10 | 11 | ## Run Tests 12 | 13 | ``` npm test --coverage ``` 14 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/azure-helloworld-ci/handler.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci/handler.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / azure-helloworld-ci handler.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 5/5 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 5/5 41 |
42 |
43 |
44 |
45 |

 46 | 
 98 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18  64 |   65 | 1x 66 |   67 | 1x 68 |   69 | 1x 70 |   71 | 1x 72 |   73 |   74 |   75 |   76 |   77 |   78 |   79 | 1x 80 |  
'use strict';
 81 |  
 82 | const HelloWorld = require('./lib/hello-world');
 83 |  
 84 | module.exports.helloWorld = (event, context, callback) => {
 85 |  
 86 |   var hlloWorld = new HelloWorld();
 87 |  
 88 |   const response = {
 89 |     statusCode: 200,
 90 |     headers: {
 91 |       'Access-Control-Allow-Origin': '*', // Required for CORS support to work
 92 |     },
 93 |     body: JSON.stringify(hlloWorld.sayHello(event)),
 94 |   };
 95 |  
 96 |   callback(null, response);
 97 | };
99 |
100 |
101 | 105 | 106 | 107 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/azure-helloworld-ci/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files azure-helloworld-ci 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 5/5 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 5/5 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 |
FileStatementsBranchesFunctionsLines
handler.js
100%5/5100%0/0100%1/1100%5/5
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/azure-helloworld-ci/lib/hello-world.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci/lib/hello-world.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / azure-helloworld-ci/lib hello-world.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 2/2 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 2/2 41 |
42 |
43 |
44 |
45 |

46 | 
74 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10  56 |   57 | 2x 58 |   59 |   60 |   61 |   62 |   63 |   64 | 1x
class HelloWorld {
65 |     sayHello(event) {
66 |         return {
67 |             message: 'Your Azure function executed successfully!',
68 |             input: event,
69 |         };
70 |     }
71 | }
72 |     
73 | module.exports = HelloWorld;
75 |
76 |
77 | 81 | 82 | 83 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/azure-helloworld-ci/lib/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci/lib 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files azure-helloworld-ci/lib 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 2/2 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 2/2 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 |
FileStatementsBranchesFunctionsLines
hello-world.js
100%2/2100%0/0100%1/1100%2/2
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/coverage-final.json: -------------------------------------------------------------------------------- 1 | {"/Users/shashi/Documents/azure-helloworld-ci/handler.js": {"path":"/Users/shashi/Documents/azure-helloworld-ci/handler.js","statementMap":{"0":{"start":{"line":3,"column":19},"end":{"line":3,"column":47}},"1":{"start":{"line":5,"column":0},"end":{"line":18,"column":2}},"2":{"start":{"line":7,"column":18},"end":{"line":7,"column":34}},"3":{"start":{"line":9,"column":19},"end":{"line":15,"column":3}},"4":{"start":{"line":17,"column":2},"end":{"line":17,"column":27}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":5,"column":28},"end":{"line":5,"column":29}},"loc":{"start":{"line":5,"column":58},"end":{"line":18,"column":1}},"line":5}},"branchMap":{},"s":{"0":1,"1":1,"2":1,"3":1,"4":1},"f":{"0":1},"b":{},"_coverageSchema":"332fd63041d2c1bcb487cc26dd0d5f7d97098a6c","hash":"d6dd0a873707bf152a031e62bee8545749c6e18a"} 2 | ,"/Users/shashi/Documents/azure-helloworld-ci/lib/hello-world.js": {"path":"/Users/shashi/Documents/azure-helloworld-ci/lib/hello-world.js","statementMap":{"0":{"start":{"line":3,"column":8},"end":{"line":6,"column":10}},"1":{"start":{"line":10,"column":0},"end":{"line":10,"column":28}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":2,"column":4},"end":{"line":2,"column":5}},"loc":{"start":{"line":2,"column":20},"end":{"line":7,"column":5}},"line":2}},"branchMap":{},"s":{"0":2,"1":1},"f":{"0":2},"b":{},"_coverageSchema":"332fd63041d2c1bcb487cc26dd0d5f7d97098a6c","hash":"346183194d0a773d6bf51dd378af68e887d8eb01"} 3 | } 4 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for All files 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 7/7 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 2/2 36 |
37 |
38 | 100% 39 | Lines 40 | 7/7 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 |
FileStatementsBranchesFunctionsLines
azure-helloworld-ci
100%5/5100%0/0100%1/1100%5/5
azure-helloworld-ci/lib
100%2/2100%0/0100%1/1100%2/2
89 |
90 |
91 | 95 | 96 | 97 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/lcov-report/azure-helloworld-ci/handler.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci/handler.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / azure-helloworld-ci handler.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 5/5 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 5/5 41 |
42 |
43 |
44 |
45 |

 46 | 
 98 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18  64 |   65 | 1x 66 |   67 | 1x 68 |   69 | 1x 70 |   71 | 1x 72 |   73 |   74 |   75 |   76 |   77 |   78 |   79 | 1x 80 |  
'use strict';
 81 |  
 82 | const HelloWorld = require('./lib/hello-world');
 83 |  
 84 | module.exports.helloWorld = (event, context, callback) => {
 85 |  
 86 |   var hlloWorld = new HelloWorld();
 87 |  
 88 |   const response = {
 89 |     statusCode: 200,
 90 |     headers: {
 91 |       'Access-Control-Allow-Origin': '*', // Required for CORS support to work
 92 |     },
 93 |     body: JSON.stringify(hlloWorld.sayHello(event)),
 94 |   };
 95 |  
 96 |   callback(null, response);
 97 | };
99 |
100 |
101 | 105 | 106 | 107 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/lcov-report/azure-helloworld-ci/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files azure-helloworld-ci 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 5/5 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 5/5 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 |
FileStatementsBranchesFunctionsLines
handler.js
100%5/5100%0/0100%1/1100%5/5
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/lcov-report/azure-helloworld-ci/lib/hello-world.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci/lib/hello-world.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / azure-helloworld-ci/lib hello-world.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 2/2 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 2/2 41 |
42 |
43 |
44 |
45 |

46 | 
74 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10  56 |   57 | 2x 58 |   59 |   60 |   61 |   62 |   63 |   64 | 1x
class HelloWorld {
65 |     sayHello(event) {
66 |         return {
67 |             message: 'Your Azure function executed successfully!',
68 |             input: event,
69 |         };
70 |     }
71 | }
72 |     
73 | module.exports = HelloWorld;
75 |
76 |
77 | 81 | 82 | 83 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/lcov-report/azure-helloworld-ci/lib/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci/lib 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files azure-helloworld-ci/lib 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 2/2 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 2/2 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 |
FileStatementsBranchesFunctionsLines
hello-world.js
100%2/2100%0/0100%1/1100%2/2
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/lcov-report/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for All files 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 7/7 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 2/2 36 |
37 |
38 | 100% 39 | Lines 40 | 7/7 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 |
FileStatementsBranchesFunctionsLines
azure-helloworld-ci
100%5/5100%0/0100%1/1100%5/5
azure-helloworld-ci/lib
100%2/2100%0/0100%1/1100%2/2
89 |
90 |
91 | 95 | 96 | 97 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/DevOps-for-Serverless-Applications/d7a53f0434253faf4ad638e00f2df398a5dbc155/Chapter04/New folder/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | var addSorting = (function () { 2 | "use strict"; 3 | var cols, 4 | currentSort = { 5 | index: 0, 6 | desc: false 7 | }; 8 | 9 | // returns the summary table element 10 | function getTable() { return document.querySelector('.coverage-summary'); } 11 | // returns the thead element of the summary table 12 | function getTableHeader() { return getTable().querySelector('thead tr'); } 13 | // returns the tbody element of the summary table 14 | function getTableBody() { return getTable().querySelector('tbody'); } 15 | // returns the th element for nth column 16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } 17 | 18 | // loads all columns 19 | function loadColumns() { 20 | var colNodes = getTableHeader().querySelectorAll('th'), 21 | colNode, 22 | cols = [], 23 | col, 24 | i; 25 | 26 | for (i = 0; i < colNodes.length; i += 1) { 27 | colNode = colNodes[i]; 28 | col = { 29 | key: colNode.getAttribute('data-col'), 30 | sortable: !colNode.getAttribute('data-nosort'), 31 | type: colNode.getAttribute('data-type') || 'string' 32 | }; 33 | cols.push(col); 34 | if (col.sortable) { 35 | col.defaultDescSort = col.type === 'number'; 36 | colNode.innerHTML = colNode.innerHTML + ''; 37 | } 38 | } 39 | return cols; 40 | } 41 | // attaches a data attribute to every tr element with an object 42 | // of data values keyed by column name 43 | function loadRowData(tableRow) { 44 | var tableCols = tableRow.querySelectorAll('td'), 45 | colNode, 46 | col, 47 | data = {}, 48 | i, 49 | val; 50 | for (i = 0; i < tableCols.length; i += 1) { 51 | colNode = tableCols[i]; 52 | col = cols[i]; 53 | val = colNode.getAttribute('data-value'); 54 | if (col.type === 'number') { 55 | val = Number(val); 56 | } 57 | data[col.key] = val; 58 | } 59 | return data; 60 | } 61 | // loads all row data 62 | function loadData() { 63 | var rows = getTableBody().querySelectorAll('tr'), 64 | i; 65 | 66 | for (i = 0; i < rows.length; i += 1) { 67 | rows[i].data = loadRowData(rows[i]); 68 | } 69 | } 70 | // sorts the table using the data for the ith column 71 | function sortByIndex(index, desc) { 72 | var key = cols[index].key, 73 | sorter = function (a, b) { 74 | a = a.data[key]; 75 | b = b.data[key]; 76 | return a < b ? -1 : a > b ? 1 : 0; 77 | }, 78 | finalSorter = sorter, 79 | tableBody = document.querySelector('.coverage-summary tbody'), 80 | rowNodes = tableBody.querySelectorAll('tr'), 81 | rows = [], 82 | i; 83 | 84 | if (desc) { 85 | finalSorter = function (a, b) { 86 | return -1 * sorter(a, b); 87 | }; 88 | } 89 | 90 | for (i = 0; i < rowNodes.length; i += 1) { 91 | rows.push(rowNodes[i]); 92 | tableBody.removeChild(rowNodes[i]); 93 | } 94 | 95 | rows.sort(finalSorter); 96 | 97 | for (i = 0; i < rows.length; i += 1) { 98 | tableBody.appendChild(rows[i]); 99 | } 100 | } 101 | // removes sort indicators for current column being sorted 102 | function removeSortIndicators() { 103 | var col = getNthColumn(currentSort.index), 104 | cls = col.className; 105 | 106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 107 | col.className = cls; 108 | } 109 | // adds sort indicators for current column being sorted 110 | function addSortIndicators() { 111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; 112 | } 113 | // adds event listeners for all sorter widgets 114 | function enableUI() { 115 | var i, 116 | el, 117 | ithSorter = function ithSorter(i) { 118 | var col = cols[i]; 119 | 120 | return function () { 121 | var desc = col.defaultDescSort; 122 | 123 | if (currentSort.index === i) { 124 | desc = !currentSort.desc; 125 | } 126 | sortByIndex(i, desc); 127 | removeSortIndicators(); 128 | currentSort.index = i; 129 | currentSort.desc = desc; 130 | addSortIndicators(); 131 | }; 132 | }; 133 | for (i =0 ; i < cols.length; i += 1) { 134 | if (cols[i].sortable) { 135 | // add the click event handler on the th so users 136 | // dont have to click on those tiny arrows 137 | el = getNthColumn(i).querySelector('.sorter').parentElement; 138 | if (el.addEventListener) { 139 | el.addEventListener('click', ithSorter(i)); 140 | } else { 141 | el.attachEvent('onclick', ithSorter(i)); 142 | } 143 | } 144 | } 145 | } 146 | // adds sorting functionality to the UI 147 | return function () { 148 | if (!getTable()) { 149 | return; 150 | } 151 | cols = loadColumns(); 152 | loadData(cols); 153 | addSortIndicators(); 154 | enableUI(); 155 | }; 156 | })(); 157 | 158 | window.addEventListener('load', addSorting); 159 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/lcov.info: -------------------------------------------------------------------------------- 1 | TN: 2 | SF:/Users/shashi/Documents/azure-helloworld-ci/handler.js 3 | FN:5,(anonymous_0) 4 | FNF:1 5 | FNH:1 6 | FNDA:1,(anonymous_0) 7 | DA:3,1 8 | DA:5,1 9 | DA:7,1 10 | DA:9,1 11 | DA:17,1 12 | LF:5 13 | LH:5 14 | BRF:0 15 | BRH:0 16 | end_of_record 17 | TN: 18 | SF:/Users/shashi/Documents/azure-helloworld-ci/lib/hello-world.js 19 | FN:2,(anonymous_0) 20 | FNF:1 21 | FNH:1 22 | FNDA:2,(anonymous_0) 23 | DA:3,2 24 | DA:10,1 25 | LF:2 26 | LH:2 27 | BRF:0 28 | BRH:0 29 | end_of_record 30 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/DevOps-for-Serverless-Applications/d7a53f0434253faf4ad638e00f2df398a5dbc155/Chapter04/New folder/coverage/sort-arrow-sprite.png -------------------------------------------------------------------------------- /Chapter04/New folder/coverage/sorter.js: -------------------------------------------------------------------------------- 1 | var addSorting = (function () { 2 | "use strict"; 3 | var cols, 4 | currentSort = { 5 | index: 0, 6 | desc: false 7 | }; 8 | 9 | // returns the summary table element 10 | function getTable() { return document.querySelector('.coverage-summary'); } 11 | // returns the thead element of the summary table 12 | function getTableHeader() { return getTable().querySelector('thead tr'); } 13 | // returns the tbody element of the summary table 14 | function getTableBody() { return getTable().querySelector('tbody'); } 15 | // returns the th element for nth column 16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } 17 | 18 | // loads all columns 19 | function loadColumns() { 20 | var colNodes = getTableHeader().querySelectorAll('th'), 21 | colNode, 22 | cols = [], 23 | col, 24 | i; 25 | 26 | for (i = 0; i < colNodes.length; i += 1) { 27 | colNode = colNodes[i]; 28 | col = { 29 | key: colNode.getAttribute('data-col'), 30 | sortable: !colNode.getAttribute('data-nosort'), 31 | type: colNode.getAttribute('data-type') || 'string' 32 | }; 33 | cols.push(col); 34 | if (col.sortable) { 35 | col.defaultDescSort = col.type === 'number'; 36 | colNode.innerHTML = colNode.innerHTML + ''; 37 | } 38 | } 39 | return cols; 40 | } 41 | // attaches a data attribute to every tr element with an object 42 | // of data values keyed by column name 43 | function loadRowData(tableRow) { 44 | var tableCols = tableRow.querySelectorAll('td'), 45 | colNode, 46 | col, 47 | data = {}, 48 | i, 49 | val; 50 | for (i = 0; i < tableCols.length; i += 1) { 51 | colNode = tableCols[i]; 52 | col = cols[i]; 53 | val = colNode.getAttribute('data-value'); 54 | if (col.type === 'number') { 55 | val = Number(val); 56 | } 57 | data[col.key] = val; 58 | } 59 | return data; 60 | } 61 | // loads all row data 62 | function loadData() { 63 | var rows = getTableBody().querySelectorAll('tr'), 64 | i; 65 | 66 | for (i = 0; i < rows.length; i += 1) { 67 | rows[i].data = loadRowData(rows[i]); 68 | } 69 | } 70 | // sorts the table using the data for the ith column 71 | function sortByIndex(index, desc) { 72 | var key = cols[index].key, 73 | sorter = function (a, b) { 74 | a = a.data[key]; 75 | b = b.data[key]; 76 | return a < b ? -1 : a > b ? 1 : 0; 77 | }, 78 | finalSorter = sorter, 79 | tableBody = document.querySelector('.coverage-summary tbody'), 80 | rowNodes = tableBody.querySelectorAll('tr'), 81 | rows = [], 82 | i; 83 | 84 | if (desc) { 85 | finalSorter = function (a, b) { 86 | return -1 * sorter(a, b); 87 | }; 88 | } 89 | 90 | for (i = 0; i < rowNodes.length; i += 1) { 91 | rows.push(rowNodes[i]); 92 | tableBody.removeChild(rowNodes[i]); 93 | } 94 | 95 | rows.sort(finalSorter); 96 | 97 | for (i = 0; i < rows.length; i += 1) { 98 | tableBody.appendChild(rows[i]); 99 | } 100 | } 101 | // removes sort indicators for current column being sorted 102 | function removeSortIndicators() { 103 | var col = getNthColumn(currentSort.index), 104 | cls = col.className; 105 | 106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 107 | col.className = cls; 108 | } 109 | // adds sort indicators for current column being sorted 110 | function addSortIndicators() { 111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; 112 | } 113 | // adds event listeners for all sorter widgets 114 | function enableUI() { 115 | var i, 116 | el, 117 | ithSorter = function ithSorter(i) { 118 | var col = cols[i]; 119 | 120 | return function () { 121 | var desc = col.defaultDescSort; 122 | 123 | if (currentSort.index === i) { 124 | desc = !currentSort.desc; 125 | } 126 | sortByIndex(i, desc); 127 | removeSortIndicators(); 128 | currentSort.index = i; 129 | currentSort.desc = desc; 130 | addSortIndicators(); 131 | }; 132 | }; 133 | for (i =0 ; i < cols.length; i += 1) { 134 | if (cols[i].sortable) { 135 | // add the click event handler on the th so users 136 | // dont have to click on those tiny arrows 137 | el = getNthColumn(i).querySelector('.sorter').parentElement; 138 | if (el.addEventListener) { 139 | el.addEventListener('click', ithSorter(i)); 140 | } else { 141 | el.attachEvent('onclick', ithSorter(i)); 142 | } 143 | } 144 | } 145 | } 146 | // adds sorting functionality to the UI 147 | return function () { 148 | if (!getTable()) { 149 | return; 150 | } 151 | cols = loadColumns(); 152 | loadData(cols); 153 | addSortIndicators(); 154 | enableUI(); 155 | }; 156 | })(); 157 | 158 | window.addEventListener('load', addSorting); 159 | -------------------------------------------------------------------------------- /Chapter04/New folder/handler.js: -------------------------------------------------------------------------------- 1 | const HelloWorld = require('./lib/hello-world'); 2 | 3 | module.exports = function (context, req, event) { 4 | context.log('JavaScript HTTP trigger function processed a request.'); 5 | 6 | var hlloWorld = new HelloWorld(); 7 | 8 | if (req.query.name || (req.body && req.body.name)) { 9 | context.res = { 10 | // status: 200, /* Defaults to 200 */ 11 | // body: "Hello " + (req.query.name || req.body.name) 12 | body: "Hello "+ (req.query.name || req.body.name) + JSON.stringify(hlloWorld.sayHello(event)) 13 | }; 14 | } 15 | else { 16 | context.res = { 17 | status: 400, 18 | body: "Please pass a name on the query string or in the request body" 19 | }; 20 | } 21 | context.done(); 22 | }; 23 | -------------------------------------------------------------------------------- /Chapter04/New folder/lib/hello-world.js: -------------------------------------------------------------------------------- 1 | class HelloWorld { 2 | sayHello(event) { 3 | return { 4 | message: 'Your Azure function executed successfully!', 5 | input: event, 6 | }; 7 | } 8 | } 9 | 10 | module.exports = HelloWorld; -------------------------------------------------------------------------------- /Chapter04/New folder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "azure-helloworld-ci", 3 | "version": "1.0.0", 4 | "description": "Hello World with Testing", 5 | "main": "handler.js", 6 | "directories": { 7 | "lib": "lib", 8 | "test": "tests" 9 | }, 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "jest": "^21.2.1" 13 | }, 14 | "scripts": { 15 | "test": "jest" 16 | }, 17 | "jest": { 18 | "collectCoverage": true, 19 | "coverageReporters": [ 20 | "json", 21 | "lcov", 22 | "html", 23 | "text" 24 | ] 25 | }, 26 | "author": "Shashikant Bangera", 27 | "license": "MIT" 28 | } 29 | -------------------------------------------------------------------------------- /Chapter04/New folder/serverless.yml: -------------------------------------------------------------------------------- 1 | # Welcome to serverless. Read the docs 2 | # https://serverless.com/framework/docs/ 3 | 4 | # Serverless.yml is the configuration the CLI 5 | # uses to deploy your code to your provider of choice 6 | 7 | # The `service` block is the name of the service 8 | service: azure-helloworld-ci 9 | 10 | provider: 11 | name: azure 12 | location: West US 13 | 14 | plugins: 15 | - serverless-azure-functions 16 | 17 | # exclude the code coverage files and circle ci files 18 | package: 19 | exclude: 20 | - coverage/** 21 | 22 | functions: 23 | hello: 24 | handler: handler.hello 25 | events: 26 | - http: true 27 | x-azure-settings: 28 | authLevel : anonymous 29 | - http: true 30 | x-azure-settings: 31 | direction: out 32 | name: res -------------------------------------------------------------------------------- /Chapter04/New folder/tests/hello-world.spec.js: -------------------------------------------------------------------------------- 1 | const HelloWorld = require('../lib/hello-world'); 2 | 3 | describe('sayHello', () => { 4 | var event = {}; 5 | var hWorld = new HelloWorld(); 6 | 7 | it('should call sayHello and return message', () => { 8 | expect(hWorld.sayHello(event).message).toBe('Your Azure function executed successfully!'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /Chapter04/README.md: -------------------------------------------------------------------------------- 1 | # Hello World with CI/CD 2 | 3 | A serverless hello world app built by CI/CD process. 4 | 5 | [![CircleCI](https://circleci.com/gh/rupakg/hello-world-ci/tree/master.svg?style=svg)](https://circleci.com/gh/rupakg/hello-world-ci/tree/master) 6 | 7 | ## Install Jest 8 | 9 | ``` npm install -g jest ``` 10 | 11 | ## Run Tests 12 | 13 | ``` npm test --coverage ``` 14 | -------------------------------------------------------------------------------- /Chapter04/coverage/azure-helloworld-ci/handler.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci/handler.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / azure-helloworld-ci handler.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 5/5 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 5/5 41 |
42 |
43 |
44 |
45 |

 46 | 
 98 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18  64 |   65 | 1x 66 |   67 | 1x 68 |   69 | 1x 70 |   71 | 1x 72 |   73 |   74 |   75 |   76 |   77 |   78 |   79 | 1x 80 |  
'use strict';
 81 |  
 82 | const HelloWorld = require('./lib/hello-world');
 83 |  
 84 | module.exports.helloWorld = (event, context, callback) => {
 85 |  
 86 |   var hlloWorld = new HelloWorld();
 87 |  
 88 |   const response = {
 89 |     statusCode: 200,
 90 |     headers: {
 91 |       'Access-Control-Allow-Origin': '*', // Required for CORS support to work
 92 |     },
 93 |     body: JSON.stringify(hlloWorld.sayHello(event)),
 94 |   };
 95 |  
 96 |   callback(null, response);
 97 | };
99 |
100 |
101 | 105 | 106 | 107 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Chapter04/coverage/azure-helloworld-ci/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files azure-helloworld-ci 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 5/5 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 5/5 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 |
FileStatementsBranchesFunctionsLines
handler.js
100%5/5100%0/0100%1/1100%5/5
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Chapter04/coverage/azure-helloworld-ci/lib/hello-world.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci/lib/hello-world.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / azure-helloworld-ci/lib hello-world.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 2/2 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 2/2 41 |
42 |
43 |
44 |
45 |

46 | 
74 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10  56 |   57 | 2x 58 |   59 |   60 |   61 |   62 |   63 |   64 | 1x
class HelloWorld {
65 |     sayHello(event) {
66 |         return {
67 |             message: 'Your Azure function executed successfully!',
68 |             input: event,
69 |         };
70 |     }
71 | }
72 |     
73 | module.exports = HelloWorld;
75 |
76 |
77 | 81 | 82 | 83 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /Chapter04/coverage/azure-helloworld-ci/lib/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci/lib 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files azure-helloworld-ci/lib 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 2/2 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 2/2 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 |
FileStatementsBranchesFunctionsLines
hello-world.js
100%2/2100%0/0100%1/1100%2/2
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Chapter04/coverage/coverage-final.json: -------------------------------------------------------------------------------- 1 | {"/Users/shashi/Documents/azure-helloworld-ci/handler.js": {"path":"/Users/shashi/Documents/azure-helloworld-ci/handler.js","statementMap":{"0":{"start":{"line":3,"column":19},"end":{"line":3,"column":47}},"1":{"start":{"line":5,"column":0},"end":{"line":18,"column":2}},"2":{"start":{"line":7,"column":18},"end":{"line":7,"column":34}},"3":{"start":{"line":9,"column":19},"end":{"line":15,"column":3}},"4":{"start":{"line":17,"column":2},"end":{"line":17,"column":27}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":5,"column":28},"end":{"line":5,"column":29}},"loc":{"start":{"line":5,"column":58},"end":{"line":18,"column":1}},"line":5}},"branchMap":{},"s":{"0":1,"1":1,"2":1,"3":1,"4":1},"f":{"0":1},"b":{},"_coverageSchema":"332fd63041d2c1bcb487cc26dd0d5f7d97098a6c","hash":"d6dd0a873707bf152a031e62bee8545749c6e18a"} 2 | ,"/Users/shashi/Documents/azure-helloworld-ci/lib/hello-world.js": {"path":"/Users/shashi/Documents/azure-helloworld-ci/lib/hello-world.js","statementMap":{"0":{"start":{"line":3,"column":8},"end":{"line":6,"column":10}},"1":{"start":{"line":10,"column":0},"end":{"line":10,"column":28}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":2,"column":4},"end":{"line":2,"column":5}},"loc":{"start":{"line":2,"column":20},"end":{"line":7,"column":5}},"line":2}},"branchMap":{},"s":{"0":2,"1":1},"f":{"0":2},"b":{},"_coverageSchema":"332fd63041d2c1bcb487cc26dd0d5f7d97098a6c","hash":"346183194d0a773d6bf51dd378af68e887d8eb01"} 3 | } 4 | -------------------------------------------------------------------------------- /Chapter04/coverage/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for All files 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 7/7 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 2/2 36 |
37 |
38 | 100% 39 | Lines 40 | 7/7 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 |
FileStatementsBranchesFunctionsLines
azure-helloworld-ci
100%5/5100%0/0100%1/1100%5/5
azure-helloworld-ci/lib
100%2/2100%0/0100%1/1100%2/2
89 |
90 |
91 | 95 | 96 | 97 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /Chapter04/coverage/lcov-report/azure-helloworld-ci/handler.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci/handler.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / azure-helloworld-ci handler.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 5/5 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 5/5 41 |
42 |
43 |
44 |
45 |

 46 | 
 98 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18  64 |   65 | 1x 66 |   67 | 1x 68 |   69 | 1x 70 |   71 | 1x 72 |   73 |   74 |   75 |   76 |   77 |   78 |   79 | 1x 80 |  
'use strict';
 81 |  
 82 | const HelloWorld = require('./lib/hello-world');
 83 |  
 84 | module.exports.helloWorld = (event, context, callback) => {
 85 |  
 86 |   var hlloWorld = new HelloWorld();
 87 |  
 88 |   const response = {
 89 |     statusCode: 200,
 90 |     headers: {
 91 |       'Access-Control-Allow-Origin': '*', // Required for CORS support to work
 92 |     },
 93 |     body: JSON.stringify(hlloWorld.sayHello(event)),
 94 |   };
 95 |  
 96 |   callback(null, response);
 97 | };
99 |
100 |
101 | 105 | 106 | 107 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Chapter04/coverage/lcov-report/azure-helloworld-ci/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files azure-helloworld-ci 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 5/5 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 5/5 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 |
FileStatementsBranchesFunctionsLines
handler.js
100%5/5100%0/0100%1/1100%5/5
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Chapter04/coverage/lcov-report/azure-helloworld-ci/lib/hello-world.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci/lib/hello-world.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / azure-helloworld-ci/lib hello-world.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 2/2 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 2/2 41 |
42 |
43 |
44 |
45 |

46 | 
74 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10  56 |   57 | 2x 58 |   59 |   60 |   61 |   62 |   63 |   64 | 1x
class HelloWorld {
65 |     sayHello(event) {
66 |         return {
67 |             message: 'Your Azure function executed successfully!',
68 |             input: event,
69 |         };
70 |     }
71 | }
72 |     
73 | module.exports = HelloWorld;
75 |
76 |
77 | 81 | 82 | 83 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /Chapter04/coverage/lcov-report/azure-helloworld-ci/lib/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for azure-helloworld-ci/lib 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files azure-helloworld-ci/lib 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 2/2 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 1/1 36 |
37 |
38 | 100% 39 | Lines 40 | 2/2 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 |
FileStatementsBranchesFunctionsLines
hello-world.js
100%2/2100%0/0100%1/1100%2/2
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Chapter04/coverage/lcov-report/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for All files 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 7/7 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 2/2 36 |
37 |
38 | 100% 39 | Lines 40 | 7/7 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 |
FileStatementsBranchesFunctionsLines
azure-helloworld-ci
100%5/5100%0/0100%1/1100%5/5
azure-helloworld-ci/lib
100%2/2100%0/0100%1/1100%2/2
89 |
90 |
91 | 95 | 96 | 97 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /Chapter04/coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /Chapter04/coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/DevOps-for-Serverless-Applications/d7a53f0434253faf4ad638e00f2df398a5dbc155/Chapter04/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /Chapter04/coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | var addSorting = (function () { 2 | "use strict"; 3 | var cols, 4 | currentSort = { 5 | index: 0, 6 | desc: false 7 | }; 8 | 9 | // returns the summary table element 10 | function getTable() { return document.querySelector('.coverage-summary'); } 11 | // returns the thead element of the summary table 12 | function getTableHeader() { return getTable().querySelector('thead tr'); } 13 | // returns the tbody element of the summary table 14 | function getTableBody() { return getTable().querySelector('tbody'); } 15 | // returns the th element for nth column 16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } 17 | 18 | // loads all columns 19 | function loadColumns() { 20 | var colNodes = getTableHeader().querySelectorAll('th'), 21 | colNode, 22 | cols = [], 23 | col, 24 | i; 25 | 26 | for (i = 0; i < colNodes.length; i += 1) { 27 | colNode = colNodes[i]; 28 | col = { 29 | key: colNode.getAttribute('data-col'), 30 | sortable: !colNode.getAttribute('data-nosort'), 31 | type: colNode.getAttribute('data-type') || 'string' 32 | }; 33 | cols.push(col); 34 | if (col.sortable) { 35 | col.defaultDescSort = col.type === 'number'; 36 | colNode.innerHTML = colNode.innerHTML + ''; 37 | } 38 | } 39 | return cols; 40 | } 41 | // attaches a data attribute to every tr element with an object 42 | // of data values keyed by column name 43 | function loadRowData(tableRow) { 44 | var tableCols = tableRow.querySelectorAll('td'), 45 | colNode, 46 | col, 47 | data = {}, 48 | i, 49 | val; 50 | for (i = 0; i < tableCols.length; i += 1) { 51 | colNode = tableCols[i]; 52 | col = cols[i]; 53 | val = colNode.getAttribute('data-value'); 54 | if (col.type === 'number') { 55 | val = Number(val); 56 | } 57 | data[col.key] = val; 58 | } 59 | return data; 60 | } 61 | // loads all row data 62 | function loadData() { 63 | var rows = getTableBody().querySelectorAll('tr'), 64 | i; 65 | 66 | for (i = 0; i < rows.length; i += 1) { 67 | rows[i].data = loadRowData(rows[i]); 68 | } 69 | } 70 | // sorts the table using the data for the ith column 71 | function sortByIndex(index, desc) { 72 | var key = cols[index].key, 73 | sorter = function (a, b) { 74 | a = a.data[key]; 75 | b = b.data[key]; 76 | return a < b ? -1 : a > b ? 1 : 0; 77 | }, 78 | finalSorter = sorter, 79 | tableBody = document.querySelector('.coverage-summary tbody'), 80 | rowNodes = tableBody.querySelectorAll('tr'), 81 | rows = [], 82 | i; 83 | 84 | if (desc) { 85 | finalSorter = function (a, b) { 86 | return -1 * sorter(a, b); 87 | }; 88 | } 89 | 90 | for (i = 0; i < rowNodes.length; i += 1) { 91 | rows.push(rowNodes[i]); 92 | tableBody.removeChild(rowNodes[i]); 93 | } 94 | 95 | rows.sort(finalSorter); 96 | 97 | for (i = 0; i < rows.length; i += 1) { 98 | tableBody.appendChild(rows[i]); 99 | } 100 | } 101 | // removes sort indicators for current column being sorted 102 | function removeSortIndicators() { 103 | var col = getNthColumn(currentSort.index), 104 | cls = col.className; 105 | 106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 107 | col.className = cls; 108 | } 109 | // adds sort indicators for current column being sorted 110 | function addSortIndicators() { 111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; 112 | } 113 | // adds event listeners for all sorter widgets 114 | function enableUI() { 115 | var i, 116 | el, 117 | ithSorter = function ithSorter(i) { 118 | var col = cols[i]; 119 | 120 | return function () { 121 | var desc = col.defaultDescSort; 122 | 123 | if (currentSort.index === i) { 124 | desc = !currentSort.desc; 125 | } 126 | sortByIndex(i, desc); 127 | removeSortIndicators(); 128 | currentSort.index = i; 129 | currentSort.desc = desc; 130 | addSortIndicators(); 131 | }; 132 | }; 133 | for (i =0 ; i < cols.length; i += 1) { 134 | if (cols[i].sortable) { 135 | // add the click event handler on the th so users 136 | // dont have to click on those tiny arrows 137 | el = getNthColumn(i).querySelector('.sorter').parentElement; 138 | if (el.addEventListener) { 139 | el.addEventListener('click', ithSorter(i)); 140 | } else { 141 | el.attachEvent('onclick', ithSorter(i)); 142 | } 143 | } 144 | } 145 | } 146 | // adds sorting functionality to the UI 147 | return function () { 148 | if (!getTable()) { 149 | return; 150 | } 151 | cols = loadColumns(); 152 | loadData(cols); 153 | addSortIndicators(); 154 | enableUI(); 155 | }; 156 | })(); 157 | 158 | window.addEventListener('load', addSorting); 159 | -------------------------------------------------------------------------------- /Chapter04/coverage/lcov.info: -------------------------------------------------------------------------------- 1 | TN: 2 | SF:/Users/shashi/Documents/azure-helloworld-ci/handler.js 3 | FN:5,(anonymous_0) 4 | FNF:1 5 | FNH:1 6 | FNDA:1,(anonymous_0) 7 | DA:3,1 8 | DA:5,1 9 | DA:7,1 10 | DA:9,1 11 | DA:17,1 12 | LF:5 13 | LH:5 14 | BRF:0 15 | BRH:0 16 | end_of_record 17 | TN: 18 | SF:/Users/shashi/Documents/azure-helloworld-ci/lib/hello-world.js 19 | FN:2,(anonymous_0) 20 | FNF:1 21 | FNH:1 22 | FNDA:2,(anonymous_0) 23 | DA:3,2 24 | DA:10,1 25 | LF:2 26 | LH:2 27 | BRF:0 28 | BRH:0 29 | end_of_record 30 | -------------------------------------------------------------------------------- /Chapter04/coverage/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /Chapter04/coverage/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/DevOps-for-Serverless-Applications/d7a53f0434253faf4ad638e00f2df398a5dbc155/Chapter04/coverage/sort-arrow-sprite.png -------------------------------------------------------------------------------- /Chapter04/coverage/sorter.js: -------------------------------------------------------------------------------- 1 | var addSorting = (function () { 2 | "use strict"; 3 | var cols, 4 | currentSort = { 5 | index: 0, 6 | desc: false 7 | }; 8 | 9 | // returns the summary table element 10 | function getTable() { return document.querySelector('.coverage-summary'); } 11 | // returns the thead element of the summary table 12 | function getTableHeader() { return getTable().querySelector('thead tr'); } 13 | // returns the tbody element of the summary table 14 | function getTableBody() { return getTable().querySelector('tbody'); } 15 | // returns the th element for nth column 16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } 17 | 18 | // loads all columns 19 | function loadColumns() { 20 | var colNodes = getTableHeader().querySelectorAll('th'), 21 | colNode, 22 | cols = [], 23 | col, 24 | i; 25 | 26 | for (i = 0; i < colNodes.length; i += 1) { 27 | colNode = colNodes[i]; 28 | col = { 29 | key: colNode.getAttribute('data-col'), 30 | sortable: !colNode.getAttribute('data-nosort'), 31 | type: colNode.getAttribute('data-type') || 'string' 32 | }; 33 | cols.push(col); 34 | if (col.sortable) { 35 | col.defaultDescSort = col.type === 'number'; 36 | colNode.innerHTML = colNode.innerHTML + ''; 37 | } 38 | } 39 | return cols; 40 | } 41 | // attaches a data attribute to every tr element with an object 42 | // of data values keyed by column name 43 | function loadRowData(tableRow) { 44 | var tableCols = tableRow.querySelectorAll('td'), 45 | colNode, 46 | col, 47 | data = {}, 48 | i, 49 | val; 50 | for (i = 0; i < tableCols.length; i += 1) { 51 | colNode = tableCols[i]; 52 | col = cols[i]; 53 | val = colNode.getAttribute('data-value'); 54 | if (col.type === 'number') { 55 | val = Number(val); 56 | } 57 | data[col.key] = val; 58 | } 59 | return data; 60 | } 61 | // loads all row data 62 | function loadData() { 63 | var rows = getTableBody().querySelectorAll('tr'), 64 | i; 65 | 66 | for (i = 0; i < rows.length; i += 1) { 67 | rows[i].data = loadRowData(rows[i]); 68 | } 69 | } 70 | // sorts the table using the data for the ith column 71 | function sortByIndex(index, desc) { 72 | var key = cols[index].key, 73 | sorter = function (a, b) { 74 | a = a.data[key]; 75 | b = b.data[key]; 76 | return a < b ? -1 : a > b ? 1 : 0; 77 | }, 78 | finalSorter = sorter, 79 | tableBody = document.querySelector('.coverage-summary tbody'), 80 | rowNodes = tableBody.querySelectorAll('tr'), 81 | rows = [], 82 | i; 83 | 84 | if (desc) { 85 | finalSorter = function (a, b) { 86 | return -1 * sorter(a, b); 87 | }; 88 | } 89 | 90 | for (i = 0; i < rowNodes.length; i += 1) { 91 | rows.push(rowNodes[i]); 92 | tableBody.removeChild(rowNodes[i]); 93 | } 94 | 95 | rows.sort(finalSorter); 96 | 97 | for (i = 0; i < rows.length; i += 1) { 98 | tableBody.appendChild(rows[i]); 99 | } 100 | } 101 | // removes sort indicators for current column being sorted 102 | function removeSortIndicators() { 103 | var col = getNthColumn(currentSort.index), 104 | cls = col.className; 105 | 106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 107 | col.className = cls; 108 | } 109 | // adds sort indicators for current column being sorted 110 | function addSortIndicators() { 111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; 112 | } 113 | // adds event listeners for all sorter widgets 114 | function enableUI() { 115 | var i, 116 | el, 117 | ithSorter = function ithSorter(i) { 118 | var col = cols[i]; 119 | 120 | return function () { 121 | var desc = col.defaultDescSort; 122 | 123 | if (currentSort.index === i) { 124 | desc = !currentSort.desc; 125 | } 126 | sortByIndex(i, desc); 127 | removeSortIndicators(); 128 | currentSort.index = i; 129 | currentSort.desc = desc; 130 | addSortIndicators(); 131 | }; 132 | }; 133 | for (i =0 ; i < cols.length; i += 1) { 134 | if (cols[i].sortable) { 135 | // add the click event handler on the th so users 136 | // dont have to click on those tiny arrows 137 | el = getNthColumn(i).querySelector('.sorter').parentElement; 138 | if (el.addEventListener) { 139 | el.addEventListener('click', ithSorter(i)); 140 | } else { 141 | el.attachEvent('onclick', ithSorter(i)); 142 | } 143 | } 144 | } 145 | } 146 | // adds sorting functionality to the UI 147 | return function () { 148 | if (!getTable()) { 149 | return; 150 | } 151 | cols = loadColumns(); 152 | loadData(cols); 153 | addSortIndicators(); 154 | enableUI(); 155 | }; 156 | })(); 157 | 158 | window.addEventListener('load', addSorting); 159 | -------------------------------------------------------------------------------- /Chapter04/handler.js: -------------------------------------------------------------------------------- 1 | const HelloWorld = require('./lib/hello-world'); 2 | 3 | module.exports = function (context, req, event) { 4 | context.log('JavaScript HTTP trigger function processed a request.'); 5 | 6 | var hlloWorld = new HelloWorld(); 7 | 8 | if (req.query.name || (req.body && req.body.name)) { 9 | context.res = { 10 | // status: 200, /* Defaults to 200 */ 11 | // body: "Hello " + (req.query.name || req.body.name) 12 | body: "Hello "+ (req.query.name || req.body.name) + JSON.stringify(hlloWorld.sayHello(event)) 13 | }; 14 | } 15 | else { 16 | context.res = { 17 | status: 400, 18 | body: "Please pass a name on the query string or in the request body" 19 | }; 20 | } 21 | context.done(); 22 | }; 23 | -------------------------------------------------------------------------------- /Chapter04/lib/hello-world.js: -------------------------------------------------------------------------------- 1 | class HelloWorld { 2 | sayHello(event) { 3 | return { 4 | message: 'Your Azure function executed successfully!', 5 | input: event, 6 | }; 7 | } 8 | } 9 | 10 | module.exports = HelloWorld; -------------------------------------------------------------------------------- /Chapter04/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "azure-helloworld-ci", 3 | "version": "1.0.0", 4 | "description": "Hello World with Testing", 5 | "main": "handler.js", 6 | "directories": { 7 | "lib": "lib", 8 | "test": "tests" 9 | }, 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "jest": "^21.2.1" 13 | }, 14 | "scripts": { 15 | "test": "jest" 16 | }, 17 | "jest": { 18 | "collectCoverage": true, 19 | "coverageReporters": [ 20 | "json", 21 | "lcov", 22 | "html", 23 | "text" 24 | ] 25 | }, 26 | "author": "Shashikant Bangera", 27 | "license": "MIT" 28 | } 29 | -------------------------------------------------------------------------------- /Chapter04/serverless.yml: -------------------------------------------------------------------------------- 1 | # Welcome to serverless. Read the docs 2 | # https://serverless.com/framework/docs/ 3 | 4 | # Serverless.yml is the configuration the CLI 5 | # uses to deploy your code to your provider of choice 6 | 7 | # The `service` block is the name of the service 8 | service: azure-helloworld-ci 9 | 10 | provider: 11 | name: azure 12 | location: West US 13 | 14 | plugins: 15 | - serverless-azure-functions 16 | 17 | # exclude the code coverage files and circle ci files 18 | package: 19 | exclude: 20 | - coverage/** 21 | 22 | functions: 23 | hello: 24 | handler: handler.hello 25 | events: 26 | - http: true 27 | x-azure-settings: 28 | authLevel : anonymous 29 | - http: true 30 | x-azure-settings: 31 | direction: out 32 | name: res -------------------------------------------------------------------------------- /Chapter04/tests/hello-world.spec.js: -------------------------------------------------------------------------------- 1 | const HelloWorld = require('../lib/hello-world'); 2 | 3 | describe('sayHello', () => { 4 | var event = {}; 5 | var hWorld = new HelloWorld(); 6 | 7 | it('should call sayHello and return message', () => { 8 | expect(hWorld.sayHello(event).message).toBe('Your Azure function executed successfully!'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /Chapter05/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jenkins:latest 2 | USER root 3 | 4 | # Install Node.js 5 | RUN apt-get update -y 6 | RUN apt-get install -y apt-utils 7 | RUN apt-get install -y sudo 8 | RUN apt-get install --yes curl 9 | RUN curl --silent --location https://deb.nodesource.com/setup_8.x | bash - 10 | RUN apt-get install --yes nodejs 11 | RUN apt-get install --yes build-essential 12 | RUN npm i -g npm 13 | 14 | RUN npm install serverless -g 15 | RUN npm install --global serverless serverless-openwhisk 16 | 17 | RUN npm config set prefix '~/.npm-global' 18 | RUN export PATH=~/.npm-global/bin:$PATH 19 | RUN npm i --save serverless-openwhisk 20 | 21 | USER jenkins -------------------------------------------------------------------------------- /Chapter05/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | triggers { 5 | pollSCM('*/5 * * * *') 6 | } 7 | 8 | stages { 9 | 10 | stage('Unit Tests') { 11 | steps { 12 | sh '''npm install; 13 | ./node_modules/mocha/bin/mocha test/test.js 14 | ''' 15 | } 16 | } 17 | stage('Deploy to Dev') { 18 | environment { 19 | OW_AUTH = credentials('OPENWHISK_DEV_AUTH') 20 | OW_APIHOST = credentials('OW_APIHOST') 21 | } 22 | steps { 23 | sh '''serverless deploy -v''' 24 | } 25 | } 26 | stage('Deploy to SIT') { 27 | environment { 28 | OW_AUTH = credentials('OPENWHISK_SIT_AUTH') 29 | OW_APIHOST = credentials('OW_APIHOST') 30 | } 31 | steps { 32 | sh '''serverless deploy -v''' 33 | } 34 | } 35 | stage('Deploy to Pre-Prod') { 36 | environment { 37 | OW_AUTH = credentials('OPENWHISK_PRE_PROD_AUTH') 38 | OW_APIHOST = credentials('OW_APIHOST') 39 | } 40 | steps { 41 | sh '''serverless deploy -v''' 42 | } 43 | } 44 | stage('Promotion') { 45 | steps { 46 | timeout(time: 1, unit:'DAYS') { 47 | input 'Deploy to Production?' 48 | } 49 | } 50 | } 51 | stage('Deploy to Production') { 52 | environment { 53 | OW_AUTH = credentials('OPENWHISK_PROD_AUTH') 54 | OW_APIHOST = credentials('OW_APIHOST') 55 | } 56 | steps { 57 | sh '''serverless deploy -v''' 58 | } 59 | } 60 | } 61 | post { 62 | failure { 63 | mail to: 'shzshi@gmail.com', subject: 'Build failed', body: 'Please fix!' 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Chapter05/New folder/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jenkins:latest 2 | USER root 3 | 4 | # Install Node.js 5 | RUN apt-get update -y 6 | RUN apt-get install -y apt-utils 7 | RUN apt-get install -y sudo 8 | RUN apt-get install --yes curl 9 | RUN curl --silent --location https://deb.nodesource.com/setup_8.x | bash - 10 | RUN apt-get install --yes nodejs 11 | RUN apt-get install --yes build-essential 12 | RUN npm i -g npm 13 | 14 | RUN npm install serverless -g 15 | RUN npm install --global serverless serverless-openwhisk 16 | 17 | RUN npm config set prefix '~/.npm-global' 18 | RUN export PATH=~/.npm-global/bin:$PATH 19 | RUN npm i --save serverless-openwhisk 20 | 21 | USER jenkins -------------------------------------------------------------------------------- /Chapter05/New folder/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | triggers { 5 | pollSCM('*/5 * * * *') 6 | } 7 | 8 | stages { 9 | 10 | stage('Unit Tests') { 11 | steps { 12 | sh '''npm install; 13 | ./node_modules/mocha/bin/mocha test/test.js 14 | ''' 15 | } 16 | } 17 | stage('Deploy to Dev') { 18 | environment { 19 | OW_AUTH = credentials('OPENWHISK_DEV_AUTH') 20 | OW_APIHOST = credentials('OW_APIHOST') 21 | } 22 | steps { 23 | sh '''serverless deploy -v''' 24 | } 25 | } 26 | stage('Deploy to SIT') { 27 | environment { 28 | OW_AUTH = credentials('OPENWHISK_SIT_AUTH') 29 | OW_APIHOST = credentials('OW_APIHOST') 30 | } 31 | steps { 32 | sh '''serverless deploy -v''' 33 | } 34 | } 35 | stage('Deploy to Pre-Prod') { 36 | environment { 37 | OW_AUTH = credentials('OPENWHISK_PRE_PROD_AUTH') 38 | OW_APIHOST = credentials('OW_APIHOST') 39 | } 40 | steps { 41 | sh '''serverless deploy -v''' 42 | } 43 | } 44 | stage('Promotion') { 45 | steps { 46 | timeout(time: 1, unit:'DAYS') { 47 | input 'Deploy to Production?' 48 | } 49 | } 50 | } 51 | stage('Deploy to Production') { 52 | environment { 53 | OW_AUTH = credentials('OPENWHISK_PROD_AUTH') 54 | OW_APIHOST = credentials('OW_APIHOST') 55 | } 56 | steps { 57 | sh '''serverless deploy -v''' 58 | } 59 | } 60 | } 61 | post { 62 | failure { 63 | mail to: 'shzshi@gmail.com', subject: 'Build failed', body: 'Please fix!' 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Chapter05/New folder/README.md: -------------------------------------------------------------------------------- 1 | # Serverless OpenWhisk Node.js Template 2 | 3 | Hello! 😎 4 | 5 | This is a template Node.js service for the OpenWhisk platform. Before you can deploy your service, please follow the instructions below… 6 | 7 | ### Have you set up your account credentials? 8 | 9 | Before you can deploy your service to OpenWhisk, you need to have an account registered with the platform. 10 | 11 | - *Want to run the platform locally?* Please read the project's [*Quick Start*](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. 12 | - *Want to use a hosted provider?* Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). 13 | 14 | Account credentials for OpenWhisk can be provided through a configuration file or environment variables. This plugin requires the API endpoint, namespace and authentication credentials. 15 | 16 | **Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. 17 | 18 | **Do you want to use environment variables for credentials?** Use the following environment variables to be pass in account credentials. These values override anything extracted from the configuration file. 19 | 20 | - *OW_APIHOST* - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` 21 | - *OW_AUTH* - Authentication key, e.g. `xxxxxx:yyyyy 22 | 23 | 24 | 25 | ### Have you installed and setup the provider plugin? 26 | 27 | Using the framework with the OpenWhisk platform needs you to install the provider plugin and link this to your service. 28 | 29 | #### Install the provider plugin 30 | 31 | ``` 32 | $ npm install --global serverless-openwhisk 33 | ``` 34 | 35 | *Due to an [outstanding issue](https://github.com/serverless/serverless/issues/2895) with provider plugins, the [OpenWhisk provider](https://github.com/serverless/serverless-openwhisk) must be installed as a global module.* 36 | 37 | 38 | #### Link provider plugin to service directory 39 | 40 | Using `npm link` will import the provider plugin into the service directory. Running `npm install` will automatically perform this using a `post install` script. 41 | 42 | ``` 43 | $ npm link serverless-openwhisk 44 | or 45 | $ npm install 46 | ``` 47 | 48 | 49 | 50 | **_…and that's it!_** 51 | 52 | ### Deploy Service 53 | 54 | Use the `serverless` command to deploy your service. The sample `handler.js` file can be deployed without modification. 55 | 56 | ```shell 57 | serverless deploy 58 | ``` 59 | 60 | 61 | 62 | ### Issues / Feedback / Feature Requests? 63 | 64 | If you have any issues, comments or want to see new features, please file an issue in the project repository: 65 | 66 | https://github.com/serverless/serverless-openwhisk -------------------------------------------------------------------------------- /Chapter05/New folder/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * main() will be invoked when you Run This Action. 5 | * 6 | * When enabled as a Web Action, use the following URL to invoke this action: 7 | * https://{APIHOST}/api/v1/web/{QUALIFIED ACTION NAME}?location=Austin 8 | * 9 | * For example: 10 | * https://openwhisk.ng.bluemix.net/api/v1/web/myusername@us.ibm.com_myspace/get-resource/weather?location=Austin 11 | * 12 | * In this case, the params variable will look like: 13 | * { "location": "Austin" } 14 | * 15 | */ 16 | 17 | var request = require('request'); 18 | 19 | function main(params) { 20 | var location = params.location || 'Vermont'; 21 | var url = 'https://query.yahooapis.com/v1/public/yql?q=select item.condition from weather.forecast \ 22 | where woeid in (select woeid from geo.places(1) where text="' + location + '")&format=json'; 23 | return new Promise(function(resolve, reject) { 24 | request.get(url, function(error, response, body) { 25 | if (error) { 26 | reject({ 27 | statusCode: 500, 28 | headers: { 'Content-Type': 'application/json' }, 29 | body: {'message': 'Error processing your request'} 30 | }); 31 | } 32 | else { 33 | /** The response body contains temperature data in the following format 34 | * { code: '28', 35 | * date: 'Tue, 26 Dec 2017 12:00 PM EST', 36 | * temp: '18', 37 | * text: 'Mostly Cloudy' } } 38 | */ 39 | resolve({ 40 | statusCode: 200, 41 | headers: { 'Content-Type': 'application/json' }, 42 | body: JSON.parse(body).query.results.channel.item.condition 43 | }); 44 | } 45 | }); 46 | }); 47 | } 48 | 49 | exports.main = main; -------------------------------------------------------------------------------- /Chapter05/New folder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openwhisk-nodejs", 3 | "version": "1.0.0", 4 | "description": "Sample OpenWhisk NodeJS serverless framework service.", 5 | "main": "handler.js", 6 | "scripts": { 7 | "postinstall": "npm link serverless-openwhisk" 8 | }, 9 | "devDependencies": { 10 | "mocha": "latest", 11 | "openwhisk": "latest", 12 | "eslint": "latest", 13 | "request": "latest" 14 | }, 15 | "keywords": [ 16 | "serverless", 17 | "openwhisk", 18 | "CD/CI" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /Chapter05/New folder/serverless.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Serverless! 2 | # 3 | # This file is the main config file for your service. 4 | # It's very minimal at this point and uses default values. 5 | # You can always add more config options for more control. 6 | # We've included some commented out config examples here. 7 | # Just uncomment any of them to get that config option. 8 | # 9 | # For full config options, check the docs: 10 | # docs.serverless.com 11 | # 12 | # Happy Coding! 13 | 14 | service: weatherReport 15 | 16 | # Please ensure the serverless-openwhisk provider plugin is installed globally. 17 | # $ npm install -g serverless-openwhisk 18 | # ...before installing project dependencies to register this provider. 19 | # $ npm install 20 | provider: 21 | name: openwhisk 22 | 23 | # you can add packaging information here 24 | #package: 25 | # include: 26 | # - include-me.js 27 | # - include-me-dir/** 28 | # exclude: 29 | # - exclude-me.js 30 | # - exclude-me-dir/** 31 | 32 | functions: 33 | main: 34 | handler: handler.main 35 | 36 | # Functions can be defined using sequences rather than referring 37 | # to a handler. 38 | # sequence: 39 | # - parse_input 40 | # - do_some_algorithm 41 | # - construct_output 42 | 43 | # The following are a few example events you can configure 44 | # Check the event documentation for details 45 | # events: 46 | # - http: GET /api/users/create 47 | # - trigger: trigger_name 48 | 49 | 50 | # extend the framework using plugins listed here: 51 | # https://github.com/serverless/plugins 52 | plugins: 53 | - serverless-openwhisk 54 | 55 | # you can define custom triggers and trigger feeds using the resources section. 56 | # 57 | #resources: 58 | # triggers: 59 | # my_trigger: 60 | # parameters: 61 | # hello: world 62 | # alarm_trigger: 63 | # parameters: 64 | # hello: world 65 | # feed: /whisk.system/alarms/alarm 66 | # feed_parameters: 67 | # cron: '*/8 * * * * *' 68 | -------------------------------------------------------------------------------- /Chapter05/New folder/test/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var weather = require('../handler.js'); 3 | var openwhisk = require('openwhisk'); 4 | var ow; 5 | 6 | /** 7 | * Create a function which delegates to openwhisk to run a function f 8 | */ 9 | function makeAdapter(f) { 10 | return function(params) { 11 | return ow.actions.invoke({name: f, 12 | blocking: true, 13 | result:true, 14 | params:params}); 15 | }; 16 | } 17 | 18 | /** 19 | * For each function in an object, create an openwhisk adapter. 20 | * return an object with each adapter function. 21 | */ 22 | function adapt(obj) { 23 | var adapter= {} 24 | for (var p in obj) { 25 | adapter[p] = makeAdapter(p) 26 | } 27 | return adapter; 28 | } 29 | 30 | 31 | describe('handler', function() { 32 | 33 | before( function() { 34 | if (process.env.TEST_OPENWHISK) { 35 | options = { apihost: process.env.OPENWHISK_HOST, 36 | api_key: process.env.OW_AUTH_KEY }; 37 | ow = openwhisk(options); 38 | hello = adapt(hello,ow); 39 | } 40 | }); 41 | 42 | describe('handler', function() { 43 | it('should throw an error when location is not present', function() { 44 | var params = {} 45 | return weather.main(params).then(function(result) { 46 | assert(false); 47 | }).catch(function(err) { 48 | assert(true); 49 | }); 50 | }); 51 | }); 52 | 53 | describe('hello', function() { 54 | it('should return code as 29 for location as paris!', function() { 55 | var params = { 'location': 'paris' }; 56 | 57 | return weather.main(params).then(function(result) { 58 | assert.notEqual(result.body.message,"Error processing your request"); 59 | assert.equal(result.body.code,"29"); 60 | }) 61 | }); 62 | }); 63 | 64 | }); 65 | -------------------------------------------------------------------------------- /Chapter05/README.md: -------------------------------------------------------------------------------- 1 | # Serverless OpenWhisk Node.js Template 2 | 3 | Hello! 😎 4 | 5 | This is a template Node.js service for the OpenWhisk platform. Before you can deploy your service, please follow the instructions below… 6 | 7 | ### Have you set up your account credentials? 8 | 9 | Before you can deploy your service to OpenWhisk, you need to have an account registered with the platform. 10 | 11 | - *Want to run the platform locally?* Please read the project's [*Quick Start*](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. 12 | - *Want to use a hosted provider?* Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). 13 | 14 | Account credentials for OpenWhisk can be provided through a configuration file or environment variables. This plugin requires the API endpoint, namespace and authentication credentials. 15 | 16 | **Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. 17 | 18 | **Do you want to use environment variables for credentials?** Use the following environment variables to be pass in account credentials. These values override anything extracted from the configuration file. 19 | 20 | - *OW_APIHOST* - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` 21 | - *OW_AUTH* - Authentication key, e.g. `xxxxxx:yyyyy 22 | 23 | 24 | 25 | ### Have you installed and setup the provider plugin? 26 | 27 | Using the framework with the OpenWhisk platform needs you to install the provider plugin and link this to your service. 28 | 29 | #### Install the provider plugin 30 | 31 | ``` 32 | $ npm install --global serverless-openwhisk 33 | ``` 34 | 35 | *Due to an [outstanding issue](https://github.com/serverless/serverless/issues/2895) with provider plugins, the [OpenWhisk provider](https://github.com/serverless/serverless-openwhisk) must be installed as a global module.* 36 | 37 | 38 | #### Link provider plugin to service directory 39 | 40 | Using `npm link` will import the provider plugin into the service directory. Running `npm install` will automatically perform this using a `post install` script. 41 | 42 | ``` 43 | $ npm link serverless-openwhisk 44 | or 45 | $ npm install 46 | ``` 47 | 48 | 49 | 50 | **_…and that's it!_** 51 | 52 | ### Deploy Service 53 | 54 | Use the `serverless` command to deploy your service. The sample `handler.js` file can be deployed without modification. 55 | 56 | ```shell 57 | serverless deploy 58 | ``` 59 | 60 | 61 | 62 | ### Issues / Feedback / Feature Requests? 63 | 64 | If you have any issues, comments or want to see new features, please file an issue in the project repository: 65 | 66 | https://github.com/serverless/serverless-openwhisk -------------------------------------------------------------------------------- /Chapter05/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * main() will be invoked when you Run This Action. 5 | * 6 | * When enabled as a Web Action, use the following URL to invoke this action: 7 | * https://{APIHOST}/api/v1/web/{QUALIFIED ACTION NAME}?location=Austin 8 | * 9 | * For example: 10 | * https://openwhisk.ng.bluemix.net/api/v1/web/myusername@us.ibm.com_myspace/get-resource/weather?location=Austin 11 | * 12 | * In this case, the params variable will look like: 13 | * { "location": "Austin" } 14 | * 15 | */ 16 | 17 | var request = require('request'); 18 | 19 | function main(params) { 20 | var location = params.location || 'Vermont'; 21 | var url = 'https://query.yahooapis.com/v1/public/yql?q=select item.condition from weather.forecast \ 22 | where woeid in (select woeid from geo.places(1) where text="' + location + '")&format=json'; 23 | return new Promise(function(resolve, reject) { 24 | request.get(url, function(error, response, body) { 25 | if (error) { 26 | reject({ 27 | statusCode: 500, 28 | headers: { 'Content-Type': 'application/json' }, 29 | body: {'message': 'Error processing your request'} 30 | }); 31 | } 32 | else { 33 | /** The response body contains temperature data in the following format 34 | * { code: '28', 35 | * date: 'Tue, 26 Dec 2017 12:00 PM EST', 36 | * temp: '18', 37 | * text: 'Mostly Cloudy' } } 38 | */ 39 | resolve({ 40 | statusCode: 200, 41 | headers: { 'Content-Type': 'application/json' }, 42 | body: JSON.parse(body).query.results.channel.item.condition 43 | }); 44 | } 45 | }); 46 | }); 47 | } 48 | 49 | exports.main = main; -------------------------------------------------------------------------------- /Chapter05/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openwhisk-nodejs", 3 | "version": "1.0.0", 4 | "description": "Sample OpenWhisk NodeJS serverless framework service.", 5 | "main": "handler.js", 6 | "scripts": { 7 | "postinstall": "npm link serverless-openwhisk" 8 | }, 9 | "devDependencies": { 10 | "mocha": "latest", 11 | "openwhisk": "latest", 12 | "eslint": "latest", 13 | "request": "latest" 14 | }, 15 | "keywords": [ 16 | "serverless", 17 | "openwhisk", 18 | "CD/CI" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /Chapter05/serverless.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Serverless! 2 | # 3 | # This file is the main config file for your service. 4 | # It's very minimal at this point and uses default values. 5 | # You can always add more config options for more control. 6 | # We've included some commented out config examples here. 7 | # Just uncomment any of them to get that config option. 8 | # 9 | # For full config options, check the docs: 10 | # docs.serverless.com 11 | # 12 | # Happy Coding! 13 | 14 | service: weatherReport 15 | 16 | # Please ensure the serverless-openwhisk provider plugin is installed globally. 17 | # $ npm install -g serverless-openwhisk 18 | # ...before installing project dependencies to register this provider. 19 | # $ npm install 20 | provider: 21 | name: openwhisk 22 | 23 | # you can add packaging information here 24 | #package: 25 | # include: 26 | # - include-me.js 27 | # - include-me-dir/** 28 | # exclude: 29 | # - exclude-me.js 30 | # - exclude-me-dir/** 31 | 32 | functions: 33 | main: 34 | handler: handler.main 35 | 36 | # Functions can be defined using sequences rather than referring 37 | # to a handler. 38 | # sequence: 39 | # - parse_input 40 | # - do_some_algorithm 41 | # - construct_output 42 | 43 | # The following are a few example events you can configure 44 | # Check the event documentation for details 45 | # events: 46 | # - http: GET /api/users/create 47 | # - trigger: trigger_name 48 | 49 | 50 | # extend the framework using plugins listed here: 51 | # https://github.com/serverless/plugins 52 | plugins: 53 | - serverless-openwhisk 54 | 55 | # you can define custom triggers and trigger feeds using the resources section. 56 | # 57 | #resources: 58 | # triggers: 59 | # my_trigger: 60 | # parameters: 61 | # hello: world 62 | # alarm_trigger: 63 | # parameters: 64 | # hello: world 65 | # feed: /whisk.system/alarms/alarm 66 | # feed_parameters: 67 | # cron: '*/8 * * * * *' 68 | -------------------------------------------------------------------------------- /Chapter05/test/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var weather = require('../handler.js'); 3 | var openwhisk = require('openwhisk'); 4 | var ow; 5 | 6 | /** 7 | * Create a function which delegates to openwhisk to run a function f 8 | */ 9 | function makeAdapter(f) { 10 | return function(params) { 11 | return ow.actions.invoke({name: f, 12 | blocking: true, 13 | result:true, 14 | params:params}); 15 | }; 16 | } 17 | 18 | /** 19 | * For each function in an object, create an openwhisk adapter. 20 | * return an object with each adapter function. 21 | */ 22 | function adapt(obj) { 23 | var adapter= {} 24 | for (var p in obj) { 25 | adapter[p] = makeAdapter(p) 26 | } 27 | return adapter; 28 | } 29 | 30 | 31 | describe('handler', function() { 32 | 33 | before( function() { 34 | if (process.env.TEST_OPENWHISK) { 35 | options = { apihost: process.env.OPENWHISK_HOST, 36 | api_key: process.env.OW_AUTH_KEY }; 37 | ow = openwhisk(options); 38 | hello = adapt(hello,ow); 39 | } 40 | }); 41 | 42 | describe('handler', function() { 43 | it('should throw an error when location is not present', function() { 44 | var params = {} 45 | return weather.main(params).then(function(result) { 46 | assert(false); 47 | }).catch(function(err) { 48 | assert(true); 49 | }); 50 | }); 51 | }); 52 | 53 | describe('hello', function() { 54 | it('should return code as 29 for location as paris!', function() { 55 | var params = { 'location': 'paris' }; 56 | 57 | return weather.main(params).then(function(result) { 58 | assert.notEqual(result.body.message,"Error processing your request"); 59 | assert.equal(result.body.code,"29"); 60 | }) 61 | }); 62 | }); 63 | 64 | }); 65 | -------------------------------------------------------------------------------- /Chapter06/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jenkins:latest 2 | USER root 3 | 4 | # Install Node.js and cloud SDK 5 | RUN apt-get update -y \ 6 | && apt-get install -y apt-utils curl 7 | RUN curl --silent --location https://deb.nodesource.com/setup_8.x | bash - 8 | RUN apt-get install --yes nodejs 9 | RUN apt-get install --yes build-essential 10 | RUN npm i -g npm 11 | 12 | RUN npm install serverless -g 13 | RUN npm install -g @google-cloud/functions-emulator 14 | 15 | RUN npm config set prefix '~/.npm-global' 16 | RUN export PATH=~/.npm-global/bin:$PATH 17 | 18 | ENV CLOUD_SDK_VERSION 198.0.0 19 | RUN apt-get -qqy update && apt-get install -qqy \ 20 | curl \ 21 | gcc \ 22 | python-dev \ 23 | python-setuptools \ 24 | apt-transport-https \ 25 | lsb-release \ 26 | openssh-client \ 27 | git \ 28 | && easy_install -U pip && \ 29 | pip install -U crcmod && \ 30 | export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" && \ 31 | echo "deb https://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" > /etc/apt/sources.list.d/google-cloud-sdk.list && \ 32 | curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \ 33 | apt-get update && \ 34 | apt-get install -y google-cloud-sdk=${CLOUD_SDK_VERSION}-0 \ 35 | google-cloud-sdk-app-engine-python=${CLOUD_SDK_VERSION}-0 \ 36 | google-cloud-sdk-app-engine-java=${CLOUD_SDK_VERSION}-0 \ 37 | google-cloud-sdk-app-engine-go=${CLOUD_SDK_VERSION}-0 \ 38 | google-cloud-sdk-datalab=${CLOUD_SDK_VERSION}-0 \ 39 | google-cloud-sdk-datastore-emulator=${CLOUD_SDK_VERSION}-0 \ 40 | google-cloud-sdk-pubsub-emulator=${CLOUD_SDK_VERSION}-0 \ 41 | google-cloud-sdk-bigtable-emulator=${CLOUD_SDK_VERSION}-0 \ 42 | google-cloud-sdk-cbt=${CLOUD_SDK_VERSION}-0 \ 43 | kubectl && \ 44 | gcloud config set core/disable_usage_reporting true && \ 45 | gcloud config set component_manager/disable_update_check true && \ 46 | gcloud config set metrics/environment github_docker_image && \ 47 | gcloud --version && \ 48 | kubectl version --client 49 | VOLUME ["/root/.config"] 50 | 51 | USER jenkins -------------------------------------------------------------------------------- /Chapter06/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | triggers { 5 | pollSCM('*/5 * * * *') 6 | } 7 | 8 | stages { 9 | 10 | stage('init'){ 11 | 12 | sh ''' 13 | npm install 14 | gcloud auth activate-service-account --key-file=${WORKSPACE}/My-Serverless-Project-1d8bacd4886d.json 15 | '''; 16 | } 17 | 18 | stage('Unit Tests') { 19 | steps { 20 | sh '''# executing unit test 21 | ${WORKSPACE}/node_modules/.bin/ava test/unit.http.test.js 22 | ''' 23 | } 24 | } 25 | 26 | stage('Deploy to DEV and Integration Test') { 27 | steps { 28 | sh ''' 29 | # executing integration test 30 | gcloud config set project ${YOUR_GCP_DEV_PROJECT_ID} 31 | export BASE_URL=http://localhost:8010/${YOUR_GCP_DEV_PROJECT_ID}/${YOUR_GCF_REGION} 32 | ${WORKSPACE}/node_modules/.bin/functions start 33 | ${WORKSPACE}/node_modules/.bin/functions deploy helloHttp --trigger-http 34 | ${WORKSPACE}/node_modules/.bin/ava test/integration.http.test.js 35 | ''' 36 | } 37 | } 38 | stage('Deploy to SIT and System Test') { 39 | steps { 40 | sh ''' 41 | # deploying to GCP project and executing system test 42 | gcloud config set project ${YOUR_GCP_SIT_PROJECT_ID} 43 | gcloud beta functions deploy helloHttp --trigger-http 44 | export BASE_URL=https://${YOUR_GCF_REGION}-${YOUR_GCP_PROJECT_ID}.cloudfunctions.net/helloHttp 45 | ${WORKSPACE}/node_modules/.bin/ava test/system.http.test.js 46 | ''' 47 | } 48 | } 49 | stage('Deploy to UAT') { 50 | steps { 51 | sh ''' 52 | # deploying to GCP project and executing system test 53 | gcloud config set project ${YOUR_GCP_UAT_PROJECT_ID} 54 | gcloud beta functions deploy helloHttp --trigger-http 55 | export BASE_URL=https://${YOUR_GCF_REGION}-${YOUR_GCP_UAT_PROJECT_ID}.cloudfunctions.net/helloHttp 56 | ${WORKSPACE}/node_modules/.bin/ava test/system.http.test.js 57 | ''' 58 | } 59 | } 60 | stage('Promotion') { 61 | steps { 62 | timeout(time: 1, unit:'DAYS') { 63 | input 'Deploy to Production?' 64 | } 65 | } 66 | } 67 | stage('Deploy to Production') { 68 | steps { 69 | sh ''' 70 | # deploying to GCP project and executing system test 71 | gcloud beta functions deploy helloHttp --trigger-http 72 | gcloud config set project ${YOUR_GCP_PROD_PROJECT_ID} 73 | export BASE_URL=https://${YOUR_GCF_REGION}-${YOUR_GCP_PROD_PROJECT_ID}.cloudfunctions.net/helloHttp 74 | ${WORKSPACE}/node_modules/.bin/ava test/system.http.test.js 75 | ''' 76 | } 77 | } 78 | } 79 | post { 80 | failure { 81 | mail to: 'your_email@gmail.com', subject: 'Build failed', body: 'Please fix!' 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Chapter06/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | const Buffer = require('safe-buffer').Buffer; 19 | 20 | // [START functions_helloworld_get] 21 | /** 22 | * HTTP Cloud Function. 23 | * 24 | * @param {Object} req Cloud Function request context. 25 | * @param {Object} res Cloud Function response context. 26 | */ 27 | exports.helloGET = (req, res) => { 28 | res.send('Hello World!'); 29 | }; 30 | // [END functions_helloworld_get] 31 | 32 | // [START functions_helloworld_http] 33 | /** 34 | * HTTP Cloud Function. 35 | * 36 | * @param {Object} req Cloud Function request context. 37 | * @param {Object} res Cloud Function response context. 38 | */ 39 | exports.helloHttp = (req, res) => { 40 | res.send(`Hello ${req.body.name || 'World'}!`); 41 | }; 42 | // [END functions_helloworld_http] 43 | 44 | // [START functions_helloworld_background] 45 | /** 46 | * Background Cloud Function. 47 | * 48 | * @param {object} event The Cloud Functions event. 49 | * @param {function} callback The callback function. 50 | */ 51 | exports.helloBackground = (event, callback) => { 52 | callback(null, `Hello ${event.data.name || 'World'}!`); 53 | }; 54 | // [END functions_helloworld_background] 55 | 56 | // [START functions_helloworld_pubsub] 57 | /** 58 | * Background Cloud Function to be triggered by Pub/Sub. 59 | * 60 | * @param {object} event The Cloud Functions event. 61 | * @param {function} callback The callback function. 62 | */ 63 | exports.helloPubSub = (event, callback) => { 64 | const pubsubMessage = event.data; 65 | const name = pubsubMessage.data ? Buffer.from(pubsubMessage.data, 'base64').toString() : 'World'; 66 | 67 | console.log(`Hello, ${name}!`); 68 | 69 | callback(); 70 | }; 71 | // [END functions_helloworld_pubsub] 72 | 73 | // [START functions_helloworld_storage] 74 | /** 75 | * Background Cloud Function to be triggered by Cloud Storage. 76 | * 77 | * @param {object} event The Cloud Functions event. 78 | * @param {function} callback The callback function. 79 | */ 80 | exports.helloGCS = (event, callback) => { 81 | const file = event.data; 82 | 83 | if (file.resourceState === 'not_exists') { 84 | console.log(`File ${file.name} deleted.`); 85 | } else if (file.metageneration === '1') { 86 | // metageneration attribute is updated on metadata changes. 87 | // on create value is 1 88 | console.log(`File ${file.name} uploaded.`); 89 | } else { 90 | console.log(`File ${file.name} metadata updated.`); 91 | } 92 | 93 | callback(); 94 | }; 95 | // [END functions_helloworld_storage] 96 | 97 | // [START functions_helloworld_storage_generic] 98 | /** 99 | * Generic background Cloud Function to be triggered by Cloud Storage. 100 | * 101 | * @param {object} event The Cloud Functions event. 102 | * @param {function} callback The callback function. 103 | */ 104 | exports.helloGCSGeneric = (event, callback) => { 105 | const file = event.data; 106 | const context = event.context; 107 | 108 | console.log(`Event ${context.eventId}`); 109 | console.log(` Event Type: ${context.eventType}`); 110 | console.log(` Bucket: ${file.bucket}`); 111 | console.log(` File: ${file.name}`); 112 | console.log(` Metageneration: ${file.metageneration}`); 113 | console.log(` Created: ${file.timeCreated}`); 114 | console.log(` Updated: ${file.updated}`); 115 | 116 | callback(); 117 | }; 118 | // [END functions_helloworld_storage_generic] 119 | 120 | // [START functions_helloworld_error] 121 | /** 122 | * Background Cloud Function that throws an error. 123 | * 124 | * @param {object} event The Cloud Functions event. 125 | * @param {function} callback The callback function. 126 | */ 127 | exports.helloError = (event, callback) => { 128 | // This WILL be reported to Stackdriver errors 129 | throw new Error('I failed you'); 130 | }; 131 | // [END functions_helloworld_error] 132 | 133 | /* eslint-disable */ 134 | // [START functions_helloworld_error_2] 135 | /** 136 | * Background Cloud Function that throws a value. 137 | * 138 | * @param {object} event The Cloud Functions event. 139 | * @param {function} callback The callback function. 140 | */ 141 | exports.helloError2 = (event, callback) => { 142 | // This will NOT be reported to Stackdriver errors 143 | throw 1; 144 | }; 145 | // [END functions_helloworld_error_2] 146 | 147 | // [START functions_helloworld_error_3] 148 | /** 149 | * Background Cloud Function that throws an error. 150 | * 151 | * @param {object} event The Cloud Functions event. 152 | * @param {function} callback The callback function. 153 | */ 154 | exports.helloError3 = (event, callback) => { 155 | // This will NOT be reported to Stackdriver errors 156 | callback('I failed you'); 157 | }; 158 | // [END functions_helloworld_error_3] 159 | /* eslint-enable */ 160 | 161 | // [START functions_helloworld_template] 162 | const path = require('path'); 163 | const pug = require('pug'); 164 | 165 | // Renders the index.pug 166 | exports.helloTemplate = (req, res) => { 167 | // Render the index.pug file 168 | const html = pug.renderFile(path.join(__dirname, 'index.pug')); 169 | 170 | res.send(html).end(); 171 | }; 172 | // [END functions_helloworld_template] -------------------------------------------------------------------------------- /Chapter06/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-docs-samples-functions-hello-world", 3 | "version": "0.0.1", 4 | "private": true, 5 | "license": "Apache-2.0", 6 | "author": "Google Inc.", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" 10 | }, 11 | "engines": { 12 | "node": ">=4.3.2" 13 | }, 14 | "scripts": { 15 | "lint": "repo-tools lint", 16 | "pretest": "npm run lint", 17 | "e2e-test": "export FUNCTIONS_CMD='gcloud beta functions' && sh test/updateFunctions.sh && BASE_URL=\"https://$GCF_REGION-$GCLOUD_PROJECT.cloudfunctions.net/\" ava -T 20s --verbose test/*.test.js", 18 | "test": "export FUNCTIONS_CMD='functions-emulator' && sh test/updateFunctions.sh && export BASE_URL=\"http://localhost:8010/$GCLOUD_PROJECT/$GCF_REGION\" && ava -T 20s --verbose -c 1 test/index.test.js test/*unit*test.js test/*integration*test.js", 19 | "system-test": "export FUNCTIONS_CMD='functions-emulator' && sh test/updateFunctions.sh && export BASE_URL=\"http://localhost:8010/$GCLOUD_PROJECT/$GCF_REGION\" && ava -T 20s --verbose test/*.test.js" 20 | }, 21 | "dependencies": { 22 | "@google-cloud/debug-agent": "2.4.0", 23 | "pug": "2.0.3", 24 | "safe-buffer": "5.1.1" 25 | }, 26 | "devDependencies": { 27 | "@google-cloud/nodejs-repo-tools": "2.2.5", 28 | "@google-cloud/pubsub": "^0.17.0", 29 | "@google-cloud/storage": "^1.5.0", 30 | "ava": "0.25.0", 31 | "proxyquire": "2.0.1", 32 | "sinon": "4.4.8", 33 | "supertest": "^3.0.0", 34 | "uuid": "^3.1.0", 35 | "@google-cloud/functions-emulator": "1.0.0-beta.4" 36 | }, 37 | "cloud-repo-tools": { 38 | "requiresKeyFile": true, 39 | "requiresProjectId": true, 40 | "requiredEnvVars": [ 41 | "BASE_URL", 42 | "GCF_REGION", 43 | "TOPIC", 44 | "BUCKET", 45 | "FUNCTIONS_CMD" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Chapter06/test/integration.http.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | // [START functions_http_integration_test] 17 | const test = require(`ava`); 18 | const Supertest = require(`supertest`); 19 | const supertest = Supertest(process.env.BASE_URL); 20 | 21 | test.cb(`helloHttp: should print a name`, (t) => { 22 | supertest 23 | .post(`/helloHttp`) 24 | .send({ name: 'John' }) 25 | .expect(200) 26 | .expect((response) => { 27 | t.is(response.text, 'Hello John!'); 28 | }) 29 | .end(t.end); 30 | }); 31 | 32 | test.cb(`helloHttp: should print hello world`, (t) => { 33 | supertest 34 | .get(`/helloHttp`) 35 | .expect(200) 36 | .expect((response) => { 37 | t.is(response.text, `Hello World!`); 38 | }) 39 | .end(t.end); 40 | }); 41 | // [END functions_http_integration_test] -------------------------------------------------------------------------------- /Chapter06/test/system.http.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | // [START functions_http_system_test] 17 | const test = require(`ava`); 18 | const Supertest = require(`supertest`); 19 | const supertest = Supertest(process.env.BASE_URL); 20 | 21 | test.cb(`helloHttp: should print a name`, (t) => { 22 | supertest 23 | .post(`/helloHttp`) 24 | .send({ name: 'John' }) 25 | .expect(200) 26 | .expect((response) => { 27 | t.is(response.text, 'Hello John!'); 28 | }) 29 | .end(t.end); 30 | }); 31 | 32 | test.cb(`helloHttp: should print hello world`, (t) => { 33 | supertest 34 | .get(`/helloHttp`) 35 | .expect(200) 36 | .expect((response) => { 37 | t.is(response.text, `Hello World!`); 38 | }) 39 | .end(t.end); 40 | }); 41 | // [END functions_http_system_test] -------------------------------------------------------------------------------- /Chapter06/test/unit.http.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | // [START functions_http_unit_test] 17 | const test = require(`ava`); 18 | const sinon = require(`sinon`); 19 | const uuid = require(`uuid`); 20 | 21 | const helloHttp = require(`..`).helloHttp; 22 | 23 | test(`helloHttp: should print a name`, t => { 24 | // Initialize mocks 25 | const name = uuid.v4(); 26 | const req = { 27 | body: { 28 | name: name 29 | } 30 | }; 31 | const res = { send: sinon.stub() }; 32 | 33 | // Call tested function 34 | helloHttp(req, res); 35 | 36 | // Verify behavior of tested function 37 | t.true(res.send.calledOnce); 38 | t.deepEqual(res.send.firstCall.args, [`Hello ${name}!`]); 39 | }); 40 | 41 | test(`helloHttp: should print hello world`, t => { 42 | // Initialize mocks 43 | const req = { 44 | body: {} 45 | }; 46 | const res = { send: sinon.stub() }; 47 | 48 | // Call tested function 49 | helloHttp(req, res); 50 | 51 | // Verify behavior of tested function 52 | t.true(res.send.calledOnce); 53 | t.deepEqual(res.send.firstCall.args, [`Hello World!`]); 54 | }); 55 | // [END functions_http_unit_test] -------------------------------------------------------------------------------- /Chapter07/README.md: -------------------------------------------------------------------------------- 1 | # kubeless-serverless 2 | This repository is for kubeless serverless framework 3 | -------------------------------------------------------------------------------- /Chapter07/handler.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | import json 3 | 4 | def find(event, context): 5 | term = event['data']['term'] 6 | url = "https://feeds.capitalbikeshare.com/stations/stations.json" 7 | response = urllib2.urlopen(url) 8 | stations = json.loads(response.read()) 9 | 10 | hits = [] 11 | 12 | for station in stations["stationBeanList"]: 13 | if station["stAddress1"].find(term) > -1: 14 | hits.append(station) 15 | 16 | return json.dumps(hits) 17 | -------------------------------------------------------------------------------- /Chapter07/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kubeless-python", 3 | "version": "1.0.0", 4 | "description": "Sample Kubeless Python serverless framework service.", 5 | "dependencies": { 6 | "serverless-kubeless": "^0.3.1" 7 | }, 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [ 12 | "serverless", 13 | "kubeless" 14 | ], 15 | "author": "The Kubeless Authors", 16 | "license": "Apache-2.0" 17 | } 18 | -------------------------------------------------------------------------------- /Chapter07/serverless.yml: -------------------------------------------------------------------------------- 1 | service: bikesearch 2 | provider: 3 | name: kubeless 4 | runtime: python2.7 5 | 6 | plugins: 7 | - serverless-kubeless 8 | 9 | functions: 10 | bikesearch: 11 | handler: handler.find 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Packt 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. 22 | --------------------------------------------------------------------------------