Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functions to list keys of Function Apps #297

Merged
merged 8 commits into from
Jul 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ CHANGELOG
=========

## HEAD (Unreleased)
___NULL___

* Introduce `listHostKeys` and `listFunctionKeys` mix-in functions to retrieve Azure Functions management keys

---

Expand Down
7 changes: 7 additions & 0 deletions resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,8 @@ func Provider() tfbridge.ProviderInfo {
Dependencies: map[string]string{
"@pulumi/pulumi": "^0.17.12",
"@azure/functions": "^1.0.3",
"@azure/ms-rest-azure-js": "^1.3.8",
"@azure/ms-rest-nodeauth": "^2.0.2",
"azure-functions-ts-essentials": "^1.3.2",
},
Overlay: &tfbridge.OverlayInfo{
Expand All @@ -1044,6 +1046,11 @@ func Provider() tfbridge.ProviderInfo {
"zMixins_timer.ts",
},
},
"core": {
DestFiles: []string{
"zMixins.ts",
},
},
"cosmosdb": {
DestFiles: []string{
"zMixins.ts",
Expand Down
92 changes: 83 additions & 9 deletions sdk/nodejs/appservice/zMixins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import * as pulumi from "@pulumi/pulumi";

import * as azurefunctions from "@azure/functions";
import { AzureServiceClient } from "@azure/ms-rest-azure-js";

import { FunctionApp } from "./functionApp";

Expand Down Expand Up @@ -196,7 +197,7 @@ interface FunctionAppArgsBase {
/**
* A mapping of tags to assign to the resource.
*/
readonly tags?: pulumi.Input<{[key: string]: any}>;
readonly tags?: pulumi.Input<{ [key: string]: any }>;

/**
* The runtime version associated with the Function App. Defaults to `~2`.
Expand Down Expand Up @@ -248,7 +249,7 @@ export interface HostSettings {
* A sliding time window used in conjunction with the `healthCheckThreshold` setting.
* Defaults to 2 minutes.
*/
healthCheckWindow:string,
healthCheckWindow: string,
/**
* Maximum number of times the health check can fail before a host recycle is initiated. Defaults to `6`.
*/
Expand Down Expand Up @@ -354,14 +355,14 @@ async function produceDeploymentArchiveAsync(args: MultiCallbackFunctionAppArgs)

const body = await serializeFunctionCallback(func.callback);

map[`${func.name}/index.js`] = new pulumi.asset.StringAsset(`module.exports = require("./handler").handler`),
map[`${func.name}/index.js`] = new pulumi.asset.StringAsset(`module.exports = require("./handler").handler`);
map[`${func.name}/handler.js`] = new pulumi.asset.StringAsset(body.text);
}

return new pulumi.asset.AssetArchive(map);
}

function combineAppSettings(args: MultiCallbackFunctionAppArgs): pulumi.Output<{[key: string]: string}> {
function combineAppSettings(args: MultiCallbackFunctionAppArgs): pulumi.Output<{ [key: string]: string }> {
const applicationSetting = args.appSettings || {};
const perFunctionSettings = args.functions !== undefined ? args.functions.map(c => c.appSettings || {}) : [];
return pulumi.all([applicationSetting, ...perFunctionSettings]).apply(items => items.reduce((a, b) => ({ ...a, ...b }), {}));
Expand Down Expand Up @@ -435,10 +436,9 @@ export interface ArchiveFunctionAppArgs extends FunctionAppArgsBase {
archive: pulumi.Input<pulumi.asset.Archive>;
};

function createFunctionAppParts(
name: string,
args: ArchiveFunctionAppArgs,
opts: pulumi.CustomResourceOptions = {}) {
function createFunctionAppParts(name: string,
args: ArchiveFunctionAppArgs,
opts: pulumi.CustomResourceOptions = {}) {

if (!args.archive) {
throw new Error("Deployment [archive] must be provided.");
Expand Down Expand Up @@ -594,7 +594,7 @@ export abstract class PackagedFunctionApp extends pulumi.ComponentResource {
*/
public readonly endpoint: pulumi.Output<string>;

constructor(type:string,
constructor(type: string,
name: string,
args: ArchiveFunctionAppArgs,
opts: pulumi.ComponentResourceOptions = {}) {
Expand Down Expand Up @@ -711,3 +711,77 @@ export function getResourceGroupNameAndLocation(
const getResult = resourceGroupName.apply(n => core.getResourceGroup({ name: n }));
return { resourceGroupName, location: getResult.location };
}

/**
* Keys associated with a Function App.
*/
export interface FunctionHostKeys {
/** Master key. */
masterKey: string;
/** A dictionary of system keys, e.g. for Durable Functions or Event Grid. */
systemKeys: { [key: string]: string };
/** Default function keys. */
functionKeys: FunctionKeys;
}

/**
* Keys associated with a single Function.
*/
export interface FunctionKeys {
default: string;
[key: string]: string;
}

declare module "./functionApp" {
interface FunctionApp {
/**
* Retrieve the keys associated with the Function App.
*/
getHostKeys(): pulumi.Output<FunctionHostKeys>;

/**
* Retrieve the keys associated with the given Function.
*/
getFunctionKeys(functionName: pulumi.Input<string>): pulumi.Output<FunctionKeys>;
}
}

FunctionApp.prototype.getHostKeys = function(this: FunctionApp) {
return this.id.apply(async id => {
const credentials = await core.getServiceClientCredentials();
const client = new AzureServiceClient(credentials);
const url = `https://management.azure.com${id}/host/default/listkeys?api-version=2018-02-01`;

const response = await client.sendRequest({ method: "POST", url });
if (response.status >= 400) {
throw new Error(`Failed to retrieve the host keys: ${response.bodyAsText}`);
}

const body = response.parsedBody;
if (body.masterKey === undefined || body.systemKeys === undefined || body.functionKeys === undefined) {
throw new Error(`Wrong shape of the host keys response: ${response.bodyAsText}`);
}

return body as FunctionHostKeys;
});
};

FunctionApp.prototype.getFunctionKeys = function(this: FunctionApp, functionName) {
return pulumi.all([this.id, functionName]).apply(async ([id, functionName]) => {
const credentials = await core.getServiceClientCredentials();
const client = new AzureServiceClient(credentials);
const url = `https://management.azure.com${id}/functions/${functionName}/listkeys?api-version=2018-02-01`;

const response = await client.sendRequest({ method: "POST", url });
if (response.status >= 400) {
throw new Error(`Failed to retrieve the function keys: ${response.bodyAsText}`);
}

const body = response.parsedBody;
if (body.default === undefined) {
throw new Error(`Wrong shape of the function keys response: ${response.bodyAsText}`);
}

return body as FunctionKeys;
});
};
1 change: 1 addition & 0 deletions sdk/nodejs/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from "./getSubscriptions";
export * from "./getUserAssignedIdentity";
export * from "./resourceGroup";
export * from "./templateDeployment";
export * from "./zMixins";
37 changes: 37 additions & 0 deletions sdk/nodejs/core/zMixins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { ServiceClientCredentials } from "@azure/ms-rest-js";
import * as msnodeauth from "@azure/ms-rest-nodeauth";
import * as config from "../config";

/**
* Obtain credentials to query Azure Management API. Depending on the environment configuration, this
* are either based on MSI, a service principal, or Azure CLI user credentials.
*/
export async function getServiceClientCredentials(): Promise<ServiceClientCredentials> {
let credentials: ServiceClientCredentials;

if (config.useMsi) {
credentials = await msnodeauth.loginWithAppServiceMSI({ msiEndpoint: config.msiEndpoint });
} else if (config.clientId && config.clientSecret && config.tenantId) {
credentials = await msnodeauth.loginWithServicePrincipalSecret(
config.clientId, config.clientSecret, config.tenantId);
} else {
// `create()` will throw an error if the Az CLI is not installed or `az login` has never been run.
credentials = await msnodeauth.AzureCliCredentials.create();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure these are the complete set of configuration options supported by the Azure provider? I'm a little nervous re-creating this logic outside of the provider. Could we perhaps cross-reference to the primary source for this logic? (Ideally in the backing provider to make sure it's really the same as what the user is seeing for other @pulumi/azure operations.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, me too... I don't know of the source of truth for this.
Ideally, I'd use some function provided by Pulumi core/Terraform which would give me an access token that the provider is already using. Is there something like that? I asked on Slack once but didn't get an answer.


return credentials;
}
2 changes: 2 additions & 0 deletions sdk/nodejs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
},
"dependencies": {
"@azure/functions": "^1.0.3",
"@azure/ms-rest-azure-js": "^1.3.8",
"@azure/ms-rest-nodeauth": "^2.0.2",
"@pulumi/pulumi": "^0.17.12",
"azure-functions-ts-essentials": "^1.3.2"
},
Expand Down
1 change: 1 addition & 0 deletions sdk/nodejs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
"core/index.ts",
"core/resourceGroup.ts",
"core/templateDeployment.ts",
"core/zMixins.ts",
"cosmosdb/account.ts",
"cosmosdb/cassandraKeyspace.ts",
"cosmosdb/getAccount.ts",
Expand Down