Add Azure functions example in Typescript
ADD
azure-ts-functions/Pulumi.yaml
+name: azure-functions
+runtime: nodejs

ADD
azure-ts-functions/README.md
+# Azure Functions
+
+An example Pulumi component that deploys a Typescript function to Azure Functions.
+
+## Running the App
+
+1. Create a new stack:
+
+ ```
+ $ pulumi stack init azure-fn
+ ```
+
+1. Login to Azure CLI (you will be prompted to do this during deployment if you forget this step):
+
+ ```
+ $ az login
+ ```
+
+1. Restore NPM dependencies:
+
+ ```
+ $ npm install
+ ```
+
+1. Build the Typescript project:
+
+ ```
+ $ npm run build
+ ```
+
+1. Run `pulumi update` to preview and deploy changes:
+
+ ```
+ $ pulumi update
+ Previewing changes:
+ ...
+
+ Performing changes:
+ ...
+ info: 9 changes performed:
+ + 9 resources created
+ Update duration: 1m20.493392283s
+ ```
+
+1. Check the deployed function endpoint:
+
+ ```
+ $ pulumi stack output endpoint
+ https://fn-app051a4f8b.azurewebsites.net/api/fn
+ $ curl "$(pulumi stack output endpoint)"
+ Greetings from Azure Functions!
+ ...
+ ```

ADD
azure-ts-functions/azureFunction.ts
+import * as pulumi from "@pulumi/pulumi";
+import * as azure from "@pulumi/azure";
+
+import * as azurefunctions from "azure-functions-ts-essentials";
+
+import { signedBlobReadUrl } from "./sas";
+
+export type Context = azurefunctions.Context;
+export type HttpRequest = azurefunctions.HttpRequest;
+export * from "azure-functions-ts-essentials";
+
+export type AzureFunctionHandler = (context: Context, request: HttpRequest) => void;
+
+
+async function blobContent(name: string, handler: AzureFunctionHandler): Promise<pulumi.asset.AssetMap> {
+ let serializedHandler = await pulumi.runtime.serializeFunction(handler);
+ let map: pulumi.asset.AssetMap = {};
+
+ map["host.json"] = new pulumi.asset.StringAsset(JSON.stringify({}));
+ map[`${name}/function.json`] = new pulumi.asset.StringAsset(JSON.stringify({
+ "disabled": false,
+ "bindings": [
+ {
+ "authLevel": "anonymous",
+ "type": "httpTrigger",
+ "direction": "in",
+ "name": "req"
+ },
+ {
+ "type": "http",
+ "direction": "out",
+ "name": "$return"
+ }
+ ]
+ }));
+ map[`${name}/index.js`] = new pulumi.asset.StringAsset(`module.exports = require("./handler").handler`),
+ map[`${name}/handler.js`] = new pulumi.asset.StringAsset(serializedHandler.text);
+
+ return map;
+}
+
+
+// An Azure function exposed via an HTTP endpoint.
+
+export class HttpFunction extends pulumi.ComponentResource {
+ readonly resourceGroup: azure.core.ResourceGroup;
+
+ readonly storageAccount: azure.storage.Account;
+ readonly storageContainer: azure.storage.Container;
+ readonly blob: azure.storage.ZipBlob;
+
+ readonly appServicePlan: azure.appservice.Plan;
+ readonly functionApp: azure.appservice.FunctionApp;
+
+ readonly codeBlobUrl: pulumi.Output<string>;
+ readonly endpoint: pulumi.Output<string>;
+
+ constructor(name: string, handler: AzureFunctionHandler, options?: pulumi.ResourceOptions) {
+ super("azure:HttpFunction", name, {}, options);
+
+ let parentArgs = { parent: this };
+
+ this.resourceGroup = new azure.core.ResourceGroup(`${name}-rg`, {
+ location: "West US 2",
+ }, parentArgs);
+
+ let resourceGroupArgs = {
+ resourceGroupName: this.resourceGroup.name,
+ location: this.resourceGroup.location,
+ };
+
+ this.storageAccount = new azure.storage.Account(`${name}sa`, {
+ ...resourceGroupArgs,
+
+ accountKind: "StorageV2",
+ accountTier: "Standard",
+ accountReplicationType: "LRS",
+ }, parentArgs);
+
+ this.storageContainer = new azure.storage.Container(`${name}-c`, {
+ resourceGroupName: this.resourceGroup.name,
+ storageAccountName: this.storageAccount.name,
+ containerAccessType: "private",
+ }, parentArgs);
+
+ this.blob = new azure.storage.ZipBlob(`${name}-b`, {
+ resourceGroupName: this.resourceGroup.name,
+ storageAccountName: this.storageAccount.name,
+ storageContainerName: this.storageContainer.name,
+ type: "block",
+
+ content: new pulumi.asset.AssetArchive(blobContent(name, handler)),
+ }, parentArgs);
+
+ this.codeBlobUrl = signedBlobReadUrl(this.blob, this.storageAccount, this.storageContainer);
+
+ this.appServicePlan = new azure.appservice.Plan(`${name}-p`, {
+ ...resourceGroupArgs,
+
+ kind: "FunctionApp",
+
+ // https://social.msdn.microsoft.com/Forums/azure/en-US/665c365d-2b86-4a77-8cea-72ccffef216c
+ sku: {
+ tier: "Dynamic",
+ size: "Y1",
+ },
+ }, parentArgs);
+
+ this.functionApp = new azure.appservice.FunctionApp(`${name}-app`, {
+ ...resourceGroupArgs,
+
+ appServicePlanId: this.appServicePlan.id,
+ storageConnectionString: this.storageAccount.primaryConnectionString,
+
+ appSettings: {
+ "WEBSITE_RUN_FROM_ZIP": this.codeBlobUrl,
+ },
+ }, parentArgs);
+
+ this.endpoint = this.functionApp.defaultHostname.apply(h => {
+ return `https://${h}/api/${name}`;
+ });
+ }
+};

ADD
azure-ts-functions/index.ts
+import * as azureFunction from "./azureFunction";
+
+// Create an Azure function that prints a message and the request headers.
+
+function handler(context: azureFunction.Context, request: azureFunction.HttpRequest) {
+ let body = "";
+ let headers = context.req!.headers!;
+ for (let h in headers) {
+ body = body + `${h} = ${headers[h]}\n`;
+ }
+
+ let res: azureFunction.HttpResponse = {
+ status: azureFunction.HttpStatusCode.OK,
+ headers: {
+ "content-type": "text/plain",
+ },
+ body: `Greetings from Azure Functions!\n\n===\n\n${body}`,
+ };
+
+ context.done(undefined, res);
+}
+
+let fn = new azureFunction.HttpFunction("fn", handler);
+export let endpoint = fn.endpoint;

ADD
azure-ts-functions/package.json
+{
+ "name": "azure-ts-functions",
+ "version": "1.0.0",
+ "main": "bin/index.js",
+ "typings": "bin/index.d.ts",
+ "scripts": {
+ "build": "tsc"
+ },
+ "devDependencies": {
+ "@types/node": "^10.3.3",
+ "azure-functions-ts-essentials": "^1.3.2",
+ "typescript": "^2.9.2"
+ },
+ "dependencies": {
+ "@pulumi/azure": "^0.14.0",
+ "@pulumi/pulumi": "^0.14.0",
+ "azure-storage": "^2.9.0-preview"
+ }
+}

ADD
azure-ts-functions/sas.ts
+import * as pulumi from "@pulumi/pulumi";
+import * as azure from "@pulumi/azure";
+
+import * as azurestorage from "azure-storage";
+
+// Given an Azure blob, create a SAS URL that can read it.
+export function signedBlobReadUrl(
+ blob: azure.storage.Blob | azure.storage.ZipBlob,
+ account: azure.storage.Account,
+ container: azure.storage.Container,
+): pulumi.Output<string> {
+ // Choose a fixed, far-future expiration date for signed blob URLs.
+ // The shared access signature (SAS) we generate for the Azure storage blob must remain valid for as long as the
+ // Function App is deployed, since new instances will download the code on startup. By using a fixed date, rather
+ // than (e.g.) "today plus ten years", the signing operation is idempotent.
+ const signatureExpiration = new Date(2100, 1);
+
+ return pulumi.all([
+ account.primaryConnectionString,
+ container.name,
+ blob.name,
+ ]).apply(([connectionString, containerName, blobName]) => {
+ let blobService = new azurestorage.BlobService(connectionString);
+ let signature = blobService.generateSharedAccessSignature(
+ containerName,
+ blobName,
+ {
+ AccessPolicy: {
+ Expiry: signatureExpiration,
+ Permissions: azurestorage.BlobUtilities.SharedAccessPermissions.READ,
+ },
+ }
+ );
+
+ return blobService.getUrl(containerName, blobName, signature);
+ });
+}

ADD
azure-ts-functions/tsconfig.json
+{
+ "compilerOptions": {
+ "outDir": "bin",
+ "target": "es6",
+ "lib": [
+ "es6"
+ ],
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "declaration": true,
+ "sourceMap": true,
+ "stripInternal": true,
+ "experimentalDecorators": true,
+ "pretty": true,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitAny": true,
+ "noImplicitReturns": true,
+ "forceConsistentCasingInFileNames": true,
+ "strictNullChecks": true
+ },
+ "files": [
+ "azureFunction.ts",
+ "index.ts",
+ "sas.ts",
+ ]
+}

Add Azure functions example in Typescript