Add container example
ADD
voting-app/.gitignore
+/.pulumi/
+/bin/
+/node_modules/
+/yarn.lock
\ No newline at end of file

ADD
voting-app/Pulumi.yaml
+name: voting-app
+runtime: nodejs
+description: Voting app that uses containers
\ No newline at end of file

ADD
voting-app/frontend/Dockerfile
+FROM tiangolo/uwsgi-nginx-flask:python3.6
+RUN pip install redis
+COPY /app /app

ADD
voting-app/frontend/app/config_file.cfg
+# UI Configurations
+TITLE = 'Pulumi Voting App'
+VOTE1VALUE = 'Tabs'
+VOTE2VALUE = 'Spaces'
+SHOWHOST = 'false'
\ No newline at end of file

ADD
voting-app/frontend/app/main.py
+# Copied from https://github.com/Azure-Samples/azure-voting-app-redis
+
+from flask import Flask, request, render_template
+import os
+import random
+import redis
+import socket
+import sys
+
+app = Flask(__name__)
+
+# Load configurations
+app.config.from_pyfile('config_file.cfg')
+button1 = app.config['VOTE1VALUE']
+button2 = app.config['VOTE2VALUE']
+title = app.config['TITLE']
+
+# Redis configurations
+redis_server = os.environ['REDIS']
+redis_port = os.environ['REDIS_PORT']
+redis_password = os.environ['REDIS_PWD']
+
+# Redis Connection
+try:
+ r = redis.StrictRedis(host=redis_server, port=redis_port, password=redis_password)
+ r.ping()
+except redis.ConnectionError:
+ exit('Failed to connect to Redis, terminating.')
+
+# Init Redis
+if not r.get(button1): r.set(button1,0)
+if not r.get(button2): r.set(button2,0)
+
+@app.route('/', methods=['GET', 'POST'])
+def index():
+
+ if request.method == 'GET':
+
+ # Get current values
+ vote1 = r.get(button1).decode('utf-8')
+ vote2 = r.get(button2).decode('utf-8')
+
+ # Return index with values
+ return render_template("index.html", value1=int(vote1), value2=int(vote2), button1=button1, button2=button2, title=title)
+
+ elif request.method == 'POST':
+
+ if request.form['vote'] == 'reset':
+
+ # Empty table and return results
+ r.set(button1,0)
+ r.set(button2,0)
+ vote1 = r.get(button1).decode('utf-8')
+ vote2 = r.get(button2).decode('utf-8')
+ return render_template("index.html", value1=int(vote1), value2=int(vote2), button1=button1, button2=button2, title=title)
+
+ else:
+
+ # Insert vote result into DB
+ vote = request.form['vote']
+ r.incr(vote,1)
+
+ # Get current values
+ vote1 = r.get(button1).decode('utf-8')
+ vote2 = r.get(button2).decode('utf-8')
+
+ # Return results
+ return render_template("index.html", value1=int(vote1), value2=int(vote2), button1=button1, button2=button2, title=title)
+
+if __name__ == "__main__":
+ app.run()

ADD
voting-app/frontend/app/static/default.css
+body {
+ background-color:#F8F8F8;
+}
+
+div#container {
+ margin-top:5%;
+}
+
+div#space {
+ display:block;
+ margin: 0 auto;
+ width: 500px;
+ height: 10px;
+
+}
+
+div#logo {
+ display:block;
+ margin: 0 auto;
+ width: 500px;
+ text-align: center;
+ font-size:30px;
+ font-family: 'PT Sans', sans-serif;
+ /*border-bottom: 1px solid black;*/
+}
+
+div#form {
+ padding: 20px;
+ padding-right: 20px;
+ padding-top: 20px;
+ display:block;
+ margin: 0 auto;
+ width: 500px;
+ text-align: center;
+ font-size:30px;
+ font-family: 'PT Sans', sans-serif;
+ border-bottom: 1px solid black;
+ border-top: 1px solid black;
+}
+
+div#results {
+ display:block;
+ margin: 0 auto;
+ width: 500px;
+ text-align: center;
+ font-size:30px;
+ font-family: 'PT Sans', sans-serif;
+}
+
+.button {
+ background-color: #4CAF50; /* Green */
+ border: none;
+ color: white;
+ padding: 16px 32px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+ margin: 4px 2px;
+ -webkit-transition-duration: 0.4s; /* Safari */
+ transition-duration: 0.4s;
+ cursor: pointer;
+ width: 250px;
+}
+
+.button1 {
+ background-color: white;
+ color: black;
+ border: 2px solid #008CBA;
+}
+
+.button1:hover {
+ background-color: #008CBA;
+ color: white;
+}
+.button2 {
+ background-color: white;
+ color: black;
+ border: 2px solid #555555;
+}
+
+.button2:hover {
+ background-color: #555555;
+ color: white;
+}
+
+.button3 {
+ background-color: white;
+ color: black;
+ border: 2px solid #f44336;
+}
+
+.button3:hover {
+ background-color: #f44336;
+ color: white;
+}
\ No newline at end of file

ADD
voting-app/index.ts
+// Copyright 2017, Pulumi Corporation. All rights reserved.
+
+import * as cloud from "@pulumi/cloud";
+
+// To simplify this example, we have defined the password directly in code
+// In a real app, would add the secret via `pulumi config secret <key> <value>` and
+// access via config APIs
+let redisPassword = "SECRETPASSWORD";
+
+let redisPort = 6379;
+
+let redisCache = new cloud.Service("voting-app-cache", {
+ containers: {
+ redis: {
+ image: "redis:alpine",
+ memory: 128,
+ ports: [{ port: redisPort }],
+ command: ["redis-server", "--requirepass", redisPassword],
+ },
+ },
+});
+
+let frontend = new cloud.Service("voting-app-frontend", {
+ containers: {
+ votingAppFrontend: {
+ build: "./frontend",
+ memory: 128,
+ ports: [{ port: 80 }],
+ environment: {
+ // pass in the created container info in environment variables
+ "REDIS": redisCache.getEndpoint("redis", redisPort).then(e => e.hostname),
+ "REDIS_PORT": redisCache.getEndpoint("redis", redisPort).then(e => e.port).then(port => port.toString()),
+ "REDIS_PWD": redisPassword
+ }
+ },
+ },
+});
+
+frontend.getEndpoint().then(e => e.hostname).then(hostname => console.log(`URL: ${hostname}`));
+frontend.getEndpoint().then(e => e.port).then(port => console.log(`PORT: ${port}`));

ADD
voting-app/package.json
+{
+ "name": "pulumi-voting-app",
+ "version": "0.1",
+ "main": "bin/index.js",
+ "typings": "bin/index.d.ts",
+ "scripts": {
+ "build": "tsc"
+ },
+ "devDependencies": {
+ "@types/node": "^8.0.27",
+ "typescript": "^2.1.4"
+ },
+ "peerDependencies": {
+ "@pulumi/cloud": "*"
+ },
+ "dependencies": {
+ "@types/node-fetch": "^1.6.7",
+ "node-fetch": "^1.7.3",
+ "redis": "^2.8.0"
+ }
+}

ADD
voting-app/tsconfig.json
+{
+ "compilerOptions": {
+ "outDir": "bin",
+ "target": "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": [
+ "index.ts"
+ ]
+}

Add container example (#1)