From 2ca732a051eda014c52caeb974be05169d639cd2 Mon Sep 17 00:00:00 2001 From: pragnagopa <pgopa@microsoft.com> Date: Mon, 30 Jan 2017 10:13:32 -0800 Subject: [PATCH 1/2] Azure Serverless plugin --- .gitignore | 4 + .npmignore | 36 + LICENSE | 21 + README.md | 77 +- deploy/azureDeploy.js | 36 + deploy/azureDeployFunction.js | 28 + .../lib/CreateResourceGroupAndFunctionApp.js | 10 + deploy/lib/cleanUpFunctions.js | 9 + deploy/lib/createFunction.js | 13 + deploy/lib/createFunctions.js | 19 + index.js | 31 + invoke/azureInvoke.js | 45 + invoke/lib/invokeFunction.js | 14 + logs/azureLogs.js | 29 + logs/lib/retrieveLogs.js | 11 + package.json | 42 + provider/armTemplates/azuredeploy.json | 83 + provider/armTemplates/azuredeployWithGit.json | 102 + provider/azureProvider.js | 607 ++++++ remove/azureRemove.js | 28 + remove/lib/deleteResourceGroup.js | 9 + shared/bindings.json | 1741 +++++++++++++++++ shared/getAdminKey.js | 7 + shared/loginToAzure.js | 9 + shared/parseBindings.js | 43 + shared/utils.js | 204 ++ 26 files changed, 3256 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 LICENSE create mode 100644 deploy/azureDeploy.js create mode 100644 deploy/azureDeployFunction.js create mode 100644 deploy/lib/CreateResourceGroupAndFunctionApp.js create mode 100644 deploy/lib/cleanUpFunctions.js create mode 100644 deploy/lib/createFunction.js create mode 100644 deploy/lib/createFunctions.js create mode 100644 index.js create mode 100644 invoke/azureInvoke.js create mode 100644 invoke/lib/invokeFunction.js create mode 100644 logs/azureLogs.js create mode 100644 logs/lib/retrieveLogs.js create mode 100644 package.json create mode 100644 provider/armTemplates/azuredeploy.json create mode 100644 provider/armTemplates/azuredeployWithGit.json create mode 100644 provider/azureProvider.js create mode 100644 remove/azureRemove.js create mode 100644 remove/lib/deleteResourceGroup.js create mode 100644 shared/bindings.json create mode 100644 shared/getAdminKey.js create mode 100644 shared/loginToAzure.js create mode 100644 shared/parseBindings.js create mode 100644 shared/utils.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ab3d6d3b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +*.dll +.eslintrc.js +.eslintrc.json diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..1f7f2257 --- /dev/null +++ b/.npmignore @@ -0,0 +1,36 @@ +# Logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..39c786a0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Serverless + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. diff --git a/README.md b/README.md index fa14d8bb..bcb5f7e5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,75 @@ -# serverless-azure-functions -WIP – Coming soon... +# Azure Functions Plugin + +This plugin enables Azure Functions support within the Serverless Framework. + +## Getting started + + +### Get a Serverless Service with Azure as the Provider + +1. Clone gitrepo: `git clone -b dev https://github.com/pragnagopa/boilerplate-azurefunctions.git`. +2. npm install + +### Get an Azure Subscription + - <a href="https://azure.microsoft.com/en-us/free/?b=17.01" target="_blank">Create your free Azure account today</a> + +### Create Service Principal User for your Azure subscription +1. Create a Service Principal User with <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal" target="_blank">portal</a> or <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authenticate-service-principal-cli" target="_blank">Azure CLI</a> +2. <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal#get-tenant-id" target="_blank">Get tenant ID</a> +3. <a href="https://blogs.msdn.microsoft.com/mschray/2015/05/13/getting-your-azure-guid-subscription-id/" target="_blank">Get Azure subscription ID</a> +4. <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal#get-application-id-and-authentication-key" target="_blank">Get application ID</a>. Note this is also referred to as the client id. + + +### Set the following environment variables: + +- azureSubId: YourAzureSubscriptionID +- azureServicePrincipalTenantId: servicePrincipalTenantId +- azureservicePrincipalClientId: servicePrincipalClientId +- azureServicePrincipalPassword: servicePrincipalPassword + +**Note:** If you created Service Principal User from Portal, servicePrincipalPassword is the authentication key + +### Update the config in `serverless.yml` + +Open up your `serverless.yml` file and update the following information: + +#### `service` property + +```yml +service: my-azure-functions-app # Name of the Azure function App you want to create +``` +### Quick Start + +1. **Deploy a Service:** + + Use this when you have made changes to your Functions or you simply want to deploy all changes within your Service at the same time. + ```bash + serverless deploy + ``` + +2. **Deploy the Function:** + + Use this to quickly upload and overwrite your Azure function, allowing you to develop faster. + ```bash + serverless deploy function -f httpjs + ``` + +3. **Invoke the Function:** + + Invokes an Azure Function on Azure + ```bash + serverless invoke --path httpQueryString.json -f httpjs + ``` + +4. **Stream the Function Logs:** + + Open up a separate tab in your console and stream all logs for a specific Function using this command. + ```bash + serverless logs -f httpjs -t + ``` + +5. **Remove the Service:** + + Removes all Functions and Resources from your Azure subscription. + ```bash + serverless remove diff --git a/deploy/azureDeploy.js b/deploy/azureDeploy.js new file mode 100644 index 00000000..649352c1 --- /dev/null +++ b/deploy/azureDeploy.js @@ -0,0 +1,36 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const CreateResourceGroupAndFunctionApp = require('./lib/CreateResourceGroupAndFunctionApp'); +const createFunctions = require('./lib/createFunctions'); +const cleanUpFunctions = require('./lib/cleanUpFunctions'); +const loginToAzure = require('../shared/loginToAzure'); + +class AzureDeploy { + constructor (serverless, options) { + this.serverless = serverless; + this.options = options; + this.provider = this.serverless.getProvider('azure'); + + Object.assign( + this, + loginToAzure, + cleanUpFunctions, + CreateResourceGroupAndFunctionApp, + createFunctions + ); + + this.hooks = { + 'before:deploy:deploy': () => BbPromise.bind(this) + .then(this.loginToAzure) + .then(this.cleanUpFunctions), + + 'deploy:deploy': () => BbPromise.bind(this) + .then(this.CreateResourceGroupAndFunctionApp) + .then(this.createFunctions) + .then(() => this.serverless.cli.log('Successfully created Function App')) + }; + } +} + +module.exports = AzureDeploy; diff --git a/deploy/azureDeployFunction.js b/deploy/azureDeployFunction.js new file mode 100644 index 00000000..7bc3b247 --- /dev/null +++ b/deploy/azureDeployFunction.js @@ -0,0 +1,28 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const createFunction = require('./lib/createFunction'); +const loginToAzure = require('../shared/loginToAzure'); + +class AzureDeployFunction { + constructor (serverless, options) { + this.serverless = serverless; + this.options = options; + this.provider = this.serverless.getProvider('azure'); + + Object.assign( + this, + loginToAzure, + createFunction + ); + + this.hooks = { + 'deploy:function:deploy': () => BbPromise.bind(this) + .then(this.loginToAzure) + .then(this.createFunction) + .then(() => this.serverless.cli.log('Successfully uploaded Function')) + }; + } +} + +module.exports = AzureDeployFunction; diff --git a/deploy/lib/CreateResourceGroupAndFunctionApp.js b/deploy/lib/CreateResourceGroupAndFunctionApp.js new file mode 100644 index 00000000..3c3e4cd1 --- /dev/null +++ b/deploy/lib/CreateResourceGroupAndFunctionApp.js @@ -0,0 +1,10 @@ +'use strict'; + +module.exports = { + CreateResourceGroupAndFunctionApp () { + +return this.provider.CreateResourceGroup() + .then(() => this.provider.CreateFunctionApp()); + } +}; + diff --git a/deploy/lib/cleanUpFunctions.js b/deploy/lib/cleanUpFunctions.js new file mode 100644 index 00000000..7485d89c --- /dev/null +++ b/deploy/lib/cleanUpFunctions.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = { + cleanUpFunctions () { + return this.provider.isExistingFunctionApp() + .then(() => this.provider.getDeployedFunctionsNames()) + .then(() => this.provider.cleanUpFunctionsBeforeDeploy(this.serverless.service.getAllFunctions())); + } +}; diff --git a/deploy/lib/createFunction.js b/deploy/lib/createFunction.js new file mode 100644 index 00000000..a0c22700 --- /dev/null +++ b/deploy/lib/createFunction.js @@ -0,0 +1,13 @@ +'use strict'; + +const utils = require('../../shared/utils'); + +module.exports = { + createFunction () { + const functionName = this.options.function; + const metaData = utils.getFunctionMetaData(functionName, this.provider.getParsedBindings(), this.serverless); + +return this.provider.createZipObject(functionName, metaData.entryPoint, metaData.handlerPath, metaData.params) + .then(() => this.provider.createAndUploadZipFunctions()); + } +}; diff --git a/deploy/lib/createFunctions.js b/deploy/lib/createFunctions.js new file mode 100644 index 00000000..9247ce0d --- /dev/null +++ b/deploy/lib/createFunctions.js @@ -0,0 +1,19 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const utils = require('../../shared/utils'); + +module.exports = { + createFunctions () { + const createFunctionPromises = []; + + this.serverless.service.getAllFunctions().forEach((functionName) => { + const metaData = utils.getFunctionMetaData(functionName, this.provider.getParsedBindings(), this.serverless); + + createFunctionPromises.push(this.provider.createZipObject(functionName, metaData.entryPoint, metaData.handlerPath, metaData.params)); + }); + + return BbPromise.all(createFunctionPromises) + .then(() => this.provider.createAndUploadZipFunctions()); + } +}; diff --git a/index.js b/index.js new file mode 100644 index 00000000..f2a8d8ce --- /dev/null +++ b/index.js @@ -0,0 +1,31 @@ +'use strict'; + +/* +NOTE: this plugin is used to add all the differnet provider related plugins at once. +This way only one plugin needs to be added to the service in order to get access to the +whole provider implementation. +*/ + +const AzureDeploy = require('./deploy/azureDeploy'); +const AzureDeployFunction = require('./deploy/azureDeployFunction'); +const AzureProvider = require('./provider/azureProvider'); +const AzureInvoke = require('./invoke/azureInvoke'); +const AzureLogs = require('./logs/azureLogs'); +const AzureRemove = require('./remove/azureRemove'); + + +class AzureIndex { + constructor(serverless, options) { + this.serverless = serverless; + this.options = options; + + this.serverless.pluginManager.addPlugin(AzureProvider); + this.serverless.pluginManager.addPlugin(AzureDeploy); + this.serverless.pluginManager.addPlugin(AzureDeployFunction); + this.serverless.pluginManager.addPlugin(AzureInvoke); + this.serverless.pluginManager.addPlugin(AzureLogs); + this.serverless.pluginManager.addPlugin(AzureRemove); + } +} + +module.exports = AzureIndex; diff --git a/invoke/azureInvoke.js b/invoke/azureInvoke.js new file mode 100644 index 00000000..5c2f6c63 --- /dev/null +++ b/invoke/azureInvoke.js @@ -0,0 +1,45 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const invokeFunction = require('./lib/invokeFunction'); +const getAdminKey = require('../shared/getAdminKey'); +const loginToAzure = require('../shared/loginToAzure'); +const path = require('path'); + +class AzureInvoke { + constructor (serverless, options) { + this.serverless = serverless; + this.options = options; + this.provider = this.serverless.getProvider('azure'); + + Object.assign( + this, + loginToAzure, + getAdminKey, + invokeFunction + ); + + if (this.options.path) { + const absolutePath = path.isAbsolute(this.options.path) + ? this.options.path + : path.join(this.serverless.config.servicePath, this.options.path); + + if (!this.serverless.utils.fileExistsSync(absolutePath)) { + throw new this.serverless.classes.Error('The file you provided does not exist.'); + } + this.options.data = this.serverless.utils.readFileSync(absolutePath); + } + + this.hooks = { + + 'before:invoke:invoke': () => BbPromise.bind(this) + .then(this.loginToAzure) + .then(this.getAdminKey), + + 'invoke:invoke': () => BbPromise.bind(this) + .then(this.invokeFunction) + }; + } +} + +module.exports = AzureInvoke; diff --git a/invoke/lib/invokeFunction.js b/invoke/lib/invokeFunction.js new file mode 100644 index 00000000..45d4e1a2 --- /dev/null +++ b/invoke/lib/invokeFunction.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = { + invokeFunction () { + const func = this.options.function; + const functionObject = this.serverless.service.getFunction(func); + const eventType = Object.keys(functionObject.events[0])[0]; + + return this.provider.invoke(func, eventType, this.options.data); + // TODO: Github issue: https://github.com/Azure/azure-webjobs-sdk-script/issues/1122 + // .then(() => this.provider.getInvocationId(func)) + // .then(() => this.provider.getLogsForInvocationId()); + } +}; diff --git a/logs/azureLogs.js b/logs/azureLogs.js new file mode 100644 index 00000000..7d252d6f --- /dev/null +++ b/logs/azureLogs.js @@ -0,0 +1,29 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const retrieveLogs = require('./lib/retrieveLogs'); +const loginToAzure = require('../shared/loginToAzure'); + +class AzureLogs { + constructor (serverless, options) { + this.serverless = serverless; + this.options = options; + this.provider = this.serverless.getProvider('azure'); + + Object.assign( + this, + loginToAzure, + retrieveLogs + ); + + this.hooks = { + 'before:logs:logs': () => BbPromise.bind(this) + .then(this.loginToAzure), + + 'logs:logs': () => BbPromise.bind(this) + .then(this.retrieveLogs) + }; + } +} + +module.exports = AzureLogs; diff --git a/logs/lib/retrieveLogs.js b/logs/lib/retrieveLogs.js new file mode 100644 index 00000000..ad63c14c --- /dev/null +++ b/logs/lib/retrieveLogs.js @@ -0,0 +1,11 @@ +'use strict'; + +module.exports = { + retrieveLogs () { + const functionName = this.options.function; + +return this.provider.getAdminKey() + .then(() => this.provider.pingHostStatus(functionName)) + .then(() => this.provider.getLogsStream(functionName)); + } +}; diff --git a/package.json b/package.json new file mode 100644 index 00000000..0c3e2fe4 --- /dev/null +++ b/package.json @@ -0,0 +1,42 @@ +{ + "name": "serverless-azure-functions", + "version": "0.1.0", + "description": "Provider plugin for the Serverless Framework v1.x which adds support for Azure Functions.", + "license": "MIT", + "main": "index.js", + "author": "Azure Functions", + "repository": { + "git": "https://github.com/serverless/serverless-azure-functions" + }, + "homepage": "https://github.com/serverless/serverless-azure-functions", + "keywords": [ + "serverless", + "serverless framework", + "serverless applications", + "serverless modules", + "azure functions", + "iot", + "internet of things", + "serverless.com" + ], + "dependencies": { + "async": "^2.1.2", + "bluebird": "^3.4.6", + "chalk": "^1.1.3", + "lodash": "^4.16.6", + "fs-extra": "^2.0.0", + "jszip": "^3.1.3", + "request": "2.79.0", + "ms-rest-azure": "^1.15.4", + "azure-arm-resource": "1.6.1-preview" + }, + "devDependencies": { + "eslint": "^3.15.0", + "eslint-config-standard": "^6.2.1", + "eslint-plugin-promise": "^3.4.1", + "eslint-plugin-standard": "^2.0.1" + }, + "engines": { + "node": ">= 6.5.0" + } +} \ No newline at end of file diff --git a/provider/armTemplates/azuredeploy.json b/provider/armTemplates/azuredeploy.json new file mode 100644 index 00000000..2481445e --- /dev/null +++ b/provider/armTemplates/azuredeploy.json @@ -0,0 +1,83 @@ +{ + "$schema": "http://schemas.management.azure.com/schemas/2015-01-01-preview/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "functionAppName": { + "type": "string", + "metadata": { + "description": "The name of the function app that you wish to create." + } + }, + "storageAccountType": { + "type": "string", + "defaultValue": "Standard_LRS", + "allowedValues": [ + "Standard_LRS", + "Standard_GRS", + "Standard_ZRS", + "Premium_LRS" + ], + "metadata": { + "description": "Storage Account type" + } + } + }, + "variables": { + "functionAppName": "[parameters('functionAppName')]", + "hostingPlanName": "[parameters('functionAppName')]", + "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'azfunctions')]" + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "name": "[variables('storageAccountName')]", + "apiVersion": "2015-06-15", + "location": "[resourceGroup().location]", + "properties": { + "accountType": "[parameters('storageAccountType')]" + } + }, + { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2015-04-01", + "name": "[variables('hostingPlanName')]", + "location": "[resourceGroup().location]", + "properties": { + "name": "[variables('hostingPlanName')]", + "computeMode": "Dynamic", + "sku": "Dynamic" + } + }, + { + "apiVersion": "2015-08-01", + "type": "Microsoft.Web/sites", + "name": "[variables('functionAppName')]", + "location": "[resourceGroup().location]", + "kind": "functionapp", + "properties": { + "name": "[variables('functionAppName')]", + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" + ], + "resources": [ + { + "apiVersion": "2016-03-01", + "name": "appsettings", + "type": "config", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" + ], + "properties": { + "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]", + "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]", + "FUNCTIONS_EXTENSION_VERSION": "~1" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/provider/armTemplates/azuredeployWithGit.json b/provider/armTemplates/azuredeployWithGit.json new file mode 100644 index 00000000..38dad76d --- /dev/null +++ b/provider/armTemplates/azuredeployWithGit.json @@ -0,0 +1,102 @@ +{ + "$schema": "http://schemas.management.azure.com/schemas/2015-01-01-preview/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "functionAppName": { + "type": "string", + "metadata": { + "description": "The name of the function app that you wish to create." + } + }, + "storageAccountType": { + "type": "string", + "defaultValue": "Standard_LRS", + "allowedValues": [ + "Standard_LRS", + "Standard_GRS", + "Standard_ZRS", + "Premium_LRS" + ], + "metadata": { + "description": "Storage Account type" + } + }, + "gitUrl": { + "type": "string", + "metadata": { + "description": "Git URL" + } + } + }, + "variables": { + "functionAppName": "[parameters('functionAppName')]", + "hostingPlanName": "[parameters('functionAppName')]", + "gitRepoUrl": "[parameters('gitUrl')]", + "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'azfunctions')]" + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "name": "[variables('storageAccountName')]", + "apiVersion": "2015-06-15", + "location": "[resourceGroup().location]", + "properties": { + "accountType": "[parameters('storageAccountType')]" + } + }, + { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2015-04-01", + "name": "[variables('hostingPlanName')]", + "location": "[resourceGroup().location]", + "properties": { + "name": "[variables('hostingPlanName')]", + "computeMode": "Dynamic", + "sku": "Dynamic" + } + }, + { + "apiVersion": "2015-08-01", + "type": "Microsoft.Web/sites", + "name": "[variables('functionAppName')]", + "location": "[resourceGroup().location]", + "kind": "functionapp", + "properties": { + "name": "[variables('functionAppName')]", + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" + ], + "resources": [ + { + "apiVersion": "2016-03-01", + "name": "appsettings", + "type": "config", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" + ], + "properties": { + "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]", + "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]", + "FUNCTIONS_EXTENSION_VERSION": "~1" + } + }, + { + "apiVersion": "2016-03-01", + "name": "web", + "type": "sourcecontrols", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]" + ], + "properties": { + "repoUrl": "[variables('gitRepoUrl')]", + "IsManualIntegration": true + } + } + ] + } + ] +} \ No newline at end of file diff --git a/provider/azureProvider.js b/provider/azureProvider.js new file mode 100644 index 00000000..b5ed7543 --- /dev/null +++ b/provider/azureProvider.js @@ -0,0 +1,607 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const _ = require('lodash'); +const msRestAzure = require('ms-rest-azure'); +const resourceManagement = require('azure-arm-resource'); +const path = require('path'); +const fs = require('fs'); +const fse = require('fs-extra'); +const https = require('https'); +const JSZip = require('jszip'); +const request = require('request'); +const dns = require('dns'); +const parseBindings = require('../shared/parseBindings'); + +let resourceGroupName; +let deploymentName; +let functionAppName; +let subscriptionId; +let servicePrincipalTenantId; +let servicePrincipalClientId; +let servicePrincipalPassword; +let functionsAdminKey; +let invocationId; +let oldLogs = ''; +let principalCredentials; +let functionsFolder; +let existingFunctionApp = false; +let parsedBindings; +const zipArray = []; +const deployedFunctionNames = []; + +const constants = { + 'authorizationHeader': 'Authorization', + 'bearer': 'Bearer ', + 'contentTypeHeader': 'Content-Type', + 'functionAppApiPath': '/api/', + 'functionAppDomain': '.azurewebsites.net', + 'functionsAdminApiPath': '.azurewebsites.net/admin/functions/', + 'functionsApiPath': '/api/functions', + 'jsonContentType': 'application/json', + 'logInvocationsApiPath': '/azurejobs/api/functions/definitions/', + 'logOutputApiPath': '/azurejobs/api/log/output/', + 'logStreamApiPath': '/api/logstream/application/functions/function/', + 'masterKeyApiApth': '/api/functions/admin/masterkey', + 'providerName': 'azure', + 'scmDomain': '.scm.azurewebsites.net', + 'scmVfsPath': '.scm.azurewebsites.net/api/vfs/site/wwwroot/', + 'scmZipApiPath': '.scm.azurewebsites.net/api/zip/site/wwwroot/' +}; + +const azureCredentials = { + 'azureSubId': 'azureSubId', + 'azureServicePrincipalTenantId': 'azureServicePrincipalTenantId', + 'azureservicePrincipalClientId': 'azureservicePrincipalClientId', + 'azureServicePrincipalPassword': 'azureServicePrincipalPassword', +}; + +class AzureProvider { + static getProviderName () { + return constants.providerName; + } + + constructor (serverless) { + this.serverless = serverless; + this.provider = this; + this.serverless.setProvider(constants.providerName, this); + subscriptionId = process.env[azureCredentials.azureSubId]; + servicePrincipalTenantId = process.env[azureCredentials.azureServicePrincipalTenantId]; + servicePrincipalClientId = process.env[azureCredentials.azureservicePrincipalClientId]; + servicePrincipalPassword = process.env[azureCredentials.azureServicePrincipalPassword]; + + functionAppName = this.serverless.service.service; + resourceGroupName = `${functionAppName}-rg`; + deploymentName = `${resourceGroupName}-deployment`; + functionsFolder = path.join(this.serverless.config.servicePath, 'functions'); + } + + getParsedBindings () { + if (!this.parsedBindings) { + this.parsedBindings = parseBindings.getBindingsMetaData(this.serverless); + } + +return this.parsedBindings; + } + + LoginWithServicePrincipal () { + return new BbPromise((resolve, reject) => { + msRestAzure.loginWithServicePrincipalSecret(servicePrincipalClientId, servicePrincipalPassword, servicePrincipalTenantId, (error, credentials) => { + if (error) { + reject(error); + } else { + principalCredentials = credentials; + resolve(credentials); + } + }); + }); + } + + CreateResourceGroup () { + const groupParameters = { +'location': this.serverless.service.provider.location, +'tags': {'sampletag': 'sampleValue'} +}; + + this.serverless.cli.log(`Creating resource group: ${resourceGroupName}`); + const resourceClient = new resourceManagement.ResourceManagementClient(principalCredentials, subscriptionId); + +return new BbPromise((resolve, reject) => { + resourceClient.resourceGroups.createOrUpdate(resourceGroupName, + groupParameters, (error, result, createOrUpdateRequest, response) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + } + + CreateFunctionApp (method, params) { + this.serverless.cli.log(`Creating function app: ${functionAppName}`); + const resourceClient = new resourceManagement.ResourceManagementClient(principalCredentials, subscriptionId); + let parameters = {'functionAppName': {'value': functionAppName}}; + + const gitUrl = this.serverless.service.provider.gitUrl; + + if (gitUrl) { + parameters = { + 'functionAppName': {'value': functionAppName}, + 'gitUrl': {'value': gitUrl} + }; + } + + let templateFilePath = path.join(__dirname, 'armTemplates', 'azuredeploy.json'); + + if (gitUrl) { + templateFilePath = path.join(__dirname, 'armTemplates', 'azuredeployWithGit.json'); + } + if (this.serverless.service.provider.armTemplate) { + templateFilePath = path.join(this.serverless.config.servicePath, this.serverless.service.provider.armTemplate.file); + const userParameters = this.serverless.service.provider.armTemplate.parameters; + const userParametersKeys = Object.keys(userParameters); + + for (let paramIndex = 0; paramIndex < userParametersKeys.length; paramIndex++) { + const item = {}; + + item[userParametersKeys[paramIndex]] = {'value': userParameters[userParametersKeys[paramIndex]]}; + parameters = _.merge(parameters, item); + } + } + + const template = JSON.parse(fs.readFileSync(templateFilePath, 'utf8')); + const deploymentParameters = { + 'properties': { + 'mode': 'Incremental', + parameters, + template + } + }; + +return new BbPromise((resolve, reject) => { + resourceClient.deployments.createOrUpdate(resourceGroupName, + deploymentName, + deploymentParameters, (error, result, createOrUpdateRequest, response) => { + if (error) { + reject(error); + } else { + this.serverless.cli.log('Waiting for Kudu endpoint...'); + setTimeout(() => { + resolve(result); + }, 10000); + } + }); + }); + } + + DeleteDeployment () { + this.serverless.cli.log(`Deleting deployment: ${deploymentName}`); + const resourceClient = new resourceManagement.ResourceManagementClient(principalCredentials, subscriptionId); + +return new BbPromise((resolve, reject) => { + resourceClient.deployments.deleteMethod(resourceGroupName, + deploymentName, (error, result, deleteRequest, response) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + } + + DeleteResourceGroup () { + this.serverless.cli.log(`Deleting resource group: ${resourceGroupName}`); + const resourceClient = new resourceManagement.ResourceManagementClient(principalCredentials, subscriptionId); + +return new BbPromise((resolve, reject) => { + resourceClient.resourceGroups.deleteMethod(resourceGroupName, (error, result, deleteRequest, response) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + } + + getAdminKey () { + const options = { + 'host': functionAppName + constants.scmDomain, + 'port': 443, + 'path': constants.masterKeyApiApth, + 'headers': { + 'Authorization': constants.bearer + principalCredentials.tokenCache._entries[0].accessToken, + 'Content-Type': constants.jsonContentType + } + }; + + return new BbPromise((resolve, reject) => { + https.get(options, (res) => { + let body = ''; + + res.on('data', (data) => { + body += data; + }); + res.on('end', () => { + const parsed = JSON.parse(body); + + functionsAdminKey = parsed.masterKey; + resolve(res); + }); + res.on('error', (responseError) => { + reject(responseError); + }); + }); + }); + } + + pingHostStatus (functionName) { + const requestUrl = `https://${functionAppName}${constants.functionAppDomain}/admin/functions/${functionName}/status`; + const options = { + 'host': functionAppName + constants.functionAppDomain, + 'method': 'get', + 'url': requestUrl, + 'headers': { + 'x-functions-key': functionsAdminKey, + 'Accept': 'application/json,*/*' + } + }; + + return new BbPromise((resolve, reject) => { + this.serverless.cli.log('Pinging host status...'); + request(options, (err, res, body) => { + if (err) { + reject(err); + } else { + resolve(res); + } + }); + }); + } + + isExistingFunctionApp () { + const host = functionAppName + constants.scmDomain; + +return new BbPromise((resolve, reject) => { + dns.resolve4(host, (err, addresses) => { + if (err) { + if (err.message.includes('ENOTFOUND')) { + resolve(existingFunctionApp); + } else { + reject(err); + } + } else { + existingFunctionApp = true; + resolve(existingFunctionApp); + } + }); + }); + } + + getDeployedFunctionsNames () { + const requestUrl = `https://${functionAppName}${constants.scmDomain}${constants.functionsApiPath}`; + const options = { + 'host': functionAppName + constants.scmDomain, + 'method': 'get', + 'url': requestUrl, + 'headers': { + 'Authorization': constants.bearer + principalCredentials.tokenCache._entries[0].accessToken, + 'Accept': 'application/json,*/*' + } + }; + +return new BbPromise((resolve, reject) => { + if (existingFunctionApp) { + this.serverless.cli.log('Looking for deployed functions that are not part of the current deployment...'); + request(options, (err, res, body) => { + if (err) { + if (err.message.includes('ENOTFOUND')) { + resolve(res); + } else { + reject(err); + } + } else { + if (res.statusCode === 200) { + const parsed = JSON.parse(body); + + for (let functionNamesIndex = 0; functionNamesIndex < parsed.length; functionNamesIndex++) { + deployedFunctionNames.push(parsed[functionNamesIndex].name); + } + } + resolve(res); + } + }); + } else { + resolve('New service...'); + } + }); + } + + getLogsStream (functionName) { + const logOptions = { + 'host': functionAppName + constants.scmDomain, + 'port': 443, + 'path': constants.logStreamApiPath + functionName, + 'headers': { + 'Authorization': constants.bearer + principalCredentials.tokenCache._entries[0].accessToken, + 'Accept': '*/*' + } + }; + + https.get(logOptions, (res) => { + let body = ''; + + res.on('data', (data) => { + body += data; + const currentLogs = body.substring(oldLogs.length, body.length - 1); + + console.log(currentLogs); + oldLogs += currentLogs; + }); + res.on('end', function () { + console.log(body); + this.getLogsStream(functionName); + }); + res.on('error', (responseError) => { + console.log(`Got error: ${responseError.message}`); + }); + }); + } + + getInvocationId (functionName) { + const options = { + 'host': functionAppName + constants.scmDomain, + 'port': 443, + 'path': `${constants.logInvocationsApiPath + functionAppName}-${functionName}/invocations?limit=5`, + 'headers': { + 'Authorization': constants.bearer + principalCredentials.tokenCache._entries[0].accessToken, + 'Content-Type': constants.jsonContentType + } + }; + +return new BbPromise((resolve, reject) => { + https.get(options, (res) => { + let body = ''; + + res.on('data', (data) => { + body += data; + }); + res.on('end', () => { + const parsed = JSON.parse(body); + + invocationId = parsed.entries[0].id; + resolve(res); + }); + res.on('error', (responseError) => { + reject(responseError); + }); + }); + }); + } + + getLogsForInvocationId () { + this.serverless.cli.log(`Logs for InvocationId: ${invocationId}`); + const options = { + 'host': functionAppName + constants.scmDomain, + 'port': 443, + 'path': constants.logOutputApiPath + invocationId, + 'headers': { + 'Authorization': constants.bearer + principalCredentials.tokenCache._entries[0].accessToken, + 'Content-Type': constants.jsonContentType + } + }; + +return new BbPromise((resolve, reject) => { + https.get(options, (res) => { + let body = ''; + + res.on('data', (data) => { + body += data; + }); + res.on('end', () => { + console.log(body); + }); + res.on('error', (e) => { + reject(e); + }); + resolve(res); + }); + }); + } + + invoke (functionName, eventType, eventData) { + let options = {}; + + if (eventType === 'http') { + let queryString = ''; + + if (eventData) { + Object.keys(eventData).forEach((key) => { + const value = eventData[key]; + + queryString = `${key}=${value}`; + }); + } + options = { + 'host': functionAppName + constants.functionAppDomain, + 'port': 443, + 'path': `${constants.functionAppApiPath + functionName}?${queryString}` + }; + +return new BbPromise((resolve, reject) => { + https.get(options, (res) => { + let body = ''; + + res.on('data', (data) => { + body += data; + }); + res.on('end', () => { + console.log(body); + }); + res.on('error', (e) => { + reject(e); + }); + resolve(res); + }); + }); + } + const requestUrl = `https://${functionAppName}${constants.functionsAdminApiPath}${functionName}`; + + options = { + 'host': constants.functionAppDomain, + 'method': 'post', + 'body': eventData, + 'url': requestUrl, + 'json': true, + 'headers': { + 'x-functions-key': functionsAdminKey, + 'Accept': 'application/json,*/*' + } + }; + +return new BbPromise((resolve, reject) => { + request(options, (err, res, body) => { + if (err) { + reject(err); + } + this.serverless.cli.log(`Invoked function at: ${requestUrl}. \nResponse statuscode: ${res.statusCode}`); + resolve(res); + }); + }); + + } + + cleanUpFunctionsBeforeDeploy (serverlessFunctions) { + const deleteFunctionPromises = []; + + deployedFunctionNames.forEach((functionName) => { + if (serverlessFunctions.indexOf(functionName) < 0) { + this.serverless.cli.log(`Deleting function : ${functionName}`); + deleteFunctionPromises.push(this.deleteFunction(functionName)); + } + }); + +return BbPromise.all(deleteFunctionPromises); + } + + deleteFunction (functionName) { + const requestUrl = `https://${functionAppName}${constants.scmVfsPath}${functionName}/?recursive=true`; + const options = { + 'host': functionAppName + constants.scmDomain, + 'method': 'delete', + 'url': requestUrl, + 'headers': { + 'Authorization': constants.bearer + principalCredentials.tokenCache._entries[0].accessToken, + 'Accept': '*/*', + 'Content-Type': constants.jsonContentType + } + }; + +return new BbPromise((resolve, reject) => { + request(options, (err, res, body) => { + if (err) { + reject(err); + } else { + resolve(res); + } + }); + }); + } + + createZipObject (functionName, entryPoint, filePath, params) { + return new BbPromise((resolve, reject) => { + this.serverless.cli.log(`Packaging function: ${functionName}`); + const folderForJSFunction = path.join(functionsFolder, functionName); + const handlerPath = path.join(this.serverless.config.servicePath, filePath); + + if (!fs.existsSync(folderForJSFunction)) { + fs.mkdirSync(folderForJSFunction); + } + fse.copySync(handlerPath, path.join(folderForJSFunction, 'index.js')); + const functionJSON = params.functionsJson; + + functionJSON.entryPoint = entryPoint; + fs.writeFileSync(path.join(folderForJSFunction, 'function.json'), JSON.stringify(functionJSON, null, 4)); + fs.readdirSync(functionsFolder).filter((folder) => { + const folderName = path.basename(folder); + + if (fs.statSync(path.join(functionsFolder, folder)).isDirectory() && functionName == folderName) { + const zip = new JSZip(); + + fs.readdir(path.join(functionsFolder, folder), (err, files) => { + if (err) { + reject(err); + } else { + let filesInFolder = 0; + + for (let i = 0; i < files.length; i++) { + const filepathtobezipped = path.join(functionsFolder, folder, files[i]); + const data = fs.readFileSync(filepathtobezipped); + + filesInFolder++; + zip.folder(path.basename(folder)).folder(path.basename(folder)).file(path.basename(filepathtobezipped), data); + if (filesInFolder === files.length) { + zipArray.push({ + 'key': path.basename(folder), + 'value': zip + }); + resolve(`done folder..${folder}`); + } + } + } + }); + } + }); + }); + } + + createZipFileAndUploadFunction (folder, zip) { + return new BbPromise((resolve, reject) => { + const generateOptions = { + 'type': 'nodebuffer', + 'streamFiles': true + }; + const zipFileName = `${path.basename(folder)}.zip`; + const outputZipPath = path.join(functionsFolder, zipFileName); + + zip.folder(path.basename(folder)).generateNodeStream(generateOptions) + .pipe(fs.createWriteStream(outputZipPath)) + .on('error', (error) => { + reject(error); + }) + .on('finish', () => { + const requestUrl = `https://${functionAppName}${constants.scmZipApiPath}`; + const options = { + 'url': requestUrl, + 'headers': { + 'Authorization': constants.bearer + principalCredentials.tokenCache._entries[0].accessToken, + 'Accept': '*/*' + } + }; + + fs.createReadStream(outputZipPath) + .pipe(request.put(options, (err, res, body) => { + if (err) { + reject(err); + } else { + resolve('ZipFileCreated and uploaded'); + } + fse.removeSync(outputZipPath); + })); + }); + }); + } + + createAndUploadZipFunctions () { + const zipFunctions = []; + + for (let j = 0; j < zipArray.length; j++) { + zipFunctions.push(this.createZipFileAndUploadFunction(zipArray[j].key, zipArray[j].value)); + } + +return BbPromise.all(zipFunctions); + } +} +module.exports = AzureProvider; diff --git a/remove/azureRemove.js b/remove/azureRemove.js new file mode 100644 index 00000000..cbbc36d0 --- /dev/null +++ b/remove/azureRemove.js @@ -0,0 +1,28 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const deleteResourceGroup = require('./lib/deleteResourceGroup'); +const loginToAzure = require('../shared/loginToAzure'); + +class AzureRemove { + constructor (serverless, options) { + this.serverless = serverless; + this.options = options; + this.provider = this.serverless.getProvider('azure'); + + Object.assign( + this, + loginToAzure, + deleteResourceGroup + ); + + this.hooks = { + 'remove:remove': () => BbPromise.bind(this) + .then(this.loginToAzure) + .then(this.deleteResourceGroup) + .then(() => this.serverless.cli.log('Service successfully removed')) + }; + } +} + +module.exports = AzureRemove; diff --git a/remove/lib/deleteResourceGroup.js b/remove/lib/deleteResourceGroup.js new file mode 100644 index 00000000..7748a113 --- /dev/null +++ b/remove/lib/deleteResourceGroup.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = { + deleteResourceGroup () { + return this.provider.LoginWithServicePrincipal() + .then(() => this.provider.DeleteDeployment()) + .then(() => this.provider.DeleteResourceGroup()); + } +}; diff --git a/shared/bindings.json b/shared/bindings.json new file mode 100644 index 00000000..57b78476 --- /dev/null +++ b/shared/bindings.json @@ -0,0 +1,1741 @@ +{ + "$schema": "<TBD>", + "contentVersion": "2016-03-04-alpha", + "variables": { + "storageConnStringLabel": "$variables_storageConnStringLabel", + "appSettingsHelp": "$variables_appSettingsHelp", + "selectConnection": "$variables_selectConnection", + "parameterName": "$variables_parameterName", + "paramNameLabel": "$variables_paramNameLabel", + "paramNameInputHelp": "$variables_paramNameInputHelp", + "paramNameOutputHelp": "$variables_paramNameOutputHelp", + "apiHubTableDataSetLabel": "$variables_apiHubTableDataSetLabel", + "apiHubTableDataSetHelp": "$variables_apiHubTableDataSetHelp", + "apiHubTableNameLabel": "$variables_apiHubTableNameLabel", + "apiHubTableHelp": "$variables_apiHubTableHelp", + "apiHubTableEntityLabel": "$variables_apiHubTableEntityLabel", + "apiHubTableEntityHelp": "$variables_apiHubTableEntityHelp", + "apiHubTableConnectionLabel": "$variables_apiHubTableConnectionLabel", + "apiHubTableConnectionHelp": "$variables_apiHubTableConnectionHelp" + }, + "bindings": [ + { + "type": "timerTrigger", + "displayName": "$timerTrigger_displayName", + "direction": "trigger", + "enabledInTryMode": true, + "documentation": "$content=Documentation\\timerTrigger.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "myTimer", + "required": true, + "label": "$timerTrigger_name_label", + "help": "$timerTrigger_name_help", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "schedule", + "value": "string", + "defaultValue": "0 * * * * *", + "required": true, + "label": "$timerTrigger_schedule_label", + "help": "$timerTrigger_schedule_help", + "validators": [ + { + "expression": "^(\\*|((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?)(,((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?)(,((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|(((1\\d)|(2[0-3])|\\d)(\\-((1\\d)|(2[0-3])|\\d)(\\/\\d+)?)?)(,(((1\\d)|(2[0-3])|\\d)(\\-((1\\d)|(2[0-3])|\\d)(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|((([1-2]\\d)|(3[0-1])|[1-9])(\\-(([1-2]\\d)|(3[0-1])|[1-9])(\\/\\d+)?)?)(,((([1-2]\\d)|(3[0-1])|[1-9])(\\-(([1-2]\\d)|(3[0-1])|[1-9])(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|(([A-Za-z]+|(1[0-2])|[1-9])(\\-([A-Za-z]+|(1[0-2])|[1-9])(\\/\\d+)?)?)(,(([A-Za-z]+|(1[0-2])|[1-9])(\\-([A-Za-z]+|(1[0-2])|[1-9])(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|(([A-Za-z]+|[0-6])(\\-([A-Za-z]+|[0-6])(\\/\\d+)?)?)(,(([A-Za-z]+|[0-6])(\\-([A-Za-z]+|[0-6])(\\/\\d+)?)?))*)(\\/\\d+)?$", + "errorText": "$timerTrigger_schedule_errorText" + } + ] + } + ] + }, + { + "type": "eventHubTrigger", + "displayName": "$eventHubTrigger_displayName", + "direction": "trigger", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\eventHubTrigger.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "myEventHubMessage", + "required": true, + "label": "$eventHubTrigger_name_label", + "help": "$eventHubTrigger_name_help", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "path", + "value": "string", + "defaultValue": "myeventhub", + "required": true, + "label": "$eventHubOut_path_label", + "help": "$eventHubTrigger_path_help", + "validators": [ + { + "expression": "^[a-z0-9]$|^[a-z0-9][a-z0-9-_.]{0,48}[a-z0-9]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$eventHubTrigger_path_errorText" + } + ] + }, + { + "name": "consumerGroup", + "value": "string", + "defaultValue": "$Default", + "required": false, + "label": "$eventHubTrigger_consumerGroup_label", + "help": "$eventHubTrigger_consumerGroup_help", + "validators": [ + { + "expression": "(^[a-z0-9]$|^[a-z0-9][a-z0-9-_.]{0,48}[a-z0-9]$)|^\\$Default$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$eventHubTrigger_consumerGroup_errorText" + } + ] + }, + { + "name": "connection", + "value": "string", + "resource": "EventHub", + "required": true, + "label": "$eventHubTrigger_connection_label", + "help": "$eventHubTrigger_connection_help", + "placeholder": "[variables('selectConnection')]" + } + ] + }, + { + "type": "eventHub", + "displayName": "$eventHubOut_displayName", + "direction": "out", + "enabledInTryMode": false, + "actions": [ + { + "template": "EventHubTrigger", + "binding": "eventHubTrigger", + "settings": [ + "path", + "connection" + ] + } + ], + "documentation": "$content=Documentation\\eventHubOut.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "outputEventHubMessage", + "required": true, + "label": "$eventHubOut_name_label", + "help": "$eventHubOut_name_help", + "validators": [ + { + "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "path", + "value": "string", + "defaultValue": "outeventhub", + "required": true, + "label": "$eventHubOut_path_label", + "help": "$eventHubOut_path_help", + "validators": [ + { + "expression": "^[a-z0-9]$|^[a-z0-9][a-z0-9-_.]{0,48}[a-z0-9]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$eventHubOut_path_errorText" + } + ] + }, + { + "name": "connection", + "value": "string", + "resource": "EventHub", + "required": true, + "label": "$eventHubOut_connection_label", + "help": "$eventHubOut_connection_help", + "placeholder": "[variables('selectConnection')]" + } + ] + }, + { + "type": "queue", + "displayName": "$queueOut_displayName", + "direction": "out", + "enabledInTryMode": true, + "actions": [ + { + "template": "QueueTrigger", + "binding": "queueTrigger", + "settings": [ + "queueName", + "connection" + ] + } + ], + "documentation": "$content=Documentation\\queueOut.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "outputQueueItem", + "required": true, + "label": "$queueOut_name_label", + "help": "$queueOut_name_help", + "validators": [ + { + "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "queueName", + "value": "string", + "defaultValue": "outqueue", + "required": true, + "label": "$queueOut_queueName_label", + "help": "$queueOut_queueName_help", + "validators": [ + { + "expression": "^[0-9a-z][a-z0-9-]{1,61}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$queueOut_queueName_errorText" + } + ] + }, + { + "name": "connection", + "value": "string", + "resource": "Storage", + "required": true, + "label": "[variables('storageConnStringLabel')]", + "help": "[variables('appSettingsHelp')]", + "placeholder": "[variables('selectConnection')]" + } + ] + }, + { + "type": "queueTrigger", + "displayName": "$queueTrigger_displayName", + "direction": "trigger", + "enabledInTryMode": true, + "documentation": "$content=Documentation\\queueTrigger.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "myQueueItem", + "required": true, + "label": "$queueTrigger_name_label", + "help": "$queueTrigger_name_help", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "queueName", + "value": "string", + "defaultValue": "myqueue", + "required": true, + "label": "$queueTrigger_queueName_label", + "help": "$queueTrigger_queueName_help", + "validators": [ + { + "expression": "^[0-9a-z][a-z0-9-]{1,61}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$queueTrigger_queueName_errorText" + } + ] + }, + { + "name": "connection", + "value": "string", + "resource": "Storage", + "required": true, + "label": "[variables('storageConnStringLabel')]", + "help": "[variables('appSettingsHelp')]", + "placeholder": "[variables('selectConnection')]" + } + ] + }, + { + "type": "blob", + "displayName": "$blobOut_displayName", + "direction": "out", + "enabledInTryMode": true, + "actions": [ + { + "template": "BlobTrigger", + "binding": "blobTrigger", + "settings": [ + "path", + "connection" + ] + } + ], + "documentation": "$content=Documentation\\blobOut.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "outputBlob", + "required": true, + "label": "$blobOut_name_label", + "help": "$blobOut_name_help", + "validators": [ + { + "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "path", + "value": "string", + "defaultValue": "outcontainer/{rand-guid}", + "required": true, + "label": "$blobOut_path_label", + "help": "$blobOut_path_help", + "validators": [ + { + "expression": "^[a-z0-9{](?:[a-z0-9{}]|(?:\\-(?!\\-))){1,61}[a-z0-9{}][\\/](\\S){0,1023}[^\\/]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$blobOut_path_errorText" + } + ] + }, + { + "name": "connection", + "value": "string", + "resource": "Storage", + "required": true, + "label": "[variables('storageConnStringLabel')]", + "help": "[variables('appSettingsHelp')]", + "placeholder": "[variables('selectConnection')]" + } + ] + }, + { + "type": "blob", + "displayName": "$blobIn_displayName", + "direction": "in", + "enabledInTryMode": true, + "documentation": "$content=Documentation\\blobIn.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "inputBlob", + "required": true, + "label": "$blobIn_name_label", + "help": "$blobIn_name_help", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "path", + "value": "string", + "defaultValue": "incontainer/{name}", + "required": true, + "label": "$blobIn_path_label", + "help": "$blobIn_path_help", + "validators": [ + { + "expression": "^[a-z0-9{](?:[a-z0-9{}]|(?:\\-(?!\\-))){1,61}[a-z0-9{}][\\/](\\S){0,1023}[^\\/]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$blobIn_patherrorText" + } + ] + }, + { + "name": "connection", + "value": "string", + "resource": "Storage", + "required": true, + "label": "[variables('storageConnStringLabel')]", + "help": "[variables('appSettingsHelp')]", + "placeholder": "[variables('selectConnection')]" + } + ] + }, + { + "type": "blobTrigger", + "displayName": "$blobTrigger_displayName", + "direction": "trigger", + "enabledInTryMode": true, + "documentation": "$content=Documentation\\blobTrigger.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "myBlob", + "required": true, + "label": "$blobTrigger_name_label", + "help": "$blobTrigger_name_help", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "path", + "value": "string", + "defaultValue": "mycontainer", + "required": true, + "label": "$blobTrigger_path_label", + "help": "$blobTrigger_path_help", + "validators": [ + { + "expression": "(^[a-z0-9{](?:[a-z0-9{}]|(?:\\-(?!\\-))){1,61}[a-z0-9{}]$)|(^[a-z0-9{](?:[a-z0-9{}]|(?:\\-(?!\\-))){1,61}[a-z0-9{}][\\/](\\S){0,1023}[^\\/]$)|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$blobTrigger_path_errorText" + } + ] + }, + { + "name": "connection", + "value": "string", + "resource": "Storage", + "required": true, + "label": "[variables('storageConnStringLabel')]", + "help": "[variables('appSettingsHelp')]", + "placeholder": "[variables('selectConnection')]" + } + ] + }, + { + "type": "apiHubFile", + "displayName": "$apiHubFileIn_displayName", + "direction": "in", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\apiHubFileIn.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "inputFile", + "required": true, + "label": "$apiHubFileIn_name_label", + "help": "$apiHubFileIn_name_help", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "path", + "value": "string", + "defaultValue": "path/{file}", + "required": true, + "label": "$apiHubFileIn_path_label", + "help": "$apiHubFileIn_path_help", + "validators": [] + }, + { + "name": "connection", + "value": "string", + "resource": "ApiHub", + "required": true, + "label": "$apiHubFileIn_connection_label", + "help": "$apiHubFileIn_connection_help", + "metadata": { + "capability": "blob" + } + } + ] + }, + { + "type": "apiHubFile", + "displayName": "$apiHubFileIn_displayName", + "direction": "out", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\apiHubFileOut.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "outputFile", + "required": true, + "label": "$apiHubFileIn_name_label", + "help": "$apiHubFileIn_name_help", + "validators": [ + { + "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "path", + "value": "string", + "defaultValue": "path/{file}", + "required": true, + "label": "$apiHubFileIn_path_label", + "help": "$apiHubFileIn_path_help", + "validators": [] + }, + { + "name": "connection", + "value": "string", + "resource": "ApiHub", + "required": true, + "label": "$apiHubFileIn_connection_label", + "help": "$apiHubFileIn_connection_help", + "metadata": { + "capability": "blob" + } + } + ] + }, + { + "type": "apiHubFileTrigger", + "displayName": "$apiHubFileTrigger_displayName", + "direction": "trigger", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\apiHubFileTrigger.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "inputFile", + "required": true, + "label": "$apiHubFileTrigger_name_label", + "help": "$apiHubFileTrigger_name_help", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "path", + "value": "string", + "defaultValue": "path/{file}", + "required": true, + "label": "$apiHubFileTrigger_path_label", + "help": "$apiHubFileTrigger_path_help", + "validators": [] + }, + { + "name": "connection", + "value": "string", + "resource": "ApiHub", + "required": true, + "label": "$apiHubFileTrigger_connection_label", + "help": "$apiHubFileTrigger_connection_help", + "metadata": { + "capability": "blob", + "excluded": [ + "googledrive", + "azureblob" + ] + } + } + ] + }, + { + "type": "apiHubTable", + "displayName": "$apiHubTableIn_displayName", + "direction": "in", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\apiHubTable.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "inputTable", + "required": true, + "label": "[variables('paramNameLabel')]", + "help": "[variables('paramNameInputHelp')]", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "dataSetName", + "value": "string", + "defaultValue": "default", + "required": false, + "label": "[variables('apiHubTableDataSetLabel')]", + "help": "[variables('apiHubTableDataSetHelp')]", + "validators": [] + }, + { + "name": "tableName", + "value": "string", + "defaultValue": null, + "required": false, + "label": "[variables('apiHubTableNameLabel')]", + "help": "[variables('apiHubTableHelp')]", + "validators": [] + }, + { + "name": "entityId", + "value": "string", + "defaultValue": null, + "required": false, + "label": "[variables('apiHubTableEntityLabel')]", + "help": "[variables('apiHubTableEntityHelp')]", + "validators": [] + }, + { + "name": "connection", + "value": "string", + "resource": "ApiHub", + "required": true, + "label": "[variables('apiHubTableConnectionLabel')]", + "help": "[variables('apiHubTableConnectionHelp')]", + "metadata": { + "capability": "tabular" + } + } + ] + }, + { + "type": "apiHubTable", + "displayName": "$apiHubTableOut_displayName", + "direction": "out", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\apiHubTableOut.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "outputTable", + "required": true, + "label": "[variables('paramNameLabel')]", + "help": "[variables('paramNameOutputHelp')]", + "validators": [ + { + "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "dataSetName", + "value": "string", + "defaultValue": "default", + "required": false, + "label": "[variables('apiHubTableDataSetLabel')]", + "help": "[variables('apiHubTableDataSetHelp')]", + "validators": [] + }, + { + "name": "tableName", + "value": "string", + "defaultValue": null, + "required": false, + "label": "[variables('apiHubTableNameLabel')]", + "help": "[variables('apiHubTableHelp')]", + "validators": [] + }, + { + "name": "entityId", + "value": "string", + "defaultValue": null, + "required": false, + "label": "[variables('apiHubTableEntityLabel')]", + "help": "[variables('apiHubTableEntityHelp')]", + "validators": [] + }, + { + "name": "connection", + "value": "string", + "resource": "ApiHub", + "required": true, + "label": "[variables('apiHubTableConnectionLabel')]", + "help": "[variables('apiHubTableConnectionHelp')]", + "metadata": { + "capability": "tabular" + } + } + ] + }, + { + "type": "httpTrigger", + "displayName": "$httpTrigger_displayName", + "direction": "trigger", + "enabledInTryMode": true, + "documentation": "$content=Documentation\\httpTrigger.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "req", + "required": true, + "label": "$httpTrigger_name_label", + "help": "$httpTrigger_name_help", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "route", + "value": "string", + "required": false, + "label": "$httpTrigger_route_label", + "help": "$httpTrigger_route_help", + "validators": [] + }, + { + "name": "webHookType", + "value": "enum", + "enum": [ + { + "value": "github", + "display": "GitHub" + }, + { + "value": "genericJson", + "display": "Generic JSON" + }, + { + "value":"slack", + "display":"Slack" + } + ], + "label": "$httpTrigger_webHookType_label", + "help": "$httpTrigger_webHookType_help" + }, + { + "name": "authLevel", + "value": "enum", + "enum": [ + { + "value": "function", + "display": "Function" + }, + { + "value": "anonymous", + "display": "Anonymous" + }, + { + "value": "admin", + "display": "Admin" + } + ], + "label": "$httpTrigger_authLevel_label", + "help": "$httpTrigger_authLevel_help" + }, + { + "name": "methods", + "value": "checkBoxList", + "defaultValue": [ + "get", + "post", + "delete", + "head", + "patch", + "put", + "options", + "trace" + ], + "enum": [ + { + "value": "get", + "display": "GET" + }, + { + "value": "post", + "display": "POST" + }, + { + "value": "delete", + "display": "DELETE" + }, + { + "value": "head", + "display": "HEAD" + }, + { + "value": "patch", + "display": "PATCH" + }, + { + "value": "put", + "display": "PUT" + }, + { + "value": "options", + "display": "OPTIONS" + }, + { + "value": "trace", + "display": "TRACE" + } + ], + "label": "$httpTrigger_methods_label", + "help": "$httpTrigger_methods_help" + } + ], + "rules": [ + { + "name": "mode", + "type": "exclusivity", + "values": [ + { + "value": "authLevel", + "display": "Standard", + "hiddenSettings": [ + "webHookType" + ], + "shownSettings": [ + "authLevel" + ] + }, + { + "value": "webHookType", + "display": "Webhook", + "hiddenSettings": [ + "authLevel" + ], + "shownSettings": [ + "webHookType" + ] + } + ], + "label": "$httpTrigger_mode_label", + "help": "$httpTrigger_mode_help" + }, + { + "name": "methodRule", + "type": "exclusivity", + "values": [ + { + "value": "allMethods", + "display": "All methods", + "hiddenSettings": [ + "methods" + ], + "shownSettings": [] + }, + { + "value": "methods", + "display": "Selected methods", + "hiddenSettings": [], + "shownSettings": [ + "methods" + ] + } + ], + "label": "$httpTrigger_methodRule_label", + "help": "$httpTrigger_methodRule_help" + } + ] + }, + { + "type": "http", + "displayName": "$httpOut_displayName", + "direction": "out", + "enabledInTryMode": true, + "documentation": "$content=Documentation\\httpOut.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "res", + "required": true, + "label": "$httpOut_name_label", + "help": "$httpOut_name_help", + "validators": [ + { + "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$", + "errorText": "[variables('parameterName')]" + } + ] + } + ] + }, + { + "type": "serviceBusTrigger", + "displayName": "$serviceBusTrigger_displayName", + "direction": "trigger", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\serviceBusTrigger.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "mySbMsg", + "required": true, + "label": "$serviceBusTrigger_name_label", + "help": "$serviceBusTrigger_name_help", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "queueName", + "value": "string", + "defaultValue": "mysbqueue", + "required": true, + "label": "$serviceBusTrigger_queueName_label", + "help": "$serviceBusTrigger_queueName_help", + "validators": [ + { + "expression": "^[0-9a-z][a-z0-9_.-]{1,48}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$serviceBusTrigger_queueName_errorText" + } + ] + }, + { + "name": "topicName", + "value": "string", + "defaultValue": "mysbtopic", + "required": true, + "label": "$serviceBusTrigger_topicName_label", + "help": "$serviceBusTrigger_topicName_help", + "validators": [ + { + "expression": "^[0-9a-z][a-z0-9_.-]{1,48}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$serviceBusTrigger_topicName_errorText" + } + ] + }, + { + "name": "subscriptionName", + "value": "string", + "defaultValue": "mysubscription", + "required": true, + "label": "$serviceBusTrigger_subscriptionName_label", + "help": "$serviceBusTrigger_subscriptionName_help", + "validators": [ + { + "expression": "^[0-9a-zA-Z][a-zA-Z0-9_.-]{1,48}[0-9a-zA-Z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$serviceBusTrigger_subscriptionName_errorText" + } + ] + }, + { + "name": "connection", + "value": "string", + "resource": "EventHub", + "required": true, + "label": "$serviceBusTrigger_connection_label", + "help": "$serviceBusTrigger_connection_help", + "placeholder": "[variables('selectConnection')]" + }, + { + "name": "accessRights", + "value": "enum", + "enum": [ + { + "value": "Manage", + "display": "Manage" + }, + { + "value": "Listen", + "display": "Listen" + } + ], + "label": "$serviceBusTrigger_accessRights_label", + "help": "$serviceBusTrigger_accessRights_help" + } + ], + "rules": [ + { + "name": "messageType", + "type": "exclusivity", + "values": [ + { + "value": "queueName", + "display": "$serviceBusTrigger_messageType_queueName", + "hiddenSettings": [ + "topicName", + "subscriptionName" + ], + "shownSettings": [ + "queueName" + ] + }, + { + "value": "topicName", + "display": "$serviceBusTrigger_messageType_topicName", + "hiddenSettings": [ + "queueName" + ], + "shownSettings": [ + "topicName", + "subscriptionName" + ] + } + ], + "label": "$serviceBusTrigger_messageType_label", + "help": "$serviceBusTrigger_messageType_help" + } + ] + }, + { + "type": "serviceBus", + "displayName": "$serviceBusOut_displayName", + "direction": "out", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\serviceBusOut.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "outputSbMsg", + "required": true, + "label": "$serviceBusOut_name_label", + "help": "$serviceBusOut_name_help", + "validators": [ + { + "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "queueName", + "value": "string", + "defaultValue": "outqueue", + "required": true, + "label": "$serviceBusOut_queueName_label", + "help": "$serviceBusOut_queueName_help", + "validators": [ + { + "expression": "^[0-9a-z][a-z0-9_.-]{1,48}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$serviceBusOut_queueName_errorText" + } + ] + }, + { + "name": "topicName", + "value": "string", + "defaultValue": "outtopic", + "required": true, + "label": "$serviceBusOut_topicName_label", + "help": "$serviceBusOut_topicName_help", + "validators": [ + { + "expression": "^[0-9a-z][a-z0-9_.-]{1,48}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$serviceBusOut_topicName_errorText" + } + ] + }, + { + "name": "connection", + "value": "string", + "resource": "EventHub", + "required": true, + "label": "$serviceBusOut_connection_label", + "help": "$serviceBusOut_connection_help", + "placeholder": "[variables('selectConnection')]" + }, + { + "name": "accessRights_", + "value": "enum", + "enum": [ + { + "value": "Manage", + "display": "Manage" + }, + { + "value": "Send", + "display": "Send" + } + ], + "label": "$serviceBusOut_accessRights_label", + "help": "$serviceBusOut_accessRights_help" + } + ], + "rules": [ + { + "name": "messageType", + "type": "exclusivity", + "values": [ + { + "value": "queueName", + "display": "$serviceBusOut_messageType_queueName", + "hiddenSettings": [ + "topicName" + ], + "shownSettings": [ + "queueName" + ] + }, + { + "value": "topicName", + "display": "$serviceBusOut_messageType_topicName", + "hiddenSettings": [ + "queueName" + ], + "shownSettings": [ + "topicName" + ] + } + ], + "label": "$serviceBusOut_messageType_label", + "help": "$serviceBusOut_messageType_help" + } + ] + }, + { + "type": "manualTrigger", + "displayName": "Manual", + "direction": "trigger", + "enabledInTryMode": true, + "documentation": "$content=Documentation\\manualTrigger.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "input", + "required": true, + "label": "$manualTrigger_name_label", + "help": "$manualTrigger_name_help", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + } + ] + }, + { + "type": "table", + "displayName": "$tableout_displayName", + "direction": "out", + "enabledInTryMode": true, + "documentation": "$content=Documentation\\tableOut.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "outputTable", + "required": true, + "label": "$tableout_name_label", + "help": "$tableout_name_help", + "validators": [ + { + "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "tableName", + "value": "string", + "defaultValue": "outTable", + "required": true, + "label": "$table_tableName_label", + "help": "$table_tableName_help", + "validators": [ + { + "expression": "^[A-Za-z][A-Za-z0-9]{2,62}$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$table_tableName_errorText" + } + ] + }, + { + "name": "connection", + "value": "string", + "resource": "Storage", + "required": true, + "label": "[variables('storageConnStringLabel')]", + "help": "[variables('appSettingsHelp')]", + "placeholder": "[variables('selectConnection')]" + } + ] + }, + { + "type": "table", + "displayName": "$tableIn_displayName", + "direction": "in", + "enabledInTryMode": true, + "documentation": "$content=Documentation\\tableIn.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "inputTable", + "required": true, + "label": "$tableIn_name_label", + "help": "$tableIn_name_help", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "tableName", + "value": "string", + "defaultValue": "inTable", + "required": true, + "label": "$tableIn_tableName_label", + "help": "$tableIn_tableName_help", + "validators": [ + { + "expression": "^[A-Za-z][A-Za-z0-9]{2,62}$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$", + "errorText": "$tableIn_tableName_errorText" + } + ] + }, + { + "name": "partitionKey", + "value": "string", + "required": false, + "label": "$tableIn_partitionKey_label", + "help": "$tableIn_partitionKey_help" + }, + { + "name": "rowKey", + "value": "string", + "required": false, + "label": "$tableIn_rowKey_label", + "help": "$tableIn_rowKey_help" + }, + { + "name": "take", + "value": "int", + "defaultValue": 50, + "required": false, + "label": "$tableIn_take_label", + "help": "$tableIn_take_help" + }, + { + "name": "filter", + "value": "string", + "required": false, + "label": "$tableIn_filter_label", + "help": "$tableIn_filter_help" + }, + { + "name": "connection", + "value": "string", + "resource": "Storage", + "required": true, + "label": "[variables('storageConnStringLabel')]", + "help": "[variables('appSettingsHelp')]", + "placeholder": "[variables('selectConnection')]" + } + ] + }, + { + "type": "documentDB", + "displayName": "$documentDB_displayName", + "direction": "out", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\documentDBOut.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "outputDocument", + "required": true, + "label": "$documentDBOut_name_label", + "help": "$documentDBOut_name_help", + "validators": [ + { + "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "databaseName", + "value": "string", + "defaultValue": "outDatabase", + "required": true, + "label": "$documentDBOut_databaseName_label", + "help": "$documentDBOut_databaseName_help" + }, + { + "name": "collectionName", + "value": "string", + "defaultValue": "MyCollection", + "required": true, + "label": "$documentDBOut_collectionName_label", + "help": "$documentDBOut_collectionName_help" + }, + { + "name": "createIfNotExists", + "value": "boolean", + "defaultValue": false, + "required": true, + "label": "$documentDBOut_createIfNotExists_label", + "help": "$documentDBOut_createIfNotExists_help" + }, + { + "name": "connection", + "value": "string", + "resource": "DocumentDB", + "required": true, + "label": "$documentDBOut_connection_label", + "help": "$documentDBOut_connection_help", + "placeholder": "[variables('selectConnection')]" + } + ] + }, + { + "type": "documentDB", + "displayName": "$documentDBIn_displayName", + "direction": "in", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\documentDBIn.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "inputDocument", + "required": true, + "label": "$documentDBIn_name_label", + "help": "$documentDBIn_name_help", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "databaseName", + "value": "string", + "defaultValue": "inDatabase", + "required": true, + "label": "$documentDBIn_databaseName_label", + "help": "$documentDBIn_databaseName_help" + }, + { + "name": "collectionName", + "value": "string", + "defaultValue": "MyCollection", + "required": true, + "label": "$documentDBIn_collectionName_label", + "help": "$documentDBIn_collectionName_help" + }, + { + "name": "id", + "value": "string", + "defaultValue": "{documentId}", + "required": true, + "label": "$documentDBIn_id_label", + "help": "$documentDBIn_id_help" + }, + { + "name": "connection", + "value": "string", + "resource": "DocumentDB", + "required": true, + "label": "$documentDBIn_connection_label", + "help": "$documentDBIn_connection_help", + "placeholder": "[variables('selectConnection')]" + } + ] + }, + { + "type": "mobileTable", + "displayName": "$mobileTableOut_displayName", + "direction": "out", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\mobileTableOut.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "outputRecord", + "required": true, + "label": "$mobileTableOut_name_label", + "help": "$mobileTableOut_name_help", + "validators": [ + { + "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "tableName", + "value": "string", + "defaultValue": "outTable", + "required": true, + "label": "$mobileTableOut_tableName_label", + "help": "$mobileTableOut_tableName_help" + }, + { + "name": "connection", + "value": "string", + "defaultValue": "My_MobileApp_Uri", + "required": true, + "label": "$mobileTableOut_connection_label", + "help": "$mobileTableOut_connection_help" + }, + { + "name": "apiKey", + "value": "string", + "required": false, + "label": "$mobileTableOut_apiKey_label", + "help": "$mobileTableOut_apiKey_help" + } + ] + }, + { + "type": "mobileTable", + "displayName": "$mobileTableIn_displayName", + "direction": "in", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\mobileTableIn.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "inputRecord", + "required": true, + "label": "$mobileTableIn_name_label", + "help": "$mobileTableIn_name_help", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "tableName", + "value": "string", + "defaultValue": "inTable", + "required": true, + "label": "$mobileTableIn_tableName_label", + "help": "$mobileTableIn_tableName_help" + }, + { + "name": "id", + "value": "string", + "defaultValue": "{itemId}", + "required": true, + "label": "$mobileTableIn_id_label", + "help": "$mobileTableIn_id_help" + }, + { + "name": "connection", + "value": "string", + "defaultValue": "My_MobileApp_Uri", + "required": true, + "label": "$mobileTableIn_connection_label", + "help": "$mobileTableIn_connection_help" + }, + { + "name": "apiKey", + "value": "string", + "required": false, + "label": "$mobileTableIn_apiKey_label", + "help": "$mobileTableIn_apiKey_help" + } + ] + }, + { + "type": "notificationHub", + "displayName": "$notificationHubOut_displayName", + "direction": "out", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\notificationHubOut.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "notification", + "required": true, + "label": "$notificationHubOut_name_label", + "help": "$notificationHubOut_name_help", + "validators": [ + { + "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "hubName", + "value": "string", + "required": true, + "label": "$notificationHubOut_hubName_label", + "help": "$notificationHubOut_hubName_help" + }, + { + "name": "connection", + "value": "string", + "resource": "ServiceBus", + "required": true, + "label": "$notificationHubOut_connection_label", + "help": "$notificationHubOut_connection_help", + "placeholder": "[variables('selectConnection')]" + }, + { + "name": "tagExpression", + "value": "string", + "required": false, + "label": "$notificationHubOut_tagExpression_label", + "help": "$notificationHubOut_tagExpression_help" + }, + { + "name": "enableTestSend", + "value": "boolean", + "required": false, + "label": "$notificationHubOut_enableTestSend_label", + "help": "$notificationHubOut_enableTestSend_help" + }, + { + "name": "platform", + "value": "enum", + "enum": [ + { + "value": "", + "display": "Template" + }, + { + "value": "apns", + "display": "Apple (APNS)" + }, + { + "value": "adm", + "display": "Amazon (ADM)" + }, + { + "value": "gcm", + "display": "Google (GCM)" + }, + { + "value": "wns", + "display": "Windows (WNS)" + }, + { + "value": "mpns", + "display": "Windows Phone (MPNS)" + } + ], + "label": "$notificationHubOut_platform_label", + "help": "$notificationHubOut_platform_help" + } + ] + }, + { + "type": "sendGrid", + "displayName": "$sendGrid_displayName", + "direction": "out", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\sendGridOut.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "message", + "required": true, + "label": "$sendGrid_name_label", + "help": "$sendGrid_name_help", + "validators": [ + { + "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "apiKey", + "value": "string", + "defaultValue": "SendGridApiKey", + "required": true, + "label": "$sendGrid_apiKey_label", + "help": "$sendGrid_apiKey_help" + }, + { + "name": "to", + "value": "string", + "defaultValue": "", + "required": false, + "label": "$sendGrid_to_label", + "help": "$sendGrid_to_help" + }, + { + "name": "from", + "value": "string", + "defaultValue": "", + "required": false, + "label": "$sendGrid_from_label", + "help": "$sendGrid_from_help" + }, + { + "name": "subject", + "value": "string", + "defaultValue": "", + "required": false, + "label": "$sendGrid_subject_label", + "help": "$sendGrid_subject_help" + }, + { + "name": "text", + "value": "string", + "defaultValue": "", + "required": false, + "label": "$sendGrid_text_label", + "help": "$sendGrid_text_help" + } + ] + }, + { + "type": "twilioSms", + "displayName": "$twilioSms_displayName", + "direction": "out", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\twilioSmsOut.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "message", + "required": true, + "label": "$twilioSms_name_label", + "help": "$twilioSms_name_help", + "validators": [ + { + "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "accountSid", + "value": "string", + "defaultValue": "TwilioAccountSid", + "required": true, + "label": "$twilioSms_accountsid_label", + "help": "$twilioSms_accountsid_help" + }, + { + "name": "authToken", + "value": "string", + "defaultValue": "TwilioAuthToken", + "required": true, + "label": "$twilioSms_authtoken_label", + "help": "$twilioSms_authtoken_help" + }, + { + "name": "to", + "value": "string", + "defaultValue": "", + "required": false, + "label": "$twilioSms_to_label", + "help": "$twilioSms_to_help" + }, + { + "name": "from", + "value": "string", + "defaultValue": "", + "required": false, + "label": "$twilioSms_from_label", + "help": "$twilioSms_from_help" + }, + { + "name": "body", + "value": "string", + "defaultValue": "", + "required": false, + "label": "$twilioSms_body_label", + "help": "$twilioSms_body_help" + } + ] + }, + { + "type": "bot", + "displayName": "$bot_out_displayName", + "direction": "out", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\botOut.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "$return", + "required": true, + "label": "$bot_out_name_label", + "help": "$bot_out_name_help", + "validators": [ + { + "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "botId", + "value": "string", + "required": true, + "label": "$bot_botId_label", + "help": "$bot_botId_help" + }, + { + "name": "secret", + "value": "string", + "required": false, + "label": "$bot_out_secret_label", + "help": "$bot_out_secret_help" + } + ] + }, + { + "type": "bot", + "displayName": "$bot_in_displayName", + "direction": "in", + "enabledInTryMode": false, + "documentation": "$content=Documentation\\botIn.md", + "settings": [ + { + "name": "name", + "value": "string", + "defaultValue": "bot", + "required": true, + "label": "$bot_in_name_label", + "help": "$bot_in_name_help", + "validators": [ + { + "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$", + "errorText": "[variables('parameterName')]" + } + ] + }, + { + "name": "secret", + "value": "string", + "required": false, + "label": "$bot_in_secret_label", + "help": "$bot_in_secret_help" + } + ] + } + ] +} \ No newline at end of file diff --git a/shared/getAdminKey.js b/shared/getAdminKey.js new file mode 100644 index 00000000..74ca01d0 --- /dev/null +++ b/shared/getAdminKey.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = { + getAdminKey () { + return this.provider.getAdminKey(); + } +}; diff --git a/shared/loginToAzure.js b/shared/loginToAzure.js new file mode 100644 index 00000000..c23cccc9 --- /dev/null +++ b/shared/loginToAzure.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = { + loginToAzure () { + this.serverless.cli.log('Logging in to Azure'); + +return this.provider.LoginWithServicePrincipal(); + } +}; diff --git a/shared/parseBindings.js b/shared/parseBindings.js new file mode 100644 index 00000000..f2e35337 --- /dev/null +++ b/shared/parseBindings.js @@ -0,0 +1,43 @@ +'use strict'; + +const path = require('path'); + +const bindingsJson = require(path.join(__dirname, 'bindings.json')); + +const constants = { + 'bindings': 'bindings', + 'settings': 'settings', + 'name': 'name', + 'displayName': 'displayName', + 'type': 'type' +}; + +module.exports = { + 'getBindingsMetaData': function (serverless) { + serverless.cli.log('Parsing Azure Functions Bindings.json...'); + const bindingDisplayNames = []; + const bindingTypes = []; + const bindingSettings = []; + const bindingSettingsNames = []; + + for (let bindingsIndex = 0; bindingsIndex < bindingsJson[constants.bindings].length; bindingsIndex++) { + const settingsNames = []; + + bindingTypes.push(bindingsJson[constants.bindings][bindingsIndex][constants.type]); + bindingDisplayNames.push(bindingsJson[constants.bindings][bindingsIndex][constants.displayName].toLowerCase()); + bindingSettings[bindingsIndex] = bindingsJson[constants.bindings][bindingsIndex][constants.settings]; + for (let bindingSettingsIndex = 0; bindingSettingsIndex < bindingSettings[bindingsIndex].length; bindingSettingsIndex++) { + settingsNames.push(bindingSettings[bindingsIndex][bindingSettingsIndex][constants.name]); + } + bindingSettingsNames[bindingsIndex] = settingsNames; + } + const parsedBindings = { + 'bindingDisplayNames': bindingDisplayNames, + 'bindingTypes': bindingTypes, + 'bindingSettings': bindingSettings, + 'bindingSettingsNames': bindingSettingsNames + }; + +return parsedBindings; + } +}; diff --git a/shared/utils.js b/shared/utils.js new file mode 100644 index 00000000..a87cc3ca --- /dev/null +++ b/shared/utils.js @@ -0,0 +1,204 @@ +'use strict'; + +const constants = { + 'type': 'type', + 'direction': 'direction', + 'trigger': 'Trigger', + 'inDirection': 'in', + 'outDirection': 'out', + 'settings': 'settings', + 'name': 'name', + 'value': 'value', + 'resource': 'resource', + 'required': 'required', + 'storage': 'storage', + 'connection': 'connection', + 'enum': 'enum', + 'defaultValue': 'defaultValue', + 'webHookType': 'webHookType', + 'httpTrigger': 'httpTrigger', + 'queue': 'queue', + 'queueName': 'queueName', + 'displayName': 'displayName', + 'xAzureSettings': 'x-azure-settings', + 'entryPoint': 'entryPoint' +}; + +module.exports = { + 'getFunctionMetaData': function (functionName, parsedBindings, serverless) { + const bindings = []; + let bindingSettingsNames = []; + let bindingSettings = []; + let bindingUserSettings = {}; + let bindingType; + const functionsJson = {'disabled': false, 'bindings': []}; + const functionObject = serverless.service.getFunction(functionName); + const handler = functionObject.handler; + const events = functionObject.events; + const params = {}; + + const bindingTypes = parsedBindings.bindingTypes; + const bindingDisplayNames = parsedBindings.bindingDisplayNames; + + for (let eventsIndex = 0; eventsIndex < events.length; eventsIndex++) { + bindingType = Object.keys(functionObject.events[eventsIndex])[0]; + + if (eventsIndex === 0) { + bindingType += constants.trigger; + } + + const index = bindingTypes.indexOf(bindingType); + + if (index < 0) { + throw new Error(`Binding ${bindingType} not supported`); + } + + serverless.cli.log(`Building binding for function: ${functionName} event: ${bindingType}`); + + bindingUserSettings = {}; + const azureSettings = events[eventsIndex][constants.xAzureSettings]; + let bindingTypeIndex = bindingTypes.indexOf(bindingType); + const bindingUserSettingsMetaData = this.getBindingUserSettingsMetaData(azureSettings, bindingType, bindingTypeIndex, bindingDisplayNames); + + bindingTypeIndex = bindingUserSettingsMetaData.index; + bindingUserSettings = bindingUserSettingsMetaData.userSettings; + + if (bindingType.includes(constants.queue) && functionObject.events[eventsIndex].queue) { + bindingUserSettings[constants.queueName] = functionObject.events[eventsIndex].queue; + } + + if (bindingTypeIndex < 0) { + throw new Error('Binding not supported'); + } + + bindingSettings = parsedBindings.bindingSettings[bindingTypeIndex]; + bindingSettingsNames = parsedBindings.bindingSettingsNames[bindingTypeIndex]; + + if (azureSettings) { + for (let azureSettingKeyIndex = 0; azureSettingKeyIndex < Object.keys(azureSettings).length; azureSettingKeyIndex++) { + const key = Object.keys(azureSettings)[azureSettingKeyIndex]; + + if (bindingSettingsNames.indexOf(key) >= 0) { + bindingUserSettings[key] = azureSettings[key]; + } + } + } + + bindings.push(this.getBinding(bindingType, bindingSettings, bindingUserSettings, serverless)); + } + + if (bindingType === constants.httpTrigger && bindings.length === 1) { + bindings.push(this.getHttpOutBinding(bindingUserSettings)); + } + + functionsJson.bindings = bindings; + params.functionsJson = functionsJson; + + const entryPointAndHandlerPath = this.getEntryPointAndHandlerPath(handler); + const metaData = { + 'entryPoint': entryPointAndHandlerPath[constants.entryPoint], + 'handlerPath': entryPointAndHandlerPath.handlerPath, + 'params': params + }; + +return metaData; + }, + + 'getBindingUserSettingsMetaData': function (azureSettings, bindingType, bindingTypeIndex, bindingDisplayNames) { + let bindingDisplayNamesIndex = bindingTypeIndex; + const bindingUserSettings = {}; + + if (azureSettings) { + const directionIndex = Object.keys(azureSettings).indexOf(constants.direction); + + if (directionIndex >= 0) { + const key = Object.keys(azureSettings)[directionIndex]; + const displayName = `$${bindingType}${azureSettings[key]}_displayName`; + + bindingDisplayNamesIndex = bindingDisplayNames.indexOf(displayName.toLowerCase()); + bindingUserSettings[constants.direction] = azureSettings[key]; + } + } + const bindingUserSettingsMetaData = { + 'index': bindingDisplayNamesIndex, + 'userSettings': bindingUserSettings + }; + +return bindingUserSettingsMetaData; + }, + + 'getEntryPointAndHandlerPath': function (handler) { + let handlerPath = 'handler.js'; + let entryPoint = handler; + const handlerSplit = handler.split('.'); + + if (handlerSplit.length > 1) { + entryPoint = handlerSplit[handlerSplit.length - 1]; + handlerPath = `${handler.substring(0, handler.lastIndexOf('.'))}.js`; + } + const metaData = { + 'entryPoint': entryPoint, + 'handlerPath': handlerPath + }; + +return metaData; + }, + + 'getHttpOutBinding': function (bindingUserSettings) { + const binding = {}; + + binding[constants.type] = 'http'; + binding[constants.direction] = constants.outDirection; + binding[constants.name] = '$return'; + if (bindingUserSettings[constants.webHookType]) { + binding[constants.name] = 'res'; + } + +return binding; + }, + + 'getBinding': function (bindingType, bindingSettings, bindingUserSettings, serverless) { + const binding = {}; + + binding[constants.type] = bindingType; + if (bindingUserSettings && bindingUserSettings[constants.direction]) { + binding[constants.direction] = bindingUserSettings[constants.direction]; + } else if (bindingType.includes(constants.trigger)) { + binding[constants.direction] = constants.inDirection; + } else { + binding[constants.direction] = constants.outDirection; + } + + for (let bindingSettingsIndex = 0; bindingSettingsIndex < bindingSettings.length; bindingSettingsIndex++) { + const name = bindingSettings[bindingSettingsIndex][constants.name]; + + if (bindingUserSettings && bindingUserSettings[name]) { + binding[name] = bindingUserSettings[name]; + continue; + } + const value = bindingSettings[bindingSettingsIndex][constants.value]; + const required = bindingSettings[bindingSettingsIndex][constants.required]; + const resource = bindingSettings[bindingSettingsIndex][constants.resource]; + + if (required) { + const defaultValue = bindingSettings[bindingSettingsIndex][constants.defaultValue]; + + if (defaultValue) { + binding[name] = defaultValue; + } else if (name === constants.connection && resource.toLowerCase() === constants.storage) { + binding[name] = 'AzureWebJobsStorage'; + } else { + throw new Error(`Required property ${name} is missing for binding:${bindingType}`); + } + } + + if (value === constants.enum && name !== constants.webHookType) { + const enumValues = bindingSettings[bindingSettingsIndex][constants.enum]; + + binding[name] = enumValues[0][constants.value]; + } + } + +return binding; + } +}; From 1d41a9e0a1bd2c48faeb61ce3b8fc09a41406f74 Mon Sep 17 00:00:00 2001 From: pragnagopa <pgopa@microsoft.com> Date: Fri, 17 Feb 2017 09:52:28 -0800 Subject: [PATCH 2/2] Work around for kudu sync triggers issue --- deploy/lib/createFunction.js | 3 ++- deploy/lib/createFunctions.js | 3 ++- provider/azureProvider.js | 31 +++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/deploy/lib/createFunction.js b/deploy/lib/createFunction.js index a0c22700..5ddd9d64 100644 --- a/deploy/lib/createFunction.js +++ b/deploy/lib/createFunction.js @@ -8,6 +8,7 @@ module.exports = { const metaData = utils.getFunctionMetaData(functionName, this.provider.getParsedBindings(), this.serverless); return this.provider.createZipObject(functionName, metaData.entryPoint, metaData.handlerPath, metaData.params) - .then(() => this.provider.createAndUploadZipFunctions()); + .then(() => this.provider.createAndUploadZipFunctions()) + .then(() => this.provider.syncTriggers()); } }; diff --git a/deploy/lib/createFunctions.js b/deploy/lib/createFunctions.js index 9247ce0d..e1960681 100644 --- a/deploy/lib/createFunctions.js +++ b/deploy/lib/createFunctions.js @@ -14,6 +14,7 @@ module.exports = { }); return BbPromise.all(createFunctionPromises) - .then(() => this.provider.createAndUploadZipFunctions()); + .then(() => this.provider.createAndUploadZipFunctions()) + .then(() => this.provider.syncTriggers()); } }; diff --git a/provider/azureProvider.js b/provider/azureProvider.js index b5ed7543..8c6b9243 100644 --- a/provider/azureProvider.js +++ b/provider/azureProvider.js @@ -473,6 +473,33 @@ return new BbPromise((resolve, reject) => { } + syncTriggers () { + let options = {}; + const requestUrl = ` https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.Web/sites/${functionAppName}/functions/synctriggers?api-version=2015-08-01`; + options = { + 'host': 'management.azure.com', + 'method': 'post', + 'body': {}, + 'url': requestUrl, + 'json': true, + 'headers': { + 'Authorization': constants.bearer + principalCredentials.tokenCache._entries[0].accessToken, + 'Accept': 'application/json,*/*' + } + }; + +return new BbPromise((resolve, reject) => { + request(options, (err, res, body) => { + if (err) { + reject(err); + } + this.serverless.cli.log(`Syncing Triggers....Response statuscode: ${res.statusCode}`); + resolve(res); + }); + }); + + } + cleanUpFunctionsBeforeDeploy (serverlessFunctions) { const deleteFunctionPromises = []; @@ -516,6 +543,10 @@ return new BbPromise((resolve, reject) => { const folderForJSFunction = path.join(functionsFolder, functionName); const handlerPath = path.join(this.serverless.config.servicePath, filePath); + if (!fs.existsSync(functionsFolder)) { + fs.mkdirSync(functionsFolder); + } + if (!fs.existsSync(folderForJSFunction)) { fs.mkdirSync(folderForJSFunction); }