diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..ac10382
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,3 @@
+FROM tiangolo/uwsgi-nginx-flask:python3.6
+RUN pip install redis
+ADD /azure-vote /app
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644
index 0000000..429ac0c
--- /dev/null
+++ b/azure-pipelines.yml
@@ -0,0 +1,142 @@
+# Deploy to Azure Kubernetes Service
+# Build and push image to Azure Container Registry; Deploy to Azure Kubernetes Service
+# https://docs.microsoft.com/azure/devops/pipelines/languages/docker
+
+trigger:
+- master
+
+resources:
+- repo: self
+
+variables:
+
+ # Container registry service connection established during pipeline creation
+ dockerRegistryServiceConnection: '540ecf2e-798b-4d1b-b6ec-4b7b846bc760'
+ imageRepository: 'azure-vote-front-devops'
+ containerRegistry: 'acrsea.azurecr.io'
+ dockerfilePath: '**/Dockerfile'
+ tag: '$(Build.BuildId)'
+ imagePullSecret: 'acrsea98100991-auth'
+
+ # Agent VM image name
+ vmImageName: 'ubuntu-latest'
+
+ # Name of the new namespace being created to deploy the PR changes.
+ k8sNamespaceForPR: 'review-app-$(System.PullRequest.PullRequestId)'
+
+stages:
+- stage: Build
+ displayName: Build stage
+ jobs:
+ - job: Build
+ displayName: Build
+ pool:
+ vmImage: $(vmImageName)
+ steps:
+ - task: Docker@2
+ displayName: Build and push an image to container registry
+ inputs:
+ command: buildAndPush
+ repository: $(imageRepository)
+ dockerfile: $(dockerfilePath)
+ containerRegistry: $(dockerRegistryServiceConnection)
+ tags: |
+ $(tag)
+
+ - upload: manifests
+ artifact: manifests
+
+- stage: Deploy
+ displayName: Deploy stage
+ dependsOn: Build
+
+ jobs:
+ - deployment: Deploy
+ condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/pull/')))
+ displayName: Deploy
+ pool:
+ vmImage: $(vmImageName)
+ environment: 'devopsonazurevotedevops.devops'
+ strategy:
+ runOnce:
+ deploy:
+ steps:
+ - task: KubernetesManifest@0
+ displayName: Create imagePullSecret
+ inputs:
+ action: createSecret
+ secretName: $(imagePullSecret)
+ dockerRegistryEndpoint: $(dockerRegistryServiceConnection)
+
+ - task: KubernetesManifest@0
+ displayName: Deploy to Kubernetes cluster
+ inputs:
+ action: deploy
+ manifests: |
+ $(Pipeline.Workspace)/manifests/deployment.yml
+ $(Pipeline.Workspace)/manifests/service.yml
+ imagePullSecrets: |
+ $(imagePullSecret)
+ containers: |
+ $(containerRegistry)/$(imageRepository):$(tag)
+
+ - deployment: DeployPullRequest
+ displayName: Deploy Pull request
+ condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/pull/'))
+ pool:
+ vmImage: $(vmImageName)
+
+ environment: 'devopsonazurevotedevops.$(k8sNamespaceForPR)'
+ strategy:
+ runOnce:
+ deploy:
+ steps:
+ - reviewApp: devops
+
+ - task: Kubernetes@1
+ displayName: 'Create a new namespace for the pull request'
+ inputs:
+ command: apply
+ useConfigurationFile: true
+ inline: '{ "kind": "Namespace", "apiVersion": "v1", "metadata": { "name": "$(k8sNamespaceForPR)" }}'
+
+ - task: KubernetesManifest@0
+ displayName: Create imagePullSecret
+ inputs:
+ action: createSecret
+ secretName: $(imagePullSecret)
+ namespace: $(k8sNamespaceForPR)
+ dockerRegistryEndpoint: $(dockerRegistryServiceConnection)
+
+ - task: KubernetesManifest@0
+ displayName: Deploy to the new namespace in the Kubernetes cluster
+ inputs:
+ action: deploy
+ namespace: $(k8sNamespaceForPR)
+ manifests: |
+ $(Pipeline.Workspace)/manifests/deployment.yml
+ $(Pipeline.Workspace)/manifests/service.yml
+ imagePullSecrets: |
+ $(imagePullSecret)
+ containers: |
+ $(containerRegistry)/$(imageRepository):$(tag)
+
+ - task: Kubernetes@1
+ name: get
+ displayName: 'Get services in the new namespace'
+ continueOnError: true
+ inputs:
+ command: get
+ namespace: $(k8sNamespaceForPR)
+ arguments: svc
+ outputFormat: jsonpath='http://{.items[0].status.loadBalancer.ingress[0].ip}:{.items[0].spec.ports[0].port}'
+
+ # Getting the IP of the deployed service and writing it to a variable for posing comment
+ - script: |
+ url="$(get.KubectlOutput)"
+ message="Your review app has been deployed"
+ if [ ! -z "$url" -a "$url" != "http://:" ]
+ then
+ message="${message} and is available at $url.
[Learn More](https://aka.ms/testwithreviewapps) about how to test and provide feedback for the app."
+ fi
+ echo "##vso[task.setvariable variable=GITHUB_COMMENT]$message"
diff --git a/azure-vote/config_file.cfg b/azure-vote/config_file.cfg
new file mode 100644
index 0000000..07e9667
--- /dev/null
+++ b/azure-vote/config_file.cfg
@@ -0,0 +1,5 @@
+# UI Configurations
+TITLE = 'Azure Voting App - Azure Devops Pipeline'
+VOTE1VALUE = 'Cats'
+VOTE2VALUE = 'Dogs'
+SHOWHOST = 'false'
\ No newline at end of file
diff --git a/azure-vote/main.py b/azure-vote/main.py
new file mode 100644
index 0000000..e6198ce
--- /dev/null
+++ b/azure-vote/main.py
@@ -0,0 +1,88 @@
+from flask import Flask, request, render_template
+import os
+import random
+import redis
+import socket
+import sys
+
+app = Flask(__name__)
+
+# Load configurations from environment or config file
+app.config.from_pyfile('config_file.cfg')
+
+if ("VOTE1VALUE" in os.environ and os.environ['VOTE1VALUE']):
+ button1 = os.environ['VOTE1VALUE']
+else:
+ button1 = app.config['VOTE1VALUE']
+
+if ("VOTE2VALUE" in os.environ and os.environ['VOTE2VALUE']):
+ button2 = os.environ['VOTE2VALUE']
+else:
+ button2 = app.config['VOTE2VALUE']
+
+if ("TITLE" in os.environ and os.environ['TITLE']):
+ title = os.environ['TITLE']
+else:
+ title = app.config['TITLE']
+
+# Redis configurations
+redis_server = os.environ['REDIS']
+
+# Redis Connection
+try:
+ if "REDIS_PWD" in os.environ:
+ r = redis.StrictRedis(host=redis_server,
+ port=6379,
+ password=os.environ['REDIS_PWD'])
+ else:
+ r = redis.Redis(redis_server)
+ r.ping()
+except redis.ConnectionError:
+ exit('Failed to connect to Redis, terminating.')
+
+# Change title to host name to demo NLB
+if app.config['SHOWHOST'] == "true":
+ title = socket.gethostname()
+
+# 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()
diff --git a/azure-vote/static/default.css b/azure-vote/static/default.css
new file mode 100644
index 0000000..02f1cc5
--- /dev/null
+++ b/azure-vote/static/default.css
@@ -0,0 +1,96 @@
+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:Helvetica;
+ /*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:Helvetica;
+ 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:Helvetica;
+}
+
+.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
diff --git a/azure-vote/templates/index.html b/azure-vote/templates/index.html
new file mode 100644
index 0000000..603e110
--- /dev/null
+++ b/azure-vote/templates/index.html
@@ -0,0 +1,29 @@
+
+
+