Add AKS/CosmosDB/Helm MEAN example
ADD
azure-ts-aks-mean/Pulumi.yaml
+name: azure-mean
+description: A simple Deployment running nginx, exposed to the Internet with a Service
+runtime: nodejs

ADD
azure-ts-aks-mean/cluster.ts
+// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
+
+import * as azure from "@pulumi/azure";
+import * as pulumi from "@pulumi/pulumi";
+import * as k8s from "@pulumi/kubernetes";
+import * as config from "./config";
+
+// Create the AD service principal for the K8s cluster.
+let adApp = new azure.ad.Application("aks");
+let adSp = new azure.ad.ServicePrincipal("aksSp", { applicationId: adApp.applicationId });
+let adSpPassword = new azure.ad.ServicePrincipalPassword("aksSpPassword", {
+ servicePrincipalId: adSp.id,
+ value: config.password,
+ endDate: "2099-01-01T00:00:00Z",
+});
+
+// Now allocate an AKS cluster.
+export const k8sCluster = new azure.containerservice.KubernetesCluster("aksCluster", {
+ resourceGroupName: config.resourceGroup.name,
+ location: config.location,
+ agentPoolProfile: {
+ name: "aksagentpool",
+ count: config.nodeCount,
+ vmSize: config.nodeSize,
+ },
+ dnsPrefix: `${pulumi.getStack()}-kubernetes`,
+ linuxProfile: {
+ adminUsername: "aksuser",
+ sshKeys: [{
+ keyData: config.sshPublicKey,
+ }],
+ },
+ servicePrincipal: {
+ clientId: adApp.applicationId,
+ clientSecret: adSpPassword.value,
+ },
+});
+
+// Expose a K8s provider instance using our custom cluster instance.
+export const k8sProvider = new k8s.Provider("aksK8s", {
+ kubeconfig: k8sCluster.kubeConfigRaw,
+});

ADD
azure-ts-aks-mean/config.ts
+// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
+
+import * as azure from "@pulumi/azure";
+import * as pulumi from "@pulumi/pulumi";
+
+// Parse and export configuration variables for this stack.
+const config = new pulumi.Config();
+export const password = config.require("password");
+export const location = config.get("location") || "East US";
+export const failoverLocation = config.get("failoverLocation") || "East US 2";
+export const nodeCount = config.getNumber("nodeCount") || 2;
+export const nodeSize = config.get("nodeSize") || "Standard_D2_v2";
+export const sshPublicKey = config.require("sshPublicKey");
+export const resourceGroup = new azure.core.ResourceGroup("aks", { location });

ADD
azure-ts-aks-mean/index.ts
+import * as k8s from "@pulumi/kubernetes";
+import * as azure from "@pulumi/azure";
+import * as mongoHelpers from "./mongoHelpers";
+import * as config from "./config";
+
+// Create an AKS cluster.
+import { k8sCluster, k8sProvider } from "./cluster";
+
+// Create a MongoDB-flavored instance of CosmosDB.
+const cosmosdb = new azure.cosmosdb.Account("cosmosDb", {
+ kind: "MongoDB",
+ resourceGroupName: config.resourceGroup.name,
+ location: config.location,
+ consistencyPolicy: {
+ consistencyLevel: "BoundedStaleness",
+ maxIntervalInSeconds: 10,
+ maxStalenessPrefix: 200
+ },
+ offerType: "Standard",
+ enableAutomaticFailover: true,
+ geoLocations: [
+ { location: config.location, failoverPriority: 0 },
+ { location: config.failoverLocation, failoverPriority: 1 }
+ ]
+});
+
+// Create secret from MongoDB connection string.
+const mongoConnStrings = new k8s.core.v1.Secret(
+ "mongo-secrets",
+ {
+ metadata: { name: "mongo-secrets" },
+ data: mongoHelpers.parseConnString(cosmosdb.connectionStrings)
+ },
+ { provider: k8sProvider }
+);
+
+// Boot up nodejs Helm chart example using CosmosDB in place of in-cluster MongoDB.
+const node = new k8s.helm.v2.Chart(
+ "node",
+ {
+ repo: "bitnami",
+ chart: "node",
+ version: "4.0.1",
+ values: {
+ serviceType: "LoadBalancer",
+ mongodb: { install: false },
+ externaldb: { ssl: true, secretName: "mongo-secrets" }
+ }
+ },
+ { providers: { kubernetes: k8sProvider }, dependsOn: mongoConnStrings }
+);
+
+// Export kubeconfig file, cluster name, and public IP address for Kubernetes application. These can
+// be accessed from the CLI, like: `pulumi stack output kubeconfig > kubeconfig.yaml`.
+export const kubeconfig = k8sCluster.kubeConfig;
+export const cluster = k8sCluster.name;
+export const frontendAddress = node
+ .getResource("v1/Service", "node-node")
+ .status.apply(status => status.loadBalancer.ingress[0].ip);

ADD
azure-ts-aks-mean/mongoHelpers.ts
+import * as pulumi from "@pulumi/pulumi";
+
+export function parseConnString(
+ conns: pulumi.Output<string[]>
+): pulumi.Output<{ [key: string]: string }> {
+ // Per the official docs[1], the format of this connection string is:
+ //
+ // mongodb://username:password@host:port/[database]?ssl=true
+ //
+ // Where these could have the following values:
+ //
+ // {
+ // username: "cosmosdb93a4133a",
+ // password: "23maXrWsrzZ1LmPe4w6XNGRJJTHsqGZPDTjyVQNbPaw119KCoCNpStH0DQms5MKdyAecisBM9uWbpV7lUnyNeQ==",
+ // host: "cosmosdb93a4133a.documents.azure.com",
+ // port: "10255",
+ // database: "mydatabase"
+ // }
+ //
+ // There are a few subtleties involved in getting the Bitnami node Chart to actually be able to
+ // use this:
+ //
+ // 1. The `database` field is optional, we default to `test`, as the API expects.
+ // 2. The node Chart expects the components of this connection string to be parsed and
+ // presented in files in a `Secret`. The CosmosDb API doesn't natively expose this, so we
+ // must parse it ourselves.
+ // 3. The node Chart uses mongoose to speak the MongoDB wire protocol to CosmosDB. Mongoose
+ // fails to parse base64-encoded passwords because it doesn't like the `=` character. This
+ // means we have to (1) URI-encode the password component ourselves, and (2) base64-encode
+ // that URI-encoded password, because this is the format Kubernetes expects.
+ //
+ // [1]: https://docs.microsoft.com/en-us/azure/cosmos-db/connect-mongodb-account
+
+ function toBase64(s: string): string {
+ return Buffer.from(s).toString("base64");
+ }
+
+ return conns.apply(conns => {
+ const conn = conns[0];
+ const noProtocol = conn.replace(/^mongodb\:\/\//, "");
+ const [username, rest1, rest2] = noProtocol.split(":", 3);
+ const [password, host] = rest1.split("@", 2);
+ const [port, rest3] = rest2.split("/", 2);
+ const database = rest3.replace(/\?ssl=true$/, "");
+ return {
+ host: toBase64(host),
+ port: toBase64(port),
+ username: toBase64(username),
+ password: toBase64(encodeURIComponent(password)),
+ database: toBase64(database == "" ? "test" : database)
+ };
+ });
+}

ADD
azure-ts-aks-mean/package.json
+{
+ "name": "exposed-deployment",
+ "devDependencies": {
+ "@types/node": "latest"
+ },
+ "dependencies": {
+ "@pulumi/azure": "latest",
+ "@pulumi/kubernetes": "v0.17.0-rc1",
+ "@pulumi/pulumi": "latest",
+ "@types/mongoose": "^5.2.11",
+ "mongoose": "^5.2.13"
+ }
+}

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

Add AKS/CosmosDB/Helm MEAN example