├── .npmignore ├── index.js ├── .gitignore ├── test ├── schemas │ ├── function-listing.js │ ├── function-create.js │ └── function.js ├── assets │ ├── runtests.sh │ └── .config.json ├── live │ └── azure-functions.js ├── mocks │ └── azure-arm-resource.js └── azure-functions.js ├── .beautifyrc ├── .travis.yml ├── .jshintrc ├── LICENSE ├── Gruntfile.js ├── package.json ├── CONTRIBUTING.MD ├── .jscsrc ├── modules └── azure-functions.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | *.png 2 | 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./modules/azure-functions'); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .config-live.json 2 | node_modules 3 | .vscode 4 | .DS_STORE 5 | # code coverage file 6 | coverage 7 | 8 | -------------------------------------------------------------------------------- /test/schemas/function-listing.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'array', 3 | items: require('./function') 4 | }; 5 | -------------------------------------------------------------------------------- /.beautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "js": { 3 | "indentChar": " ", 4 | "indentLevel": 0, 5 | "indentSize": 4, 6 | "indentWithTabs": false, 7 | "space_after_anon_function": "true" 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /test/assets/runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ "${TRAVIS_PULL_REQUEST}" = "false" ] 3 | then 4 | istanbul cover _mocha -- --recursive 5 | if [ "${?}" = "0" ] 6 | then 7 | codeclimate-test-reporter < coverage/lcov.info 8 | else 9 | exit 1 10 | fi 11 | fi 12 | -------------------------------------------------------------------------------- /test/assets/.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_comment": "The values below are fake but conforming credentials", 3 | "CLIENT_ID": "dafaad92-9eee-4275-913a-2497ffd1de1c", 4 | "CLIENT_SECRET": "C1F4CFC3-1085-4F99-B38E-1F5CDEADBEEF", 5 | "SUBSCRIPTION_ID": "DEADBEEF-8e28-41ed-8537-DEADBEEF01f5", 6 | "AD_DOMAIN": "microsoft.com", 7 | "RESOURCE_GROUP_NAME": "AzureFunctions-WestUS", 8 | "FUNCTION_APP_NAME": "serverlessdemo" 9 | } 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4.1.2' 4 | sudo: false 5 | cache: 6 | directories: 7 | - node_modules 8 | before_install: 9 | - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH 10 | - npm config set spin false 11 | - npm install -g npm@^2 12 | install: 13 | - npm install 14 | - npm install -g grunt-cli 15 | - npm install -g codeclimate-test-reporter 16 | - npm install -g istanbul 17 | - npm install -g mocha 18 | script: 19 | - grunt ci 20 | - ./test/assets/runtests.sh 21 | 22 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "node": true, 4 | "browser": true, 5 | "nomen": false, 6 | "bitwise": true, 7 | "eqeqeq": true, 8 | "forin": true, 9 | "immed": true, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "noempty": true, 14 | "nonew": true, 15 | "plusplus": true, 16 | "regexp": true, 17 | "undef": true, 18 | "unused": true, 19 | "trailing": true, 20 | "indent": 4, 21 | "esnext": true, 22 | "onevar": true, 23 | "white": true, 24 | "quotmark": "single", 25 | "predef": { 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Microsoft and Steven Edouard 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /test/schemas/function-create.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | additionalProperties: false, 3 | properties: { 4 | location: { 5 | type: 'string', 6 | minLength: 4 7 | }, 8 | properties: { 9 | type: 'object', 10 | additionalProperties: false, 11 | properties: { 12 | config: { 13 | type: 'object', 14 | additionalProperties: false, 15 | properties: { 16 | bindings: { 17 | type: 'array', 18 | required: true, 19 | minLength: 1 20 | } 21 | } 22 | }, 23 | files: { 24 | type: 'object', 25 | additionalProperties: false, 26 | properties: { 27 | 'index.js': { 28 | type: 'string', 29 | minLength: 20, 30 | required: true 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | var grunt = require('grunt'); 2 | require('load-grunt-tasks')(grunt); 3 | 4 | var jsFiles = ['modules/**/*.js', 'test/**/*.js']; 5 | 6 | grunt.initConfig({ 7 | mochacli: { 8 | mockTests: { 9 | options: { 10 | reporter: 'spec', 11 | grep: '.*-mock', 12 | recursive: true, 13 | bail: false 14 | }, 15 | all: jsFiles 16 | }, 17 | liveTests: { 18 | options: { 19 | reporter: 'spec', 20 | grep: '.*-live', 21 | recursive: true, 22 | bail: false 23 | }, 24 | all: jsFiles 25 | } 26 | }, 27 | jshint: { 28 | files: jsFiles, 29 | options: { 30 | jshintrc: '.jshintrc' 31 | } 32 | }, 33 | jscs: { 34 | files: { 35 | src: jsFiles 36 | }, 37 | options: { 38 | config: '.jscsrc', 39 | esnext: true 40 | } 41 | }, 42 | jsbeautifier: { 43 | write: { 44 | files: { 45 | src: jsFiles 46 | }, 47 | options: { 48 | config: '.beautifyrc' 49 | } 50 | } 51 | } 52 | }); 53 | grunt.registerTask('ci', ['mochacli:mockTests', 'jshint', 'jscs']); 54 | grunt.registerTask('live-test', ['mochacli:liveTests', 'jshint', 'jscs']); 55 | grunt.registerTask('validate', ['jshint', 'jscs']); 56 | 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "azure-functions", 3 | "version": "1.0.2", 4 | "description": "A node client library for Azure Functions", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "grunt test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/sedouard/azure-functions-node.git" 12 | }, 13 | "keywords": [ 14 | "Azure", 15 | "Functions", 16 | "Lambda", 17 | "Azure", 18 | "Kudu", 19 | "Azure", 20 | "Webapps", 21 | "Websites" 22 | ], 23 | "author": "sedouard", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/sedouard/azure-functions-node/issues" 27 | }, 28 | "homepage": "https://github.com/sedouard/azure-functions-node#readme", 29 | "dependencies": { 30 | "azure-arm-resource": "^1.4.1-preview", 31 | "bluebird": "^3.3.5", 32 | "clone": "^1.0.2", 33 | "debug": "^2.2.0", 34 | "guid": "0.0.12", 35 | "ms-rest": "^1.12.0", 36 | "ms-rest-azure": "^1.12.0", 37 | "skeemas": "^1.2.2" 38 | }, 39 | "devDependencies": { 40 | "grunt": "^1.0.1", 41 | "grunt-contrib-jshint": "^1.0.0", 42 | "grunt-jsbeautifier": "^0.2.12", 43 | "grunt-jscs": "^2.8.0", 44 | "grunt-mocha": "^1.0.1", 45 | "grunt-mocha-cli": "^2.1.0", 46 | "guid": "0.0.12", 47 | "load-grunt-tasks": "^3.5.0", 48 | "mockery": "^1.6.2", 49 | "nconf": "^0.8.4" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CONTRIBUTING.MD: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are always welcome! Be sure to follow the [github workflow](https://guides.github.com/introduction/flow/) when contributing to this project: 4 | 5 | * Create an issue, or comment on an issue to indicate what you are working on. This avoids work duplication. 6 | * Fork the repository and clone to your local machine 7 | * You should already be on the default branch `master` - if not, check it out (`git checkout master`) 8 | * Create a new branch for your feature/fix `git checkout -b my-new-feature`) 9 | * Write your feature/fix 10 | * Stage the changed files for a commit (`git add .`) 11 | * Commit your files with a *useful* commit message ([example](https://github.com/Azure/azure-quickstart-templates/commit/53699fed9983d4adead63d9182566dec4b8430d4)) (`git commit`) 12 | * Push your new branch to your GitHub Fork (`git push origin my-new-feature`) 13 | * Visit this repository in GitHub and create a Pull Request. 14 | 15 | # Running the Tests 16 | 17 | You'll want to make sure you add new tests for any new features you contribute and that you don't break any existing tests. 18 | 19 | Tests are written in the [`./test`](./test) folder. You'll need to be working on a modern Windows operating system for developing 20 | code in this module. 21 | 22 | After you've forked and clone the repo execute the following to start the tests: 23 | 24 | ``` 25 | npm install 26 | npm install grunt-cli -g 27 | grunt ci 28 | ``` 29 | 30 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowEmptyBlocks": true, 3 | "disallowKeywords": [ 4 | "with" 5 | ], 6 | "disallowKeywordsOnNewLine": [ 7 | "else" 8 | ], 9 | "disallowMixedSpacesAndTabs": true, 10 | "disallowMultipleLineBreaks": true, 11 | "disallowNewlineBeforeBlockStatements": true, 12 | "disallowPaddingNewlinesInBlocks": false, 13 | "disallowQuotedKeysInObjects": true, 14 | "disallowSpaceAfterObjectKeys": true, 15 | "disallowSpaceAfterPrefixUnaryOperators": true, 16 | "disallowSpaceBeforeBinaryOperators": [ 17 | ], 18 | "disallowSpaceBeforePostfixUnaryOperators": true, 19 | "disallowSpacesInFunctionDeclaration": { 20 | "beforeOpeningRoundBrace": true 21 | }, 22 | "disallowSpacesInNamedFunctionExpression": { 23 | "beforeOpeningRoundBrace": true 24 | }, 25 | "disallowSpacesInsideArrayBrackets": true, 26 | "disallowSpacesInsideObjectBrackets": "all", 27 | "disallowSpacesInsideParentheses": true, 28 | "disallowTrailingComma": true, 29 | "disallowTrailingWhitespace": true, 30 | "disallowYodaConditions": true, 31 | "requireBlocksOnNewline": 1, 32 | "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties", 33 | "requireCapitalizedConstructors": true, 34 | "requireCommaBeforeLineBreak": true, 35 | "requireCurlyBraces": [ 36 | "if", 37 | "else", 38 | "for", 39 | "while", 40 | "do", 41 | "try", 42 | "catch" 43 | ], 44 | "requireDotNotation": true, 45 | "requireLineFeedAtFileEnd": true, 46 | "requireParenthesesAroundIIFE": true, 47 | "requireSpaceAfterBinaryOperators": true, 48 | "requireSpaceAfterKeywords": [ 49 | "else", 50 | "for", 51 | "while", 52 | "do", 53 | "switch", 54 | "case", 55 | "return", 56 | "try", 57 | "function", 58 | "typeof" 59 | ], 60 | "requireSpaceAfterLineComment": "allowSlash", 61 | "requireSpaceBeforeBinaryOperators": true, 62 | "requireSpaceBeforeBlockStatements": true, 63 | "requireSpacesInConditionalExpression": true, 64 | "safeContextKeyword": [ 65 | "self" 66 | ], 67 | "validateIndentation": 4, 68 | "validateQuoteMarks": "'", 69 | "esnext": true 70 | } 71 | 72 | -------------------------------------------------------------------------------- /test/schemas/function.js: -------------------------------------------------------------------------------- 1 | var scmUrlPattern = 'https://.*\.scm\.azurewebsites\.net/.*'; 2 | var rmUrlPattern = '/subscriptions/.*/resourceGroups/.*/providers/Microsoft.Web/sites/.*'; 3 | module.exports = { 4 | additionalProperties: false, 5 | properties: { 6 | id: { 7 | type: 'string', 8 | pattern: '/subscriptions/.*', 9 | required: true 10 | }, 11 | name: { 12 | type: 'string', 13 | pattern: '.*/.*', 14 | required: true 15 | }, 16 | function_app_id: { 17 | type: 'string', 18 | pattern: rmUrlPattern 19 | }, 20 | type: { 21 | type: 'string', 22 | pattern: 'Microsoft.Web/sites/functions', 23 | required: true 24 | }, 25 | location: { 26 | type: 'string', 27 | required: true 28 | }, 29 | script_root_path_href: { 30 | type: 'string', 31 | script_href: '' 32 | }, 33 | script_href: { 34 | type: 'string', 35 | script_href: scmUrlPattern 36 | }, 37 | config_href: { 38 | type: '', 39 | test_data_href: scmUrlPattern 40 | }, 41 | properties: { 42 | type: 'object', 43 | additionalProperties: false, 44 | properties: { 45 | name: { 46 | type: 'string', 47 | minLength: 1, 48 | required: true 49 | }, 50 | function_app_id: { 51 | type: 'string', 52 | minLength: 1, 53 | required: true 54 | }, 55 | script_root_path_href: { 56 | type: 'string', 57 | pattern: scmUrlPattern, 58 | required: true 59 | }, 60 | script_href: { 61 | type: 'string', 62 | pattern: scmUrlPattern, 63 | required: true 64 | }, 65 | config_href: { 66 | type: 'string', 67 | pattern: scmUrlPattern, 68 | required: true 69 | }, 70 | test_data_href: { 71 | type: 'string', 72 | pattern: scmUrlPattern, 73 | required: false 74 | }, 75 | secrets_file_href: { 76 | type: 'string', 77 | pattern: scmUrlPattern, 78 | required: true 79 | }, 80 | href: { 81 | type: 'string', 82 | pattern: scmUrlPattern, 83 | required: true 84 | }, 85 | test_data: { 86 | type: 'string', 87 | required: true 88 | }, 89 | files: { 90 | required: false 91 | }, 92 | config: { 93 | type: 'object', 94 | properties: { 95 | bindings: { 96 | type: 'array' 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | }; 104 | -------------------------------------------------------------------------------- /test/live/azure-functions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* globals describe, it*/ 3 | var fs = require('fs'); 4 | var nconf = require('nconf'); 5 | var path = require('path'); 6 | var assert = require('assert'); 7 | var debug = require('debug')('azure-functions:test:live:azure-functions'); 8 | var skeemas = require('skeemas'); 9 | var clone = require('clone'); 10 | var functionSchema = require('../schemas/function'); 11 | var functionListingSchema = require('../schemas/function-listing'); 12 | 13 | nconf.env().file({ 14 | file: path.join(__dirname, '../assets/.config.json') 15 | }); 16 | global.Promise = require('bluebird'); 17 | 18 | function validateFunctionObject(func) { 19 | var result = skeemas.validate(func, functionSchema); 20 | assert(result.valid, JSON.stringify(result.errors)); 21 | } 22 | 23 | function validateFunctionListing(func) { 24 | var result = skeemas.validate(func, functionListingSchema); 25 | assert(result.valid, JSON.stringify(result.errors)); 26 | } 27 | 28 | describe('azure-functions', function () { 29 | 30 | var sampleFunction = fs.readFileSync('./test/assets/samplefunction', { 31 | encoding: 'utf8' 32 | }); 33 | var sampleFunctionNames = ['unittesthttp', 'unittesthttp2']; 34 | 35 | describe('#CRUD', function () { 36 | this.timeout(360000); 37 | it('#deployfunction-http-live', function () { 38 | var AzureFunctions = require('../../index'); 39 | var azFunctions = new AzureFunctions(nconf.get('RESOURCE_GROUP_NAME'), 40 | nconf.get('FUNCTION_APP_NAME'), { 41 | subscriptionId: nconf.get('SUBSCRIPTION_ID'), 42 | clientId: nconf.get('CLIENT_ID'), 43 | clientSecret: nconf.get('CLIENT_SECRET'), 44 | domain: nconf.get('AD_DOMAIN') 45 | }); 46 | return azFunctions.deployFunction(sampleFunctionNames[0], sampleFunction, [{ 47 | type: 'http', 48 | direction: 'in', 49 | name: 'req' 50 | }]) 51 | .then(func => { 52 | debug(func); 53 | validateFunctionObject(func); 54 | }); 55 | }); 56 | 57 | it('#deployFunction-http2-live', function () { 58 | var AzureFunctions = require('../../index'); 59 | var azFunctions = new AzureFunctions(nconf.get('RESOURCE_GROUP_NAME'), 60 | nconf.get('FUNCTION_APP_NAME'), { 61 | subscriptionId: nconf.get('SUBSCRIPTION_ID'), 62 | clientId: nconf.get('CLIENT_ID'), 63 | clientSecret: nconf.get('CLIENT_SECRET'), 64 | domain: nconf.get('AD_DOMAIN') 65 | }); 66 | 67 | return azFunctions.deployFunction(sampleFunctionNames[1], sampleFunction, [{ 68 | type: 'http', 69 | direction: 'in', 70 | name: 'req' 71 | }]) 72 | .then(func => { 73 | debug(func); 74 | validateFunctionObject(func); 75 | }); 76 | }); 77 | 78 | it('#listFunctions-live', function () { 79 | var AzureFunctions = require('../../index'); 80 | var azFunctions = new AzureFunctions(nconf.get('RESOURCE_GROUP_NAME'), 81 | nconf.get('FUNCTION_APP_NAME'), { 82 | subscriptionId: nconf.get('SUBSCRIPTION_ID'), 83 | clientId: nconf.get('CLIENT_ID'), 84 | clientSecret: nconf.get('CLIENT_SECRET'), 85 | domain: nconf.get('AD_DOMAIN') 86 | }); 87 | 88 | return azFunctions.listFunctions() 89 | .then(functions => { 90 | debug(functions); 91 | validateFunctionListing(functions); 92 | var funcNames = clone(sampleFunctionNames); 93 | functions.forEach(func => { 94 | var found = false; 95 | funcNames.every((name, index) => { 96 | if (name === func.name.replace(nconf.get('FUNCTION_APP_NAME') + '/', '')) { 97 | found = true; 98 | delete funcNames[index]; 99 | // break; 100 | return false; 101 | } 102 | return true; 103 | }); 104 | assert(found); 105 | }); 106 | }); 107 | }); 108 | }); 109 | 110 | describe('#getFunction-live', function () { 111 | this.timeout(10000); 112 | it('gets an Azure Function', function () { 113 | var AzureFunctions = require('../../index'); 114 | var azFunctions = new AzureFunctions(nconf.get('RESOURCE_GROUP_NAME'), 115 | nconf.get('FUNCTION_APP_NAME'), { 116 | subscriptionId: nconf.get('SUBSCRIPTION_ID'), 117 | clientId: nconf.get('CLIENT_ID'), 118 | clientSecret: nconf.get('CLIENT_SECRET'), 119 | domain: nconf.get('AD_DOMAIN') 120 | }); 121 | 122 | return azFunctions.getFunction('unittesthttp') 123 | .then(func => { 124 | debug(func); 125 | validateFunctionObject(func); 126 | return azFunctions.getFunction('unittesthttp2'); 127 | }) 128 | .then(func => { 129 | validateFunctionObject(func); 130 | }); 131 | }); 132 | }); 133 | 134 | describe('#deleteFunction-live', function () { 135 | this.timeout(30000); 136 | it('deletes an Azure Function', function () { 137 | var AzureFunctions = require('../../index'); 138 | var azFunctions = new AzureFunctions(nconf.get('RESOURCE_GROUP_NAME'), 139 | nconf.get('FUNCTION_APP_NAME'), { 140 | subscriptionId: nconf.get('SUBSCRIPTION_ID'), 141 | clientId: nconf.get('CLIENT_ID'), 142 | clientSecret: nconf.get('CLIENT_SECRET'), 143 | domain: nconf.get('AD_DOMAIN') 144 | }); 145 | 146 | return azFunctions.deleteFunction('unittesthttp') 147 | .then(func => { 148 | debug(func); 149 | validateFunctionObject(func); 150 | return azFunctions.listFunctions(); 151 | }) 152 | .then(functions => { 153 | validateFunctionListing(functions); 154 | assert.equal(functions.length, 1); 155 | }); 156 | }); 157 | }); 158 | }); 159 | -------------------------------------------------------------------------------- /test/mocks/azure-arm-resource.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | var nconf = require('nconf'); 4 | var skeemas = require('skeemas'); 5 | var functionCreateSchema = require('../schemas/function-create'); 6 | var debug = require('debug')('azure-functions:test:mocks:azure-arm-resource'); 7 | var path = require('path'); 8 | 9 | nconf.env().file({ 10 | file: path.join(__dirname, '../assets/.config.json') 11 | }); 12 | var _functionCache = {}; 13 | 14 | class ResourceManagementClient { 15 | constructor(credentials, subscriptionId) { 16 | assert.equal(credentials.constructor.name, 'ApplicationTokenCredentials'); 17 | assert.equal(subscriptionId, nconf.get('SUBSCRIPTION_ID')); 18 | } 19 | 20 | _responseBodyForFunc(funcName, funcData) { 21 | return { 22 | id: '/subscriptions/' + nconf.get('AZURE_SUBSCRIPTION_ID') + '/resourceGroups/' + nconf.get('') + '/providers/Microsot.Web/sites/' + nconf.get('FUNCTION_APP_NAME') + '/functions/' + funcName, 23 | name: nconf.get('FUNCTION_APP_NAME') + '/' + funcName, 24 | location: funcData.location, 25 | type: 'Microsoft.Web/sites/functions', 26 | properties: { 27 | name: funcName, 28 | function_app_id: '/subscriptions/' + nconf.get('AZURE_SUBSCRIPTION_ID') + '/resourceGroups/' + nconf.get('RESOURCE_GROUP_NAME') + '/providers/Microsot.Web/sites/' + nconf.get('FUNCTION_APP_NAME'), 29 | script_root_path_href: 'https://' + nconf.get('FUNCTION_APP_NAME') + '.scm.azurewebsites.net/api/vfs/site/wwwroot/' + funcName, 30 | script_href: 'https://serverlessdemo.scm.azurewebsites.net/api/vfs/site/wwwroot/unittesthttp/index.js', 31 | config_href: 'https://serverlessdemo.scm.azurewebsites.net/api/vfs/site/wwwroot/unittesthttp/function.json', 32 | test_data_href: 'https://serverlessdemo.scm.azurewebsites.net/api/vfs/site/wwwroot/unittesthttp/function.json', 33 | secrets_file_href: 'https://serverlessdemo.scm.azurewebsites.net/api/vfs/site/wwwroot/unittesthttp/function.json', 34 | href: 'https://serverlessdemo.scm.azurewebsites.net/api/vfs/site/wwwroot/unittesthttp/function.json', 35 | config: { 36 | bindings: funcData.properties.bindings 37 | }, 38 | files: null, 39 | test_data: '' 40 | } 41 | }; 42 | } 43 | pipeline(httpRequest, callback) { 44 | assert.equal(httpRequest.constructor.name, 'WebResource'); 45 | var rgRegex = new RegExp('\/subscriptions\/' + nconf.get('SUBSCRIPTION_ID') + '\/resourceGroups\/' + nconf.get('RESOURCE_GROUP_NAME')); 46 | var functionRegex = new RegExp('\/providers\/Microsoft\.Web\/sites\/' + nconf.get('FUNCTION_APP_NAME') + '\/functions\/'); 47 | var functionsRegex = new RegExp('\/providers\/Microsoft\.Web\/sites\/' + nconf.get('FUNCTION_APP_NAME') + '\/functions'); 48 | 49 | var response = { 50 | statusCode: 200 51 | }; 52 | var responseBody; 53 | debug('URL: ' + httpRequest.url); 54 | if (httpRequest.method === 'PUT' && httpRequest.url.match(functionRegex)) { 55 | debug('MOCK CREATE FUNCTION'); 56 | // create function 57 | let body = JSON.parse(httpRequest.body); 58 | let validation = skeemas.validate(body, functionCreateSchema); 59 | let parts = httpRequest.url.split('/'); 60 | let funcName = parts[parts.length - 1].split('?')[0]; 61 | assert(validation.valid, validation.errors); 62 | responseBody = JSON.stringify(this._responseBodyForFunc(funcName, body)); 63 | response.statusCode = 204; 64 | _functionCache[funcName] = body; 65 | debug('current function cache:'); 66 | debug(_functionCache); 67 | } else if (httpRequest.method === 'GET' && httpRequest.url.match(functionRegex)) { 68 | debug('MOCK GET FUNCTION'); 69 | // get function 70 | let parts = httpRequest.url.split('/'); 71 | let funcName = parts[parts.length - 1].split('?')[0]; 72 | debug('function cache:'); 73 | debug(_functionCache); 74 | if (!_functionCache[funcName]) { 75 | response.statusCode = 404; 76 | // not an actual azure response body but it works 77 | responseBody = JSON.stringify({ 78 | message: 'not found' 79 | }); 80 | } else { 81 | responseBody = JSON.stringify(this._responseBodyForFunc(funcName, _functionCache[funcName])); 82 | } 83 | } else if (httpRequest.method === 'DELETE' && httpRequest.url.match(functionRegex)) { 84 | debug('MOCK DELETE FUNCTION'); 85 | // get function 86 | let parts = httpRequest.url.split('/'); 87 | let funcName = parts[parts.length - 1].split('?')[0]; 88 | debug('function cache:'); 89 | debug(_functionCache); 90 | if (!_functionCache[funcName]) { 91 | response.statusCode = 404; 92 | // not an actual azure response body but it works 93 | responseBody = JSON.stringify({ 94 | message: 'not found' 95 | }); 96 | } else { 97 | delete _functionCache[funcName]; 98 | responseBody = ''; 99 | } 100 | } else if (httpRequest.method === 'GET' && httpRequest.url.match(functionsRegex)) { 101 | debug('MOCK LIST FUNCTIONS'); 102 | // list functions 103 | let functionListing = []; 104 | debug('function cache:'); 105 | debug(_functionCache); 106 | for (let key in _functionCache) { 107 | if (typeof key === 'string') { 108 | functionListing.push(this._responseBodyForFunc(key, _functionCache[key])); 109 | } 110 | } 111 | debug('response function listing:'); 112 | debug({ 113 | value: functionListing 114 | }); 115 | responseBody = JSON.stringify({ 116 | value: functionListing 117 | }); 118 | } else if (httpRequest.method === 'GET' && httpRequest.url.match(rgRegex)) { 119 | debug('MOCK GET FUNCTION'); 120 | debug(httpRequest.url); 121 | responseBody = JSON.stringify({ 122 | location: 'West US' 123 | }); 124 | } else { 125 | response.statusCode = 400; 126 | // not an actual azure response body but it works 127 | responseBody = JSON.stringify({ 128 | message: 'bad request' 129 | }); 130 | } 131 | 132 | var util = function () {}; 133 | 134 | return callback(null, response, responseBody, util); 135 | } 136 | } 137 | 138 | module.exports = { 139 | ResourceManagementClient: ResourceManagementClient 140 | }; 141 | -------------------------------------------------------------------------------- /test/azure-functions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* globals describe, it, before, after*/ 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var nconf = require('nconf'); 6 | var assert = require('assert'); 7 | var mockery = require('mockery'); 8 | var debug = require('debug')('azure-functions:test:live:azure-functions'); 9 | var skeemas = require('skeemas'); 10 | var clone = require('clone'); 11 | var functionSchema = require('./schemas/function'); 12 | var functionListingSchema = require('./schemas/function-listing'); 13 | var resourceManagementMock = require('./mocks/azure-arm-resource'); 14 | 15 | nconf.env().file({ 16 | file: path.join(__dirname, '/assets/.config-mock.json') 17 | }); 18 | 19 | function validateFunctionObject(func) { 20 | var result = skeemas.validate(func, functionSchema); 21 | assert(result.valid, JSON.stringify(result.errors)); 22 | } 23 | 24 | function validateFunctionListing(func) { 25 | var result = skeemas.validate(func, functionListingSchema); 26 | assert(result.valid, JSON.stringify(result.errors)); 27 | } 28 | 29 | describe('azure-functions-mock', function () { 30 | 31 | before(function () { 32 | mockery.enable({ 33 | warnOnReplace: false, 34 | warnOnUnregistered: false, 35 | useCleanCache: true 36 | }); 37 | mockery.registerMock('azure-arm-resource', resourceManagementMock); 38 | }); 39 | 40 | after(function () { 41 | mockery.deregisterMock('azure-arm-resource', resourceManagementMock); 42 | mockery.disable({ 43 | warnOnReplace: false, 44 | warnOnUnregistered: false 45 | }); 46 | }); 47 | 48 | var sampleFunction = fs.readFileSync('./test/assets/samplefunction', { 49 | encoding: 'utf8' 50 | }); 51 | var sampleFunctionNames = ['unittesthttp', 'unittesthttp2']; 52 | 53 | describe('#CRUD', function () { 54 | this.timeout(360000); 55 | it('#deployfunction-http-mock', function () { 56 | var AzureFunctions = require('../index'); 57 | var azFunctions = new AzureFunctions(nconf.get('RESOURCE_GROUP_NAME'), 58 | nconf.get('FUNCTION_APP_NAME'), { 59 | subscriptionId: nconf.get('SUBSCRIPTION_ID'), 60 | clientId: nconf.get('CLIENT_ID'), 61 | clientSecret: nconf.get('CLIENT_SECRET'), 62 | domain: nconf.get('AD_DOMAIN') 63 | }); 64 | 65 | return azFunctions.deployFunction(sampleFunctionNames[0], sampleFunction, [{ 66 | type: 'http', 67 | direction: 'in', 68 | name: 'req' 69 | }]) 70 | .then(func => { 71 | validateFunctionObject(func); 72 | }); 73 | }); 74 | 75 | it('#deployfunction-http2-mock', function () { 76 | var AzureFunctions = require('../index'); 77 | var azFunctions = new AzureFunctions(nconf.get('RESOURCE_GROUP_NAME'), 78 | nconf.get('FUNCTION_APP_NAME'), { 79 | subscriptionId: nconf.get('SUBSCRIPTION_ID'), 80 | clientId: nconf.get('CLIENT_ID'), 81 | clientSecret: nconf.get('CLIENT_SECRET'), 82 | domain: nconf.get('AD_DOMAIN') 83 | }); 84 | 85 | return azFunctions.deployFunction(sampleFunctionNames[1], sampleFunction, [{ 86 | type: 'http', 87 | direction: 'in', 88 | name: 'req' 89 | }]) 90 | .then(func => { 91 | validateFunctionObject(func); 92 | }); 93 | }); 94 | 95 | it('#listFunctions-mock', function () { 96 | var AzureFunctions = require('../index'); 97 | var azFunctions = new AzureFunctions(nconf.get('RESOURCE_GROUP_NAME'), 98 | nconf.get('FUNCTION_APP_NAME'), { 99 | subscriptionId: nconf.get('SUBSCRIPTION_ID'), 100 | clientId: nconf.get('CLIENT_ID'), 101 | clientSecret: nconf.get('CLIENT_SECRET'), 102 | domain: nconf.get('AD_DOMAIN') 103 | }); 104 | 105 | return azFunctions.listFunctions() 106 | .then(functions => { 107 | debug('function listing:'); 108 | debug(functions); 109 | validateFunctionListing(functions); 110 | var funcNames = clone(sampleFunctionNames); 111 | functions.forEach(func => { 112 | var found = false; 113 | funcNames.every((name, index) => { 114 | if (name === func.name.replace(nconf.get('FUNCTION_APP_NAME') + '/', '')) { 115 | found = true; 116 | delete funcNames[index]; 117 | // break; 118 | return false; 119 | } 120 | return true; 121 | }); 122 | assert(found); 123 | }); 124 | }); 125 | }); 126 | }); 127 | 128 | describe('#getFunction-mock', function () { 129 | this.timeout(10000); 130 | it('gets an Azure Function', function () { 131 | var AzureFunctions = require('../index'); 132 | var azFunctions = new AzureFunctions(nconf.get('RESOURCE_GROUP_NAME'), 133 | nconf.get('FUNCTION_APP_NAME'), { 134 | subscriptionId: nconf.get('SUBSCRIPTION_ID'), 135 | clientId: nconf.get('CLIENT_ID'), 136 | clientSecret: nconf.get('CLIENT_SECRET'), 137 | domain: nconf.get('AD_DOMAIN') 138 | }); 139 | 140 | return azFunctions.getFunction('unittesthttp') 141 | .then(func => { 142 | debug('validating function:'); 143 | debug(func); 144 | validateFunctionObject(func); 145 | return azFunctions.getFunction('unittesthttp2'); 146 | }) 147 | .then(func => { 148 | validateFunctionObject(func); 149 | }); 150 | }); 151 | }); 152 | 153 | describe('#deleteFunction-mock', function () { 154 | this.timeout(30000); 155 | it('deletes an Azure Function', function () { 156 | var AzureFunctions = require('../index'); 157 | var azFunctions = new AzureFunctions(nconf.get('RESOURCE_GROUP_NAME'), 158 | nconf.get('FUNCTION_APP_NAME'), { 159 | subscriptionId: nconf.get('SUBSCRIPTION_ID'), 160 | clientId: nconf.get('CLIENT_ID'), 161 | clientSecret: nconf.get('CLIENT_SECRET'), 162 | domain: nconf.get('AD_DOMAIN') 163 | }); 164 | 165 | return azFunctions.deleteFunction('unittesthttp') 166 | .then(func => { 167 | validateFunctionObject(func); 168 | return azFunctions.listFunctions(); 169 | }) 170 | .then(functions => { 171 | validateFunctionListing(functions); 172 | assert.equal(functions.length, 1); 173 | }); 174 | }); 175 | }); 176 | }); 177 | -------------------------------------------------------------------------------- /modules/azure-functions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var debug = require('debug')('azure-functions:azure-functions'); 3 | var msRest = require('ms-rest'); 4 | var WebResource = msRest.WebResource; 5 | var msRestAzure = require('ms-rest-azure'); 6 | var resourceManagement = require('azure-arm-resource'); 7 | const BbPromise = require('bluebird'); 8 | 9 | /** 10 | * AzureFunctions Allows for easy interaction for manipulating Azure Functions 11 | * 12 | * @@class 13 | */ 14 | class AzureFunctions { 15 | /** 16 | * Creates an instance of AzureFunctions 17 | * 18 | * @constructor 19 | * @this {AzureFunctions} 20 | * @param {object} [subscriptionDetails] 21 | * @param {object} [subscriptionDetails.subscriptionId] Azure Subscription Id 22 | * @param {object} [subscriptionDetails.clientId] Service Principal Client Id 23 | * @param {object} [subscriptionDetails.clientId] Service Principal Client Secret 24 | * @param {object} [subscriptionDetails.domain] Azure Active Directory Domain (eg: yourapp.com) 25 | */ 26 | constructor(resourceGroupName, functionAppName, subscriptionDetails) { 27 | debug(subscriptionDetails); 28 | this.resourceGroupName = resourceGroupName; 29 | this._subscriptionDetails = subscriptionDetails; 30 | this.functionAppName = functionAppName; 31 | this.subscriptionDetails = subscriptionDetails; 32 | this._credentials = new msRestAzure.ApplicationTokenCredentials(subscriptionDetails.clientId, 33 | subscriptionDetails.domain, 34 | subscriptionDetails.clientSecret); 35 | this._rmClient = BbPromise.promisifyAll( 36 | new resourceManagement.ResourceManagementClient(this._credentials, 37 | subscriptionDetails.subscriptionId), { 38 | multiArgs: true 39 | }); 40 | this._rmClient.apiVersion = '2015-08-01'; 41 | } 42 | 43 | /** 44 | * Gets all Azure Functions in the function app 45 | * 46 | * @method 47 | * @return {Promise} An array of Function objects. 48 | */ 49 | listFunctions() { 50 | var requestUrl = this._buildBaseUrl(); 51 | requestUrl = requestUrl + '/providers/Microsoft.Web/sites/' + this.functionAppName + '/functions'; 52 | 53 | return this._performRequest(requestUrl) 54 | .then(functionListing => { 55 | return functionListing.value; 56 | }); 57 | } 58 | 59 | /** 60 | * Gets an Azure Function 61 | * 62 | * @method 63 | * @param {string} name The name of the function 64 | * @return {array} Ann array of Function objects. 65 | */ 66 | getFunction(name) { 67 | var requestUrl = this._buildBaseUrl(); 68 | requestUrl = requestUrl + '/providers/Microsoft.Web/sites/' + this.functionAppName + '/functions/' + name; 69 | 70 | return this._performRequest(requestUrl) 71 | .then(functionListing => { 72 | return functionListing; 73 | }); 74 | } 75 | 76 | /** 77 | * Deploys a Function to the Functions App 78 | * 79 | * @method 80 | * @param {string} name The name of the function to deploy 81 | * @param {string} functionContent The code that defines the function logic 82 | * @param {obejct} binding The Azure Function bindings 83 | * @return {Promise} A promise that resolves when the function is deployed 84 | */ 85 | deployFunction(name, functionContent, bindings) { 86 | 87 | return this._performRequest(this._buildBaseUrl(), 'GET', null, '2016-02-01') 88 | .then(group => { 89 | var requestUrl = this._buildBaseUrl(); 90 | requestUrl = requestUrl + '/providers/Microsoft.Web/sites/' + this.functionAppName + '/functions/' + name; 91 | 92 | if (!Array.isArray(bindings)) { 93 | throw new Error('bindings must be an array'); 94 | } 95 | return this._performRequest(requestUrl, 'PUT', { 96 | location: group.location, 97 | properties: { 98 | config: { 99 | bindings: bindings 100 | }, 101 | files: { 102 | 'index.js': functionContent 103 | } 104 | } 105 | }); 106 | }); 107 | } 108 | 109 | /** 110 | * Deletes a Function from the Functions App 111 | * 112 | * @method 113 | * @param {string} name The name of the function to delete 114 | * @return {Promise} A promise that resolves when the function is deleted 115 | */ 116 | deleteFunction(name) { 117 | var requestUrl = this._buildBaseUrl(); 118 | requestUrl = requestUrl + '/providers/Microsoft.Web/sites/' + this.functionAppName + '/functions/' + name; 119 | 120 | return this._performRequest(requestUrl, 'DELETE') 121 | .then(functionListing => { 122 | return functionListing; 123 | }); 124 | } 125 | 126 | /** 127 | * Generates the base url for all Azure Functions REST request 128 | * 129 | * @method 130 | * @return {string} The base url for Azure Functions REST requests 131 | */ 132 | _buildBaseUrl() { 133 | var requestUrl = this._rmClient.baseUri + '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}'; 134 | requestUrl = requestUrl.replace('{subscriptionId}', this.subscriptionDetails.subscriptionId); 135 | requestUrl = requestUrl.replace('{resourceGroupName}', this.resourceGroupName); 136 | return requestUrl; 137 | } 138 | 139 | /** 140 | * Performs authenticated call to ARM Api. Lifted from Azure/autorest 141 | * 142 | * @method 143 | * @param {string} requestUrl The full url to make the request call to 144 | * @param {string} method The HTTP method 145 | * @param {object|Readable} body The request body. Can be a Readable stream as well. 146 | * @return {Promise} A promise that resolves with the response body 147 | */ 148 | _performRequest(requestUrl, method, body, apiVersion) { 149 | if (!method) { 150 | method = 'GET'; 151 | } 152 | 153 | var httpRequest = new WebResource(); 154 | var client = this._rmClient; 155 | httpRequest.method = method; 156 | httpRequest.headers = {}; 157 | if (method === 'POST' || method === 'PUT' || method === 'PATCH') { 158 | if (body.constructor.name === 'ReadStream') { 159 | httpRequest.body = body; 160 | } else { 161 | httpRequest.headers['Content-Type'] = 'application/json; charset=utf-8'; 162 | httpRequest.body = JSON.stringify(body); 163 | } 164 | 165 | } else { 166 | httpRequest.body = null; 167 | } 168 | httpRequest.headers['x-ms-client-request-id'] = msRestAzure.generateUuid(); 169 | 170 | var queryParameters = []; 171 | queryParameters.push('api-version=' + encodeURIComponent(apiVersion || client.apiVersion)); 172 | 173 | if (queryParameters.length > 0) { 174 | requestUrl += '?' + queryParameters.join('&'); 175 | } 176 | // trim all duplicate forward slashes in the url 177 | var regex = /([^:]\/)\/+/gi; 178 | requestUrl = requestUrl.replace(regex, '$1'); 179 | 180 | httpRequest.url = requestUrl; 181 | // this logic is mostly from the Azure auto-rest generated code 182 | return client.pipelineAsync(httpRequest) 183 | .spread((response, responseBody) => { 184 | var statusCode = response.statusCode; 185 | 186 | if (statusCode < 200 || statusCode > 299) { 187 | var errorResponse = JSON.parse(responseBody); 188 | throw new Error(errorResponse.error.code, errorResponse.error.message); 189 | } 190 | // Create Result 191 | var result = null; 192 | if (responseBody === '') { 193 | responseBody = null; 194 | } 195 | 196 | result = JSON.parse(responseBody); 197 | return result; 198 | }); 199 | } 200 | } 201 | 202 | module.exports = AzureFunctions; 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/sedouard/azure-functions-node.svg?branch=master)](https://travis-ci.org/sedouard/azure-functions-node) [![Code Climate](https://codeclimate.com/github/sedouard/azure-functions-node/badges/gpa.svg)](https://codeclimate.com/github/sedouard/azure-functions-node) [![Test Coverage](https://codeclimate.com/github/sedouard/azure-functions-node/badges/coverage.svg)](https://codeclimate.com/github/sedouard/azure-functions-node/coverage) 2 | # Azure Functions Node.js Client Library 3 | 4 | List, deploy and delete [Azure Functions](https://azure.microsoft.com/en-us/services/functions/) via Node.js. 5 | 6 | **NOTE**: this is not an official Microsoft library. If you're looking for the Azure Functions CLI, see [azure-functions-cli](https://www.npmjs.com/package/azure-functions-cli). 7 | 8 | ## Getting Setup 9 | 10 | Install the module by doing: 11 | 12 | ``` 13 | npm install azure-functions 14 | ``` 15 | 16 | ### Creating an Azure Service Principal 17 | 18 | You'll need to create an Azure Service Principal to authenticate into your Azure account. Checkout [this guide](https://azure.microsoft.com/en-us/documentation/articles/resource-group-authenticate-service-principal/) on how you can set this up. 19 | 20 | With the Azure CLI you just need to do: 21 | 22 | ```bash 23 | npm install azure-cli -g 24 | # set mode to azure resource manager 25 | azure config mode arm 26 | # authenticate 27 | azure login 28 | azure ad app create --name "functions" --home-page "https://www.justpickanydomain.org" --identifier-uris "https://www.justpickanydomain.org/example" --password 29 | # output 30 | data: AppId: 4fd39843-c338-417d-b549-a545f584a745 31 | data: ObjectId: 4f8ee977-216a-45c1-9fa3-d023089b2962 32 | data: DisplayName: exampleapp 33 | ... 34 | info: ad app create command OK 35 | # create the service principal. This guid is your CLIENT_ID 36 | azure ad sp create 4fd39843-c338-417d-b549-a545f584a745 37 | # output 38 | info: Executing command ad sp create 39 | - Creating service principal for application 4fd39843-c338-417d-b549-a545f584a74+ 40 | data: Object Id: 7dbc8265-51ed-4038-8e13-31948c7f4ce7 41 | data: Display Name: exampleapp 42 | data: Service Principal Names: 43 | data: 4fd39843-c338-417d-b549-a545f584a745 44 | data: https://www.contoso.org/example 45 | info: ad sp create command OK 46 | # assign a role to this service principal. You need to provide enough access 47 | # to read/write to the Functions App resource 48 | azure role assignment create --objectId 7dbc8265-51ed-4038-8e13-31948c7f4ce7 -o Owner -c /subscriptions/{subscriptionId}/ 49 | ``` 50 | 51 | ## Working with Functions 52 | 53 | Each api in this SDK returns a Promise. 54 | 55 | ### Get a Function 56 | 57 | Getting a function will return a function object. 58 | 59 | ```js 60 | var AzureFunctions = require('azure-functions'); 61 | var azFunctions = new AzureFunctions('RESOURCE_GROUP_NAME', 62 | 'FUNCTION_APP_NAME', { 63 | subscriptionId: 'SUBSCRIPTION_ID', 64 | clientId: 'CLIENT_ID', 65 | clientSecret: 'CLIENT_SECRET', 66 | domain: 'AD_DOMAIN' 67 | }); 68 | 69 | return azFunctions.getFunction('unittesthttp') 70 | .then(func => { 71 | validateFunctionObject(func); 72 | return azFunctions.getFunction('unittesthttp2'); 73 | }) 74 | .then(func => { 75 | validateFunctionObject(func); 76 | }); 77 | 78 | /** func object: 79 | { 80 | "id":"/subscriptions/SUBSCRIPTION_ID/resourceGroups/RESOURCE_GROUP_NAME/providers/Microsoft.Web/sites/FUNCTION_APP_NAME/functions/FUNCTION_NAME", 81 | "name":"serverlessdemo/unittesthttp2", 82 | "type":"Microsoft.Web/sites/functions", 83 | "location":"West US", 84 | "properties":{ 85 | "name":"unittesthttp2", 86 | "function_app_id":"/subscriptions/SUBSCRIPTION_ID/resourceGroups/RESOURCE_GROUP_NAME/providers/Microsoft.Web/sites/FUNCTION_APP_NAME", 87 | "script_root_path_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/site/wwwroot/FUNCTION_NAME/", 88 | "script_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/site/wwwroot/FUNCTION_NAME/index.js", 89 | "config_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/site/wwwroot/FUNCTION_NAME/function.json", 90 | "test_data_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/data/functions/sampledata/FUNCTION_NAME.dat", 91 | "secrets_file_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/data/functions/secrets/FUNCTION_NAME.json", 92 | "href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/functions/FUNCTION_NAME", 93 | "config":{ 94 | "bindings":[ 95 | { 96 | "type":"http", 97 | "direction":"in", 98 | "name":"req" 99 | } 100 | ] 101 | }, 102 | "files":null, 103 | "test_data":"" 104 | } 105 | } 106 | **/ 107 | ``` 108 | 109 | ### List All Functions 110 | 111 | Listing functions returns an array of function objects as shown above. 112 | 113 | ```js 114 | var AzureFunctions = require('azure-functions'); 115 | var azFunctions = new AzureFunctions('RESOURCE_GROUP_NAME', 116 | 'FUNCTION_APP_NAME', { 117 | subscriptionId: 'SUBSCRIPTION_ID', 118 | clientId: 'CLIENT_ID', 119 | clientSecret: 'CLIENT_SECRET', 120 | domain: 'AD_DOMAIN' 121 | }); 122 | 123 | return azFunctions.listFunctions('unittesthttp') 124 | .then(functionListing => { 125 | console.log(functionListing); 126 | }); 127 | 128 | /** functionListing Object: 129 | [{ 130 | "id":"/subscriptions/SUBSCRIPTION_ID/resourceGroups/RESOURCE_GROUP_NAME/providers/Microsoft.Web/sites/FUNCTION_APP_NAME/functions/FUNCTION_NAME", 131 | "name":"serverlessdemo/unittesthttp2", 132 | "type":"Microsoft.Web/sites/functions", 133 | "location":"West US", 134 | "properties":{ 135 | "name":"unittesthttp2", 136 | "function_app_id":"/subscriptions/SUBSCRIPTION_ID/resourceGroups/RESOURCE_GROUP_NAME/providers/Microsoft.Web/sites/FUNCTION_APP_NAME", 137 | "script_root_path_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/site/wwwroot/FUNCTION_NAME/", 138 | "script_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/site/wwwroot/FUNCTION_NAME/index.js", 139 | "config_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/site/wwwroot/FUNCTION_NAME/function.json", 140 | "test_data_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/data/functions/sampledata/FUNCTION_NAME.dat", 141 | "secrets_file_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/data/functions/secrets/FUNCTION_NAME.json", 142 | "href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/functions/FUNCTION_NAME", 143 | "config":{ 144 | "bindings":[ 145 | { 146 | "type":"http", 147 | "direction":"in", 148 | "name":"req" 149 | } 150 | ] 151 | }, 152 | "files":null, 153 | "test_data":"" 154 | } 155 | }, 156 | { 157 | "id":"/subscriptions/SUBSCRIPTION_ID/resourceGroups/RESOURCE_GROUP_NAME/providers/Microsoft.Web/sites/FUNCTION_APP_NAME/functions/FUNCTION_NAME", 158 | "name":"serverlessdemo/unittesthttp2", 159 | "type":"Microsoft.Web/sites/functions", 160 | "location":"West US", 161 | "properties":{ 162 | "name":"unittesthttp2", 163 | "function_app_id":"/subscriptions/SUBSCRIPTION_ID/resourceGroups/RESOURCE_GROUP_NAME/providers/Microsoft.Web/sites/FUNCTION_APP_NAME", 164 | "script_root_path_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/site/wwwroot/FUNCTION_NAME/", 165 | "script_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/site/wwwroot/FUNCTION_NAME/index.js", 166 | "config_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/site/wwwroot/FUNCTION_NAME/function.json", 167 | "test_data_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/data/functions/sampledata/FUNCTION_NAME.dat", 168 | "secrets_file_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/data/functions/secrets/FUNCTION_NAME.json", 169 | "href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/functions/FUNCTION_NAME", 170 | "config":{ 171 | "bindings":[ 172 | { 173 | "type":"http", 174 | "direction":"in", 175 | "name":"req" 176 | } 177 | ] 178 | }, 179 | "files":null, 180 | "test_data":"" 181 | } 182 | }] 183 | **/ 184 | ``` 185 | 186 | ### Create a Function 187 | 188 | Given a function name and bindings array you can create functions. See the [bindings section](## Function Bindings) for a list of available bindings. 189 | 190 | You can deploy sizeable function, up to several megabytes big. This is useful if you are compiling your functions into a single file. 191 | 192 | ```js 193 | var AzureFunctions = require('azure-functions'); 194 | var azFunctions = new AzureFunctions(nconf.get('RESOURCE_GROUP_NAME'), 195 | nconf.get('FUNCTION_APP_NAME'), { 196 | subscriptionId: nconf.get('SUBSCRIPTION_ID'), 197 | clientId: nconf.get('CLIENT_ID'), 198 | clientSecret: nconf.get('CLIENT_SECRET'), 199 | domain: nconf.get('AD_DOMAIN') 200 | }); 201 | 202 | return azFunctions.deployFunction('functionname', 'var x = \'foo\'; console.log(\'whatever\');', [{ 203 | type: 'http', 204 | direction: 'in', 205 | name: 'req' 206 | }]) 207 | .then(func => { 208 | console.log(func); 209 | }); 210 | 211 | /** func Object 212 | { 213 | "id":"/subscriptions/SUBSCRIPTION_ID/resourceGroups/RESOURCE_GROUP_NAME/providers/Microsoft.Web/sites/FUNCTION_APP_NAME/functions/FUNCTION_NAME", 214 | "name":"serverlessdemo/unittesthttp2", 215 | "type":"Microsoft.Web/sites/functions", 216 | "location":"West US", 217 | "properties":{ 218 | "name":"unittesthttp2", 219 | "function_app_id":"/subscriptions/SUBSCRIPTION_ID/resourceGroups/RESOURCE_GROUP_NAME/providers/Microsoft.Web/sites/FUNCTION_APP_NAME", 220 | "script_root_path_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/site/wwwroot/FUNCTION_NAME/", 221 | "script_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/site/wwwroot/FUNCTION_NAME/index.js", 222 | "config_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/site/wwwroot/FUNCTION_NAME/function.json", 223 | "test_data_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/data/functions/sampledata/FUNCTION_NAME.dat", 224 | "secrets_file_href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/vfs/data/functions/secrets/FUNCTION_NAME.json", 225 | "href":"https://FUNCTION_APP_NAME.scm.azurewebsites.net/api/functions/FUNCTION_NAME", 226 | "config":{ 227 | "bindings":[ 228 | { 229 | "type":"http", 230 | "direction":"in", 231 | "name":"req" 232 | } 233 | ] 234 | }, 235 | "files":null, 236 | "test_data":"" 237 | } 238 | } 239 | **/ 240 | ``` 241 | 242 | ### Delete a Function 243 | 244 | Deleting a function is very straight-forward. 245 | 246 | ```js 247 | var AzureFunctions = require('azure-functions'); 248 | var azFunctions = new AzureFunctions(nconf.get('RESOURCE_GROUP_NAME'), 249 | nconf.get('FUNCTION_APP_NAME'), { 250 | subscriptionId: nconf.get('SUBSCRIPTION_ID'), 251 | clientId: nconf.get('CLIENT_ID'), 252 | clientSecret: nconf.get('CLIENT_SECRET'), 253 | domain: nconf.get('AD_DOMAIN') 254 | }); 255 | 256 | return azFunctions.deleteFunction('functionname') 257 | .then(() => { 258 | console.log('deleted functionname'); 259 | }); 260 | ``` 261 | 262 | ## Function Triggers 263 | 264 | With Azure Functions you can bind a variety of events from Azure services to Azure functions. 265 | 266 | You need to provide this as the `bindings` array to the `createFunction` method. 267 | 268 | ### Blob Trigger 269 | 270 | ```js 271 | [{ path: 'path-within-storage-account', 272 | connection: 'storage-account-connection-string', 273 | name: 'blob-argument-name-for-function', 274 | type: 'blobTrigger', 275 | direction: 'in' 276 | }] 277 | ``` 278 | 279 | ### Eventhub Trigger 280 | 281 | ```js 282 | [{ path: 'eventhub-name', 283 | connection: 'service-bus-connection-string', 284 | type: 'eventHubTrigger', 285 | name: 'event-parameter-name-for-function', 286 | direction: 'in' 287 | }] 288 | ``` 289 | 290 | ### Webhook Trigger 291 | 292 | Webhooks must have their input as an `httpTrigger` and output as `http`. 293 | ```js 294 | [ 295 | { webHookType: 'genericJson', 296 | type: 'httpTrigger', 297 | direction: 'in', 298 | name: 'req' 299 | }, 300 | { type: 'http', direction: 'out', name: 'res' } 301 | ] 302 | ``` 303 | 304 | ### Github Webhook Trigger 305 | 306 | ```js 307 | [{ webHookType: 'github', 308 | type: 'httpTrigger', 309 | direction: 'in', 310 | name: 'req' }, 311 | { type: 'http', direction: 'out', name: 'res' }] 312 | ``` 313 | 314 | ### Storage Account Queue Trigger 315 | 316 | ```js 317 | [{ queueName: 'queue-name', 318 | connection: 'storage-account-name', 319 | name: 'message-parameter-name-for-function', 320 | type: 'queueTrigger', 321 | direction: 'in' }] 322 | ``` 323 | 324 | ### Service Bus Queue Trigger 325 | 326 | ```js 327 | [{ queueName: 'samples-input', 328 | connection: 'service-bus-connection-string', 329 | name: 'message-parameter-name-for-function', 330 | type: 'serviceBusTrigger', 331 | direction: 'in' }] 332 | ``` 333 | 334 | ### Timer Trigger 335 | 336 | ```js 337 | [{ schedule: '0 * * * * *', 338 | name: 'timer-parameter-name-for-function', 339 | type: 'timerTrigger', 340 | direction: 'in' }] 341 | ``` 342 | --------------------------------------------------------------------------------