Skip to content

Commit

Permalink
Add a couple Azure web server examples (pulumi#309)
Browse files Browse the repository at this point in the history
* Add a couple Azure web server examples

This adds two new examples:

1) A simple Azure web server in a VM, in TypeScript, with a public IP.

2) An Azure web server component that hides many details about how
   to create a VM with a public IP address, etc, and then uses it
   from the primary program to create a variable number of instances.

I used these in recent customer demos and found them to be helpful for
telling a story around components for those working with Azure.

* Fixup after review comments

* Adding the azure-ts-webserver* tests to CI

* Async-await and some formatting
  • Loading branch information
joeduffy authored and stack72 committed Jul 3, 2019
1 parent 496e8f8 commit 9184b5b
Show file tree
Hide file tree
Showing 10 changed files with 419 additions and 0 deletions.
13 changes: 13 additions & 0 deletions azure-ts-webserver-component/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: ws-ts-azure-comp
runtime: nodejs
description: Basic example of an Azure web server accessible over HTTP
template:
config:
azure:location:
description: The Azure location to use
default: westus
username:
description: The username used to configure the Virtual Machine
password:
description: The password used to configure the Virtual Machine
secret: true
69 changes: 69 additions & 0 deletions azure-ts-webserver-component/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new)

# Azure Web Server Virtual Machine Component

This example provisions a configurable number of Linux web servers in an Azure Virtual Machine, and returns the
resulting public IP addresses. This example uses a reusable [Pulumi component](
https://pulumi.io/reference/programming-model/#components) to simplify the creation of new virtual machines. By
defining a `WebServer` class, we can hide many details (see [here](./webserver.ts) for its definition).

## Prerequisites

- [Node.js](https://nodejs.org/en/download/)
- [Download and install the Pulumi CLI](https://pulumi.io/install)
- [Connect Pulumi with your Azure account](https://pulumi.io/quickstart/azure/setup.html) (if your `az` CLI is
configured, this will just work)

## Running the App

1. Create a new stack:

```
$ pulumi stack init dev
```

1. Configure the deployment. The username and password here will be used to configure the Virtual Machine. The
password must adhere to the [Azure restrictions on VM passwords](
https://docs.microsoft.com/en-us/azure/virtual-machines/windows/faq#what-are-the-password-requirements-when-creating-a-vm).

```
$ pulumi config set azure:location westus # any valid Azure region will do
$ pulumi config set username webmaster
$ pulumi config set password <your-password> --secret
$ pulumi config set count 5 # optional -- will default to 2 if left out
```

Note that `--secret` ensures your password is encrypted safely.

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. Run `pulumi up` to preview and deploy changes:

```
$ pulumi up
Previewing changes:
...
Performing changes:
...
info: 15 changes performed:
+ 15 resources created
Update duration: 4m27s
```

1. Check the resulting IP addresses:

```
$ pulumi stack output ipAddresses
[ 40.112.181.239, ..., 40.112.181.240 ]
```
42 changes: 42 additions & 0 deletions azure-ts-webserver-component/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as pulumi from "@pulumi/pulumi";
import * as azure from "@pulumi/azure";
import { WebServer } from "./webserver";

// Get the desired username and password for our webserver VMs.
const config = new pulumi.Config();
const count = config.getNumber("count") || 2;
const username = config.require("username");
const password = config.requireSecret("password");

// All resources will share a resource group.
const resourceGroupName = new azure.core.ResourceGroup("server-rg").name;

// Create a network and subnet for all VMs.
const network = new azure.network.VirtualNetwork("server-network", {
resourceGroupName,
addressSpaces: ["10.0.0.0/16"],
subnets: [{
name: "default",
addressPrefix: "10.0.1.0/24",
}],
});
const subnet = new azure.network.Subnet("server-subnet", {
resourceGroupName,
virtualNetworkName: network.name,
addressPrefix: "10.0.2.0/24",
});

// Now, allocate a few websever VMs -- by default, just 2, but this is configurable.
export const ipAddresses = [];
for (let i = 0; i < count; i++) {
const server = new WebServer(`ws-${i}`, {
username,
password,
bootScript: `#!/bin/bash\n
echo "Hello, from Server #{i+1}!" > index.html
nohup python -m SimpleHTTPServer 80 &`,
resourceGroupName: resourceGroupName,
subnetId: subnet.id,
});
ipAddresses.push(server.getIpAddress());
}
8 changes: 8 additions & 0 deletions azure-ts-webserver-component/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "ws-ts-azure-comp",
"version": "0.1.0",
"dependencies": {
"@pulumi/pulumi": "latest",
"@pulumi/azure": "latest"
}
}
100 changes: 100 additions & 0 deletions azure-ts-webserver-component/webserver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import * as pulumi from "@pulumi/pulumi";
import * as azure from "@pulumi/azure";

/**
* WebServer is a reusable web server component that creates and exports a NIC, public IP, and VM.
*/
export class WebServer extends pulumi.ComponentResource {
public readonly publicIp: azure.network.PublicIp;
public readonly networkInterface: azure.network.NetworkInterface;
public readonly vm: azure.compute.VirtualMachine;

/**
* Allocate a new web server VM, NIC, and public IP address.
* @param name The name of the web server resource.
* @param args A bag of arguments to control the web server VM creation.
*/
constructor(name: string, args: WebServerArgs) {
super("ws-ts-azure-comp:webserver:WebServer", name);

// Allocate a public IP and assign it to our NIC.
this.publicIp = new azure.network.PublicIp(`${name}-ip`, {
resourceGroupName: args.resourceGroupName,
allocationMethod: "Dynamic",
}, { parent: this });
this.networkInterface = new azure.network.NetworkInterface(`${name}-nic`, {
resourceGroupName: args.resourceGroupName,
ipConfigurations: [{
name: "webserveripcfg",
subnetId: args.subnetId,
privateIpAddressAllocation: "Dynamic",
publicIpAddressId: this.publicIp.id,
}],
}, { parent: this });

// Now create the VM, using the resource group and NIC allocated above.
this.vm = new azure.compute.VirtualMachine(`${name}-vm`, {
resourceGroupName: args.resourceGroupName,
networkInterfaceIds: [this.networkInterface.id],
vmSize: args.vmSize || "Standard_A0",
deleteDataDisksOnTermination: true,
deleteOsDiskOnTermination: true,
osProfile: {
computerName: "hostname",
adminUsername: args.username,
adminPassword: args.password,
customData: args.bootScript,
},
osProfileLinuxConfig: {
disablePasswordAuthentication: false,
},
storageOsDisk: {
createOption: "FromImage",
name: `${name}-osdisk1`,
},
storageImageReference: {
publisher: "canonical",
offer: "UbuntuServer",
sku: "16.04-LTS",
version: "latest",
},
}, { parent: this });
}

public getIpAddress(): pulumi.Output<string> {
// The public IP address is not allocated until the VM is running, so wait for that
// resource to create, and then lookup the IP address again to report its public IP.
const ready = pulumi.all({ _: this.vm.id, name: this.publicIp.name, resourceGroupName: this.publicIp.resourceGroupName });
return ready.apply(async d => {
const ip = await azure.network.getPublicIP({ name: d.name, resourceGroupName: d.resourceGroupName });
return ip.ipAddress;
});
}
}

export interface WebServerArgs {
/**
* A required username for the VM login.
*/
username: pulumi.Input<string>;
/**
* A required encrypted password for the VM password.
*/
password: pulumi.Input<string>;
/**
* An optional boot script that the VM will use.
*/
bootScript?: pulumi.Input<string>;
/**
* An optional VM size; if unspecified, Standard_A0 (micro) will be used.
*/
vmSize?: pulumi.Input<string>;
/**
* A required Resource Group in which to create the VM
*/
resourceGroupName: pulumi.Input<string>;
/**
* A required Subnet in which to deploy the VM
*/
subnetId: pulumi.Input<string>;
}
13 changes: 13 additions & 0 deletions azure-ts-webserver/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: webserver-ts-azure
runtime: nodejs
description: Basic example of an Azure web server accessible over HTTP
template:
config:
azure:location:
description: The Azure location to deploy to
default: westus
username:
description: The username used to configure the Virtual Machine
password:
description: The password used to configure the Virtual Machine
secret: true
65 changes: 65 additions & 0 deletions azure-ts-webserver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new)

# Azure Web Server Virtual Machine

This example provisions a Linux web server in an Azure Virtual Machine and gives it a public IP address.

## Prerequisites

- [Node.js](https://nodejs.org/en/download/)
- [Download and install the Pulumi CLI](https://pulumi.io/install)
- [Connect Pulumi with your Azure account](https://pulumi.io/quickstart/azure/setup.html) (if your `az` CLI is
configured, this will just work)

## Running the App

1. Create a new stack:

```
$ pulumi stack init dev
```

1. Configure the app deployment. The username and password here will be used to configure the Virtual Machine. The
password must adhere to the [Azure restrictions on VM passwords](
https://docs.microsoft.com/en-us/azure/virtual-machines/windows/faq#what-are-the-password-requirements-when-creating-a-vm).

```
$ pulumi config set azure:location westus # any valid Azure region will do
$ pulumi config set username webmaster
$ pulumi config set password <your-password> --secret
```

Note that `--secret` ensures your password is encrypted safely.

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. Run `pulumi up` to preview and deploy changes:

```
$ pulumi up
Previewing changes:
...
Performing changes:
...
info: 7 changes performed:
+ 7 resources created
Update duration: 2m38s
```

1. Check the IP address:

```
$ pulumi stack output ipAddress
40.112.181.239
```
80 changes: 80 additions & 0 deletions azure-ts-webserver/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import * as pulumi from "@pulumi/pulumi";
import * as azure from "@pulumi/azure";

// Get the desired username and password for our VM.
const config = new pulumi.Config();
const username = config.require("username");
const password = config.requireSecret("password");

// All resources will share a resource group.
const resourceGroupName = new azure.core.ResourceGroup("server-rg").name;

// Create a network and subnet for all VMs.
const network = new azure.network.VirtualNetwork("server-network", {
resourceGroupName,
addressSpaces: ["10.0.0.0/16"],
subnets: [{
name: "default",
addressPrefix: "10.0.1.0/24",
}],
});
const subnet = new azure.network.Subnet("server-subnet", {
resourceGroupName,
virtualNetworkName: network.name,
addressPrefix: "10.0.2.0/24",
});

// Now allocate a public IP and assign it to our NIC.
const publicIp = new azure.network.PublicIp("server-ip", {
resourceGroupName,
allocationMethod: "Dynamic",
});

const networkInterface = new azure.network.NetworkInterface("server-nic", {
resourceGroupName,
ipConfigurations: [{
name: "webserveripcfg",
subnetId: subnet.id,
privateIpAddressAllocation: "Dynamic",
publicIpAddressId: publicIp.id,
}],
});

// Now create the VM, using the resource group and NIC allocated above.
const vm = new azure.compute.VirtualMachine("server-vm", {
resourceGroupName,
networkInterfaceIds: [networkInterface.id],
vmSize: "Standard_A0",
deleteDataDisksOnTermination: true,
deleteOsDiskOnTermination: true,
osProfile: {
computerName: "hostname",
adminUsername: username,
adminPassword: password,
customData: `#!/bin/bash\n
echo "Hello, World!" > index.html
nohup python -m SimpleHTTPServer 80 &`,
},
osProfileLinuxConfig: {
disablePasswordAuthentication: false,
},
storageOsDisk: {
createOption: "FromImage",
name: "myosdisk1",
},
storageImageReference: {
publisher: "canonical",
offer: "UbuntuServer",
sku: "16.04-LTS",
version: "latest",
},
});

// The public IP address is not allocated until the VM is running, so wait for that
// resource to create, and then lookup the IP address again to report its public IP.
const done = pulumi.all({ _: vm.id, name: publicIp.name, resourceGroupName: publicIp.resourceGroupName });

export const ipAddress = done.apply(async d => {
const ip = await azure.network.getPublicIP({ name: d.name, resourceGroupName: d.resourceGroupName });
return ip.ipAddress;
});
Loading

0 comments on commit 9184b5b

Please sign in to comment.