forked from pulumi/examples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Azure functions example in Typescript
- Loading branch information
Showing
7 changed files
with
285 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
name: azure-functions | ||
runtime: nodejs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# 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! | ||
... | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
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}`; | ||
}); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
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); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"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", | ||
] | ||
} |