@ -1,5 +1,3 @@
@@ -1,5 +1,3 @@
|
||||
.git |
||||
*.pyc |
||||
__pycache__ |
||||
infra/bicep |
||||
infra/*.json |
@ -0,0 +1,2 @@
@@ -0,0 +1,2 @@
|
||||
CR_PAT=__CHANGE_ME__ |
||||
AZURE_CREDENTIALS={"clientId": "", "clientSecret": "", "subscriptionId": "", "tenantId": ""} |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
# Act |
||||
|
||||
Act is an amazing command line local runner for GitHub Actions |
||||
https://github.com/nektos/act |
||||
|
||||
Install with |
||||
|
||||
```bash |
||||
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash |
||||
``` |
||||
|
||||
To run the workflows for this repo, example commands are given below |
||||
|
||||
The `.secrets` file must be created first, see the sample file for a reference. |
||||
|
||||
### Run CI |
||||
|
||||
```bash |
||||
act push --secret-file .github/.secrets --platform ubuntu-latest=ghcr.io/benc-uk/act-runner:python |
||||
``` |
||||
|
||||
### Run a deployment |
||||
|
||||
```bash |
||||
act workflow_dispatch --eventpath .github/workflow_dispatch.json --secret-file .github/.secrets --platform ubuntu-latest=ghcr.io/benc-uk/act-runner:python |
||||
``` |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
{ |
||||
"inputs": { |
||||
"IMAGE_TAG": "26-03-2021.1610" |
||||
} |
||||
} |
@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
|
||||
# Welcome |
||||
|
||||
Hello! Thanks for taking an interest in this project and code :) |
||||
|
||||
Contributions to this project are welcome of course, otherwise it wouldn't reside on GitHub 😃 however there's a few things to be aware of: |
||||
|
||||
- This is a personal project, it is not maintained by a team or group. |
||||
- It might take a long time for the maintainer(s) to reply to issues or review PRs, they will have have a day jobs & might not have looked at the code for a while. |
||||
- The code here is likely to not be bullet proof & production grade, there might be a lack of unit tests or other practices missing from the code base. |
||||
|
||||
# Contributing |
||||
|
||||
There's several ways of contributing to this project, and effort has been made to make this as easy and transparent as possible, whether it's: |
||||
|
||||
- Reporting a bug |
||||
- Discussing the current state of the code |
||||
- Submitting a fix |
||||
- Proposing new features |
||||
- Becoming a maintainer |
||||
|
||||
## All code changes happen though pull requests (PRs) |
||||
|
||||
Pull requests are the best way to propose changes to the codebase (using the standard [Github Flow](https://guides.github.com/introduction/flow/index.html)). |
||||
|
||||
Some PR guidance: |
||||
|
||||
- Please keep PRs small and focused on a single feature or change, with discreet commits. Use multiple PRs if need be. |
||||
- If you're thinking of adding a feature via a PR please create an issue first where it can be discussed. |
||||
|
||||
High level steps: |
||||
|
||||
1. Fork the repo and create your branch from `master` or `main`. |
||||
2. If you've changed APIs, update the documentation. |
||||
3. Ensure the test suite (if any) passes (run `make lint`). |
||||
4. Make sure your code lints (run `make lint`). |
||||
5. Issue that pull request! |
||||
|
||||
## Any contributions you make will be under the MIT Software License |
||||
|
||||
In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. |
||||
|
||||
## Report bugs using Github's issues |
||||
|
||||
This project uses GitHub issues to track public bugs. Report a bug by [opening a new issue](./issues/new/choose) |
||||
|
||||
## Write bug reports with detail, background, and sample code |
||||
|
||||
**Great Bug Reports** tend to have: |
||||
|
||||
- A quick summary and/or background |
||||
- Steps to reproduce |
||||
- Be specific! |
||||
- Give sample code if you can. Even if it's a snippet |
||||
- What you expected would happen |
||||
- What actually happens |
||||
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) |
||||
|
||||
## Use a consistent coding style |
||||
|
||||
Run `make lint-fix` in order to format the code fix any formatting & linting issues that might be present. A [Prettier](https://prettier.io/) configuration file is included |
||||
|
||||
# References |
||||
|
||||
This document was heavily adapted from the open-source contribution guidelines found in [this gist](https://gist.github.com/briandk/3d2e8b3ec8daf5a27a62) |
@ -1,13 +0,0 @@
@@ -1,13 +0,0 @@
|
||||
FROM python:3.8-buster |
||||
LABEL Name="Python Flask Demo App" Version=1.4.0 |
||||
|
||||
WORKDIR /demoapp |
||||
COPY requirements.txt . |
||||
RUN pip install --no-cache-dir -r requirements.txt |
||||
|
||||
COPY run.py . |
||||
COPY app ./app |
||||
|
||||
EXPOSE 5000 |
||||
|
||||
CMD ["gunicorn", "-b", "0.0.0.0:5000", "run:app"] |
@ -1,5 +0,0 @@
@@ -1,5 +0,0 @@
|
||||
from flask import Flask |
||||
|
||||
app = Flask(__name__) |
||||
|
||||
from app import views |
@ -1,53 +0,0 @@
@@ -1,53 +0,0 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
|
||||
<head> |
||||
<title>{% block title %}Python DemoApp{% endblock %}</title> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
<link href="/static/css/main.css" rel="stylesheet" /> |
||||
<link href="/static/img/favicon.ico" rel="icon" /> |
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/united/bootstrap.min.css" |
||||
integrity="sha384-JW3PJkbqVWtBhuV/gsuyVVt3m/ecRJjwXC3gCXlTzZZV+zIEEl6AnryAriT7GWYm" crossorigin="anonymous"> |
||||
</head> |
||||
|
||||
<body> |
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> |
||||
<a class="navbar-brand logotext" href="/"> |
||||
<img src="static/img/python.svg" width="37" height="40"> |
||||
Python Demo |
||||
</a> |
||||
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" |
||||
aria-controls="navbarNav" aria-expanded="true" aria-label="Toggle navigation"> |
||||
<span class="navbar-toggler-icon"></span> |
||||
</button> |
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav"> |
||||
<ul class="navbar-nav mr-auto"> |
||||
<li class="nav-item active"> |
||||
<a class="btn btn-success btn-lg" href="/info">🧾 Info</a> |
||||
</li> |
||||
|
||||
<li class="nav-item active"> |
||||
<a class="btn btn-success btn-lg" href="/monitor">🚦 Monitor</a> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</nav> |
||||
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.min.js" |
||||
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script> |
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" |
||||
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" |
||||
crossorigin="anonymous"></script> |
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" |
||||
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" |
||||
crossorigin="anonymous"></script> |
||||
|
||||
<div class="container body-content">{% block content %}{% endblock %}</div> |
||||
<span style="float:right">v1.4.0 [Ben Coleman, 2018-2020] </span> |
||||
</body> |
||||
|
||||
</html> |
@ -1,41 +0,0 @@
@@ -1,41 +0,0 @@
|
||||
{% extends "base.html" %} |
||||
|
||||
{% block content %} |
||||
<br> |
||||
<div class="jumbotron"> |
||||
<h1><img src="/static/img/flask.png" height="80px" /> Python & Flask Demo App</h1> |
||||
|
||||
<div class="lead"> |
||||
This is a simple web application written in Python and using Flask. It has been designed with cloud demos & |
||||
containers in mind. Demonstrating capabilities such as auto scaling, deployment to Azure or Kubernetes, or anytime |
||||
you want something quick and lightweight to run & deploy. |
||||
</div> |
||||
<br> |
||||
|
||||
<div class="dimmed-box"> |
||||
<p> |
||||
<img src="static/img/github-2.svg" class="icon"> |
||||
<a href="https://github.com/benc-uk/python-demoapp" class="btn btn-info btn px-4"> |
||||
GitHub Project |
||||
</a> |
||||
|
||||
|
||||
<img src="static/img/docker-whale.svg" class="icon"> |
||||
<a href="https://github.com/users/benc-uk/packages/container/package/python-demoapp" class="btn btn-info btn"> |
||||
Docker Images |
||||
</a> |
||||
</p> |
||||
<hr> |
||||
<p> |
||||
<img src="static/img/python.svg" class="icon"> |
||||
<a class="btn btn-info btn" href="https://azure.microsoft.com/en-gb/develop/python/"> |
||||
Get started with Azure & Python |
||||
</a> |
||||
</p> |
||||
|
||||
<br> |
||||
<p>Microsoft ❤ Open Source</p> |
||||
</div> |
||||
|
||||
</div> |
||||
{% endblock %} |
@ -1,49 +0,0 @@
@@ -1,49 +0,0 @@
|
||||
{% extends "base.html" %} |
||||
|
||||
{% block content %} |
||||
|
||||
<div class=""> |
||||
<h1>🛠 System Information</h1> |
||||
<table class="table table-striped table-hover" style="font-size:1.5em"> |
||||
<tbody> |
||||
<tr> |
||||
<td><b>Hostname</b></td> |
||||
<td>{{ info.plat.node() }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td><b>Boot Time</b></td> |
||||
<td>{{ info.boottime }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td><b>OS Platform</b></td> |
||||
<td>{{ info.plat.system() }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td><b>OS Version</b></td> |
||||
<td>{{ info.plat.version() }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td><b>Python Version</b></td> |
||||
<td>{{ info.plat.python_version() }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td><b>Processor & Cores</b></td> |
||||
<td>{{ info.cpu.count }} x {{ info.cpu.brand }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td><b>System Memory</b></td> |
||||
<td>{{ (info.mem.total / (1024*1024*1024)) | round(0,'ceil') |int }}GB ({{info.mem.percent}}% used)</td> |
||||
</tr> |
||||
<tr> |
||||
<td><b>Network Interfaces</b></td> |
||||
<td> |
||||
{% for iface, snics in info.net.items() %} {% for snic in snics if (snic.family == 2) %} |
||||
<li>{{ iface }} - <b>{{ snic.address }}</b> </li> |
||||
{% endfor %} {% endfor %} |
||||
</td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
|
||||
{% endblock %} |
@ -1,39 +0,0 @@
@@ -1,39 +0,0 @@
|
||||
{% extends "base.html" %} |
||||
|
||||
{% block content %} |
||||
|
||||
<script src="/static/js/sorttable.js"></script> |
||||
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> |
||||
<script src="/static/js/monitor.js"></script> |
||||
<script type="text/javascript"> |
||||
google.charts.load('current', { 'packages': ['gauge'] }); |
||||
google.charts.setOnLoadCallback(initCharts); |
||||
</script> |
||||
|
||||
<body> |
||||
<span style='float:right'> |
||||
Refresh Rate: <span id="refrate"></span> secs<input id="refslider" type="range" min="2" max="15" /> |
||||
</span> |
||||
<h2>Running Processes (<span id="proc_count"></span>)</h2> |
||||
<div style="height: 200px; overflow: scroll;"> |
||||
<table class="sortable"> |
||||
<thead> |
||||
<tr> |
||||
<th>PID</th> |
||||
<th>Name</th> |
||||
<th>Mem</th> |
||||
<th>CPU Time</th> |
||||
<th>Threads</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody id="process_tab"> |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
<h2>Performance Monitor</h2> |
||||
<div id="chart1" class="gauges"></div> |
||||
<div id="chart2" class="gauges"></div> |
||||
<div id="chart3" class="gauges"></div> |
||||
</body> |
||||
|
||||
{% endblock %} |
@ -1,77 +0,0 @@
@@ -1,77 +0,0 @@
|
||||
from flask import url_for, redirect, render_template, flash, g, session, jsonify |
||||
from app import app |
||||
import cpuinfo |
||||
import psutil |
||||
import platform |
||||
import datetime |
||||
|
||||
@app.route('/') |
||||
def index(): |
||||
return render_template('index.html') |
||||
|
||||
@app.route('/info') |
||||
def info(): |
||||
osinfo = {} |
||||
osinfo['plat'] = platform |
||||
osinfo['cpu'] = cpuinfo.get_cpu_info() |
||||
osinfo['mem'] = psutil.virtual_memory() |
||||
osinfo['net'] = psutil.net_if_addrs() |
||||
osinfo['boottime'] = datetime.datetime.fromtimestamp(psutil.boot_time()).strftime("%Y-%m-%d %H:%M:%S") |
||||
|
||||
return render_template('info.html', info = osinfo) |
||||
|
||||
@app.route('/monitor') |
||||
def monitor(): |
||||
return render_template('monitor.html') |
||||
|
||||
olddata = {} |
||||
olddata['disk_write'] = 0 |
||||
olddata['disk_read'] = 0 |
||||
olddata['net_sent'] = 0 |
||||
olddata['net_recv'] = 0 |
||||
@app.route('/api/monitor') |
||||
def api_monitor(): |
||||
apidata = {} |
||||
apidata['cpu'] = psutil.cpu_percent(interval=0.9) |
||||
apidata['mem'] = psutil.virtual_memory().percent |
||||
apidata['disk'] = psutil.disk_usage('/').percent |
||||
|
||||
try: |
||||
netio = psutil.net_io_counters() |
||||
apidata['net_sent'] = 0 if olddata['net_sent'] == 0 else netio.bytes_sent - olddata['net_sent'] |
||||
olddata['net_sent'] = netio.bytes_sent |
||||
apidata['net_recv'] = 0 if olddata['net_recv'] == 0 else netio.bytes_recv - olddata['net_recv'] |
||||
olddata['net_recv'] = netio.bytes_recv |
||||
except: |
||||
apidata['net_sent'] = -1 |
||||
apidata['net_recv'] = -1 |
||||
|
||||
try: |
||||
diskio = psutil.disk_io_counters() |
||||
apidata['disk_write'] = 0 if olddata['disk_write'] == 0 else diskio.write_bytes - olddata['disk_write'] |
||||
olddata['disk_write'] = diskio.write_bytes |
||||
apidata['disk_read'] = 0 if olddata['disk_read'] == 0 else diskio.read_bytes - olddata['disk_read'] |
||||
olddata['disk_read'] = diskio.read_bytes |
||||
except: |
||||
apidata['disk_write'] = -1 |
||||
apidata['disk_read'] = -1 |
||||
|
||||
return jsonify(apidata) |
||||
|
||||
@app.route('/api/process') |
||||
def api_process(): |
||||
apidata = {} |
||||
try: |
||||
apidata['processes'] = [] |
||||
for proc in psutil.process_iter(): |
||||
try: |
||||
#pinfo = proc.as_dict(attrs=['pid', 'name', 'num_handles', 'num_threads', 'memory_percent', 'cpu_times']) |
||||
pinfo = proc.as_dict(attrs=['pid', 'name', 'memory_percent', 'num_threads', 'cpu_times']) |
||||
except psutil.NoSuchProcess: |
||||
pass |
||||
else: |
||||
apidata['processes'].append(pinfo) |
||||
except: |
||||
pass |
||||
|
||||
return jsonify(apidata) |
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
FROM python:3.9-slim-buster |
||||
|
||||
LABEL Name="Python Flask Demo App" Version=1.4.1 |
||||
LABEL org.opencontainers.image.source = "https://github.com/benc-uk/python-demoapp" |
||||
|
||||
ARG srcDir=src |
||||
WORKDIR /app |
||||
COPY $srcDir/requirements.txt . |
||||
RUN pip install --no-cache-dir -r requirements.txt |
||||
|
||||
COPY $srcDir/run.py . |
||||
COPY $srcDir/app ./app |
||||
|
||||
EXPOSE 5000 |
||||
|
||||
CMD ["gunicorn", "-b", "0.0.0.0:5000", "run:app"] |
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
# Used by `image`, `push` & `deploy` targets, override as required
|
||||
IMAGE_REG ?= ghcr.io
|
||||
IMAGE_REPO ?= benc-uk/python-demoapp
|
||||
IMAGE_TAG ?= latest
|
||||
|
||||
# Used by `deploy` target, sets Azure webap defaults, override as required
|
||||
AZURE_RES_GROUP ?= temp-demoapps
|
||||
AZURE_REGION ?= uksouth
|
||||
AZURE_SITE_NAME ?= pythonapp-$(shell git rev-parse --short HEAD)
|
||||
|
||||
# Used by `test-api` target
|
||||
TEST_HOST ?= localhost:5000
|
||||
|
||||
# Don't change
|
||||
SRC_DIR := src
|
||||
|
||||
.PHONY: help lint lint-fix image push run deploy undeploy clean test-api .EXPORT_ALL_VARIABLES |
||||
.DEFAULT_GOAL := help
|
||||
|
||||
help: ## 💬 This help message
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
lint: venv ## 🔎 Lint & format, will not fix but sets exit code on error
|
||||
. $(SRC_DIR)/.venv/bin/activate \
|
||||
&& black --check $(SRC_DIR) \
|
||||
&& flake8 src/app/ && flake8 src/run.py
|
||||
|
||||
lint-fix: venv ## 📜 Lint & format, will try to fix errors and modify code
|
||||
. $(SRC_DIR)/.venv/bin/activate \
|
||||
&& black $(SRC_DIR)
|
||||
|
||||
image: ## 🔨 Build container image from Dockerfile
|
||||
docker build . --file build/Dockerfile \
|
||||
--tag $(IMAGE_REG)/$(IMAGE_REPO):$(IMAGE_TAG)
|
||||
|
||||
push: ## 📤 Push container image to registry
|
||||
docker push $(IMAGE_REG)/$(IMAGE_REPO):$(IMAGE_TAG)
|
||||
|
||||
run: venv ## 🏃 Run the server locally using Python & Flask
|
||||
. $(SRC_DIR)/.venv/bin/activate \
|
||||
&& python src/run.py
|
||||
|
||||
deploy: ## 🚀 Deploy to Azure Web App
|
||||
az group create --resource-group $(AZURE_RES_GROUP) --location $(AZURE_REGION) -o table
|
||||
az deployment group create --template-file deploy/webapp.bicep \
|
||||
--resource-group $(AZURE_RES_GROUP) \
|
||||
--parameters webappName=$(AZURE_SITE_NAME) \
|
||||
--parameters webappImage=$(IMAGE_REG)/$(IMAGE_REPO):$(IMAGE_TAG) -o table
|
||||
@echo "### 🚀 Web app deployed to https://$(AZURE_SITE_NAME).azurewebsites.net/"
|
||||
|
||||
undeploy: ## 💀 Remove from Azure
|
||||
@echo "### WARNING! Going to delete $(DEPLOY_RES_GROUP) 😲"
|
||||
az group delete -n $(DEPLOY_RES_GROUP) -o table --no-wait
|
||||
|
||||
test: ## 🎯 Unit tests for server and frontend
|
||||
cd $(SRC_DIR); go test -v | tee server_tests.txt
|
||||
cd $(SPA_DIR); npm run test
|
||||
|
||||
test-report: test ## 🎯 Unit tests for server and frontend (with report output)
|
||||
|
||||
test-api: .EXPORT_ALL_VARIABLES ## 🚦 Run integration API tests, server must be running
|
||||
cd tests \
|
||||
&& npm install newman \
|
||||
&& ./node_modules/.bin/newman run ./postman_collection.json --env-var apphost=$(TEST_HOST)
|
||||
|
||||
clean: ## 🧹 Clean up project
|
||||
rm -rf $(SRC_DIR)/.venv
|
||||
rm -rf tests/node_modules
|
||||
rm -rf tests/package*
|
||||
|
||||
# ============================================================================
|
||||
|
||||
venv: $(SRC_DIR)/.venv/touchfile |
||||
|
||||
$(SRC_DIR)/.venv/touchfile: $(SRC_DIR)/requirements.txt |
||||
python3 -m venv $(SRC_DIR)/.venv
|
||||
. $(SRC_DIR)/.venv/bin/activate; pip install -Ur $(SRC_DIR)/requirements.txt
|
||||
touch $(SRC_DIR)/.venv/touchfile
|
@ -1,4 +0,0 @@
@@ -1,4 +0,0 @@
|
||||
Flask |
||||
py-cpuinfo |
||||
psutil |
||||
gunicorn |
@ -1,8 +0,0 @@
@@ -1,8 +0,0 @@
|
||||
import os |
||||
from app import app |
||||
|
||||
if __name__ == "__main__": |
||||
port = int(os.environ.get("PORT", 5000)) |
||||
app.jinja_env.auto_reload = True |
||||
app.config['TEMPLATES_AUTO_RELOAD'] = True |
||||
app.run(host='0.0.0.0', port=port) |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
from flask import Flask |
||||
|
||||
app = Flask(__name__) |
||||
|
||||
from app import views # noqa: E402,F401 |
||||
from app import apis # noqa: E402,F401 |
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
from flask import jsonify |
||||
from app import app |
||||
import psutil |
||||
|
||||
olddata = {} |
||||
olddata["disk_write"] = 0 |
||||
olddata["disk_read"] = 0 |
||||
olddata["net_sent"] = 0 |
||||
olddata["net_recv"] = 0 |
||||
|
||||
|
||||
@app.route("/api/process") |
||||
def api_process(): |
||||
apidata = {} |
||||
try: |
||||
apidata["processes"] = [] |
||||
for proc in psutil.process_iter(): |
||||
try: |
||||
# pinfo = proc.as_dict(attrs=['pid', 'name', 'num_handles', 'num_threads', 'memory_percent', 'cpu_times']) |
||||
pinfo = proc.as_dict( |
||||
attrs=["pid", "name", "memory_percent", "num_threads", "cpu_times"] |
||||
) |
||||
except psutil.NoSuchProcess: |
||||
pass |
||||
else: |
||||
apidata["processes"].append(pinfo) |
||||
except Exception: |
||||
pass |
||||
|
||||
return jsonify(apidata) |
||||
|
||||
|
||||
@app.route("/api/monitor") |
||||
def api_monitor(): |
||||
apidata = {} |
||||
apidata["cpu"] = psutil.cpu_percent(interval=0.9) |
||||
apidata["mem"] = psutil.virtual_memory().percent |
||||
apidata["disk"] = psutil.disk_usage("/").percent |
||||
|
||||
try: |
||||
netio = psutil.net_io_counters() |
||||
apidata["net_sent"] = ( |
||||
0 if olddata["net_sent"] == 0 else netio.bytes_sent - olddata["net_sent"] |
||||
) |
||||
olddata["net_sent"] = netio.bytes_sent |
||||
apidata["net_recv"] = ( |
||||
0 if olddata["net_recv"] == 0 else netio.bytes_recv - olddata["net_recv"] |
||||
) |
||||
olddata["net_recv"] = netio.bytes_recv |
||||
except Exception: |
||||
apidata["net_sent"] = -1 |
||||
apidata["net_recv"] = -1 |
||||
|
||||
try: |
||||
diskio = psutil.disk_io_counters() |
||||
apidata["disk_write"] = ( |
||||
0 |
||||
if olddata["disk_write"] == 0 |
||||
else diskio.write_bytes - olddata["disk_write"] |
||||
) |
||||
olddata["disk_write"] = diskio.write_bytes |
||||
apidata["disk_read"] = ( |
||||
0 if olddata["disk_read"] == 0 else diskio.read_bytes - olddata["disk_read"] |
||||
) |
||||
olddata["disk_read"] = diskio.read_bytes |
||||
except Exception: |
||||
apidata["disk_write"] = -1 |
||||
apidata["disk_read"] = -1 |
||||
|
||||
return jsonify(apidata) |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 163 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 979 B After Width: | Height: | Size: 979 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<title>{% block title %}Python DemoApp{% endblock %}</title> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
<link href="/static/css/main.css" rel="stylesheet" /> |
||||
<link href="/static/img/favicon.ico" rel="icon" /> |
||||
<link |
||||
rel="stylesheet" |
||||
href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/united/bootstrap.min.css" |
||||
integrity="sha384-JW3PJkbqVWtBhuV/gsuyVVt3m/ecRJjwXC3gCXlTzZZV+zIEEl6AnryAriT7GWYm" |
||||
crossorigin="anonymous" |
||||
/> |
||||
</head> |
||||
|
||||
<body> |
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> |
||||
<a class="navbar-brand logotext" href="/"> |
||||
<img src="static/img/python.svg" width="37" height="40" /> |
||||
Python Demo |
||||
</a> |
||||
|
||||
<button |
||||
class="navbar-toggler" |
||||
type="button" |
||||
data-toggle="collapse" |
||||
data-target="#navbarNav" |
||||
aria-controls="navbarNav" |
||||
aria-expanded="true" |
||||
aria-label="Toggle navigation" |
||||
> |
||||
<span class="navbar-toggler-icon"></span> |
||||
</button> |
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav"> |
||||
<ul class="navbar-nav mr-auto"> |
||||
<li class="nav-item active"> |
||||
<a class="btn btn-success btn-lg" href="/info">🧾 Info</a> |
||||
</li> |
||||
|
||||
<li class="nav-item active"> |
||||
<a class="btn btn-success btn-lg" href="/monitor">🚦 Monitor</a> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</nav> |
||||
|
||||
<script |
||||
src="https://code.jquery.com/jquery-3.5.1.min.js" |
||||
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" |
||||
crossorigin="anonymous" |
||||
></script> |
||||
|
||||
<script |
||||
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" |
||||
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" |
||||
crossorigin="anonymous" |
||||
></script> |
||||
<script |
||||
src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" |
||||
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" |
||||
crossorigin="anonymous" |
||||
></script> |
||||
|
||||
<div class="container body-content">{% block content %}{% endblock %}</div> |
||||
<span style="float: right" |
||||
>v1.4.1 [Ben Coleman, 2018-2021] </span |
||||
> |
||||
</body> |
||||
</html> |
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
{% extends "base.html" %} {% block content %} |
||||
<br /> |
||||
<div class="jumbotron"> |
||||
<h1> |
||||
<img src="/static/img/flask.png" height="80px" /> Python & Flask Demo App |
||||
</h1> |
||||
|
||||
<div class="lead"> |
||||
This is a simple web application written in Python and using Flask. It has |
||||
been designed with cloud demos & containers in mind. Demonstrating |
||||
capabilities such as auto scaling, deployment to Azure or Kubernetes, or |
||||
anytime you want something quick and lightweight to run & deploy. |
||||
</div> |
||||
<br /> |
||||
|
||||
<div class="dimmed-box"> |
||||
<p> |
||||
<img src="static/img/github-2.svg" class="icon" /> |
||||
<a |
||||
href="https://github.com/benc-uk/python-demoapp" |
||||
class="btn btn-info btn px-4" |
||||
> |
||||
GitHub Project |
||||
</a> |
||||
|
||||
|
||||
<img src="static/img/docker-whale.svg" class="icon" /> |
||||
<a |
||||
href="https://github.com/users/benc-uk/packages/container/package/python-demoapp" |
||||
class="btn btn-info btn" |
||||
> |
||||
Docker Images |
||||
</a> |
||||
</p> |
||||
<hr /> |
||||
<p> |
||||
<img src="static/img/python.svg" class="icon" /> |
||||
<a |
||||
class="btn btn-info btn" |
||||
href="https://azure.microsoft.com/en-gb/develop/python/" |
||||
> |
||||
Get started with Azure & Python |
||||
</a> |
||||
</p> |
||||
|
||||
<br /> |
||||
<p>Microsoft ❤ Open Source</p> |
||||
</div> |
||||
</div> |
||||
{% endblock %} |
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
{% extends "base.html" %} {% block content %} |
||||
|
||||
<div class=""> |
||||
<h1>🛠 System Information</h1> |
||||
<table class="table table-striped table-hover" style="font-size: 1.5em"> |
||||
<tbody> |
||||
<tr> |
||||
<td><b>Hostname</b></td> |
||||
<td>{{ info.plat.node() }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td><b>Boot Time</b></td> |
||||
<td>{{ info.boottime }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td><b>OS Platform</b></td> |
||||
<td>{{ info.plat.system() }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td><b>OS Version</b></td> |
||||
<td>{{ info.plat.version() }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td><b>Python Version</b></td> |
||||
<td>{{ info.plat.python_version() }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td><b>Processor & Cores</b></td> |
||||
<td>{{ info.cpu.count }} x {{ info.cpu.brand }}</td> |
||||
</tr> |
||||
<tr> |
||||
<td><b>System Memory</b></td> |
||||
<td> |
||||
{{ (info.mem.total / (1024*1024*1024)) | round(0,'ceil') |int }}GB |
||||
({{info.mem.percent}}% used) |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td><b>Network Interfaces</b></td> |
||||
<td> |
||||
{% for iface, snics in info.net.items() %} {% for snic in snics if |
||||
(snic.family == 2) %} |
||||
<li>{{ iface }} - <b>{{ snic.address }}</b></li> |
||||
{% endfor %} {% endfor %} |
||||
</td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
|
||||
{% endblock %} |
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
{% extends "base.html" %} {% block content %} |
||||
|
||||
<script src="/static/js/sorttable.js"></script> |
||||
<script |
||||
type="text/javascript" |
||||
src="https://www.gstatic.com/charts/loader.js" |
||||
></script> |
||||
<script src="/static/js/monitor.js"></script> |
||||
<script type="text/javascript"> |
||||
google.charts.load("current", { packages: ["gauge"] }); |
||||
google.charts.setOnLoadCallback(initCharts); |
||||
</script> |
||||
|
||||
<body> |
||||
<span style="float: right"> |
||||
Refresh Rate: <span id="refrate"></span> secs<input |
||||
id="refslider" |
||||
type="range" |
||||
min="2" |
||||
max="15" |
||||
/> |
||||
</span> |
||||
<br /> |
||||
<h2>👓 Running Processes (<span id="proc_count"></span>)</h2> |
||||
<div style="height: 200px; overflow: scroll"> |
||||
<table class="sortable"> |
||||
<thead> |
||||
<tr> |
||||
<th>PID</th> |
||||
<th>Name</th> |
||||
<th>Mem</th> |
||||
<th>CPU Time</th> |
||||
<th>Threads</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody id="process_tab"></tbody> |
||||
</table> |
||||
</div> |
||||
|
||||
<hr /> |
||||
|
||||
<h2>🌡 Performance Monitor</h2> |
||||
<div id="chart1" class="gauges"></div> |
||||
<div id="chart2" class="gauges"></div> |
||||
<div id="chart3" class="gauges"></div> |
||||
</body> |
||||
|
||||
{% endblock %} |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
from flask import render_template |
||||
from app import app |
||||
import cpuinfo |
||||
import psutil |
||||
import platform |
||||
import datetime |
||||
|
||||
|
||||
@app.route("/") |
||||
def index(): |
||||
return render_template("index.html") |
||||
|
||||
|
||||
@app.route("/info") |
||||
def info(): |
||||
osinfo = {} |
||||
osinfo["plat"] = platform |
||||
osinfo["cpu"] = cpuinfo.get_cpu_info() |
||||
osinfo["mem"] = psutil.virtual_memory() |
||||
osinfo["net"] = psutil.net_if_addrs() |
||||
osinfo["boottime"] = datetime.datetime.fromtimestamp(psutil.boot_time()).strftime( |
||||
"%Y-%m-%d %H:%M:%S" |
||||
) |
||||
|
||||
return render_template("info.html", info=osinfo) |
||||
|
||||
|
||||
@app.route("/monitor") |
||||
def monitor(): |
||||
return render_template("monitor.html") |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
Flask==1.1.2 |
||||
py-cpuinfo==7.0.0 |
||||
psutil==5.8.0 |
||||
gunicorn==20.0.4 |
||||
black==20.8b1 |
||||
flake8==3.9.0 |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
import os |
||||
from app import app |
||||
|
||||
if __name__ == "__main__": |
||||
port = int(os.environ.get("PORT", 5000)) |
||||
app.jinja_env.auto_reload = True |
||||
app.config["TEMPLATES_AUTO_RELOAD"] = True |
||||
app.run(host="0.0.0.0", port=port) |
@ -0,0 +1,157 @@
@@ -0,0 +1,157 @@
|
||||
{ |
||||
"info": { |
||||
"_postman_id": "d758a295-d6e7-40ac-a6ba-d9312cf9bbe5", |
||||
"name": "Python Demoapp", |
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" |
||||
}, |
||||
"item": [ |
||||
{ |
||||
"name": "Check Home Page", |
||||
"event": [ |
||||
{ |
||||
"listen": "test", |
||||
"script": { |
||||
"exec": [ |
||||
"pm.test(\"Home Page: Successful GET request\", function () {", |
||||
" pm.response.to.be.ok;", |
||||
"});", |
||||
"", |
||||
"pm.test(\"Home Page: Response valid & HTML body\", function () {", |
||||
" pm.response.to.be.withBody;", |
||||
" pm.expect(pm.response.headers.get('Content-Type')).to.contain('text/html');", |
||||
"});", |
||||
"", |
||||
"pm.test(\"Home Page: Check content\", function () {", |
||||
" pm.expect(pm.response.text()).to.include('Python');", |
||||
"});", |
||||
"" |
||||
], |
||||
"type": "text/javascript" |
||||
} |
||||
} |
||||
], |
||||
"request": { |
||||
"method": "GET", |
||||
"header": [], |
||||
"url": { |
||||
"raw": "http://{{apphost}}/", |
||||
"protocol": "http", |
||||
"host": [ |
||||
"{{apphost}}" |
||||
], |
||||
"path": [ |
||||
"" |
||||
] |
||||
} |
||||
}, |
||||
"response": [] |
||||
}, |
||||
{ |
||||
"name": "Check Info Page", |
||||
"event": [ |
||||
{ |
||||
"listen": "test", |
||||
"script": { |
||||
"exec": [ |
||||
"pm.test(\"Info Page: Successful GET request\", function () {", |
||||
" pm.response.to.be.ok;", |
||||
"});", |
||||
"", |
||||
"pm.test(\"Info Page: Response valid & HTML body\", function () {", |
||||
" pm.response.to.be.withBody;", |
||||
" pm.expect(pm.response.headers.get('Content-Type')).to.contain('text/html');", |
||||
"});", |
||||
"", |
||||
"pm.test(\"Info Page: Check content\", function () {", |
||||
" pm.expect(pm.response.text()).to.include('Network Interfaces');", |
||||
"});", |
||||
"" |
||||
], |
||||
"type": "text/javascript" |
||||
} |
||||
} |
||||
], |
||||
"request": { |
||||
"method": "GET", |
||||
"header": [], |
||||
"url": { |
||||
"raw": "http://{{apphost}}/info", |
||||
"protocol": "http", |
||||
"host": [ |
||||
"{{apphost}}" |
||||
], |
||||
"path": [ |
||||
"info" |
||||
] |
||||
} |
||||
}, |
||||
"response": [] |
||||
}, |
||||
{ |
||||
"name": "Check Process API", |
||||
"event": [ |
||||
{ |
||||
"listen": "test", |
||||
"script": { |
||||
"exec": [ |
||||
"pm.test(\"Process API: Successful GET request\", function () {", |
||||
" pm.response.to.be.ok;", |
||||
"});", |
||||
"", |
||||
"pm.test(\"Process API: Response valid & JSON body\", function () {", |
||||
" pm.response.to.be.withBody;", |
||||
" pm.response.to.be.json;", |
||||
"});", |
||||
"", |
||||
"pm.test(\"Process API: Check API response\", function () {", |
||||
" var processData = pm.response.json();", |
||||
" pm.expect(processData.processes).to.be.an('array')", |
||||
" pm.expect(processData.processes[0].name).to.be.an('string')", |
||||
" pm.expect(processData.processes[0].memory_percent).to.be.an('number')", |
||||
" pm.expect(processData.processes[0].pid).to.be.an('number')", |
||||
"});", |
||||
"" |
||||
], |
||||
"type": "text/javascript" |
||||
} |
||||
} |
||||
], |
||||
"request": { |
||||
"method": "GET", |
||||
"header": [], |
||||
"url": { |
||||
"raw": "https://{{apphost}}/api/process", |
||||
"protocol": "https", |
||||
"host": [ |
||||
"{{apphost}}" |
||||
], |
||||
"path": [ |
||||
"api", |
||||
"process" |
||||
] |
||||
} |
||||
}, |
||||
"response": [] |
||||
} |
||||
], |
||||
"event": [ |
||||
{ |
||||
"listen": "prerequest", |
||||
"script": { |
||||
"type": "text/javascript", |
||||
"exec": [ |
||||
"" |
||||
] |
||||
} |
||||
}, |
||||
{ |
||||
"listen": "test", |
||||
"script": { |
||||
"type": "text/javascript", |
||||
"exec": [ |
||||
"" |
||||
] |
||||
} |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,107 @@
@@ -0,0 +1,107 @@
|
||||
# |
||||
# Deploy to Azure Kubernetes Service using Helm |
||||
# Using Bicep for infrastructure as code |
||||
# |
||||
|
||||
name: CD Release - AKS |
||||
|
||||
on: |
||||
workflow_dispatch: |
||||
inputs: |
||||
IMAGE_TAG: |
||||
description: "Image tag to be deployed" |
||||
required: true |
||||
default: "latest" |
||||
|
||||
# Note. Required secrets: CR_PAT & AZURE_CREDENTIALS |
||||
|
||||
env: |
||||
AKS_NAME: benc |
||||
AKS_RES_GROUP: aks |
||||
HELM_RELEASE: python |
||||
HELM_NAMESPACE: demoapps |
||||
INGRESS_DNS_HOST: python-demoapp.kube.benco.io |
||||
|
||||
jobs: |
||||
# |
||||
# Deploy to Kubernetes (AKS) |
||||
# |
||||
deploy-aks: |
||||
name: Deploy to AKS with Helm |
||||
runs-on: ubuntu-latest |
||||
outputs: |
||||
deployment_id: ${{ steps.deploy.outputs.deployment_id }} |
||||
|
||||
steps: |
||||
- name: "Checkout" |
||||
uses: actions/checkout@v1 |
||||
|
||||
- name: "Start deployment" |
||||
id: deploy |
||||
uses: chrnorm/deployment-action@v1.2.0 |
||||
with: |
||||
ref: ${{ github.event.ref }} |
||||
token: ${{ github.token }} |
||||
environment: AKS - ${{ env.HELM_RELEASE }} |
||||
|
||||
- name: "Login to Azure" |
||||
uses: azure/login@v1 |
||||
with: |
||||
creds: ${{ secrets.AZURE_CREDENTIALS }} |
||||
|
||||
- name: "Get AKS credentials" |
||||
run: | |
||||
az aks get-credentials -n $AKS_NAME -g $AKS_RES_GROUP |
||||
|
||||
- name: "Helm release" |
||||
run: | |
||||
helm repo add benc-uk https://benc-uk.github.io/helm-charts |
||||
helm upgrade ${{ env.HELM_RELEASE }} benc-uk/webapp \ |
||||
--install \ |
||||
--namespace ${{ env.HELM_NAMESPACE }} \ |
||||
--values ./kubernetes/aks-live.yaml \ |
||||
--set image.tag=${{ github.event.inputs.IMAGE_TAG }},ingress.host=${{ env.INGRESS_DNS_HOST }} |
||||
|
||||
- name: "End deployment - failure" |
||||
if: ${{ failure() }} |
||||
uses: chrnorm/deployment-status@v1.0.0 |
||||
with: |
||||
token: ${{ github.token }} |
||||
state: failure |
||||
deployment_id: ${{ needs.deploy-bicep.outputs.deployment_id }} |
||||
|
||||
# |
||||
# Post deployment testing stage |
||||
# |
||||
validate-deployment: |
||||
name: "Run Deployment Tests" |
||||
needs: deploy-aks |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- name: "Checkout" |
||||
uses: actions/checkout@v2 |
||||
|
||||
- name: "Validate site is running" |
||||
run: .github/scripts/url-check.sh -u https://${{ env.INGRESS_DNS_HOST }} -s "Python" -t 200 |
||||
|
||||
# - name: "Run API tests" |
||||
# run: | |
||||
# npm install newman --silent |
||||
# node_modules/newman/bin/newman.js run src/tests/postman_collection.json --global-var apphost=${{ env.INGRESS_DNS_HOST }} |
||||
|
||||
- name: "End deployment - success" |
||||
if: ${{ success() }} |
||||
uses: chrnorm/deployment-status@v1.0.0 |
||||
with: |
||||
token: ${{ github.token }} |
||||
state: success |
||||
deployment_id: ${{ needs.deploy-aks.outputs.deployment_id }} |
||||
environment_url: https://${{ env.INGRESS_DNS_HOST }} |
||||
|
||||
- name: "End deployment - failure" |
||||
if: ${{ failure() }} |
||||
uses: chrnorm/deployment-status@v1.0.0 |
||||
with: |
||||
token: ${{ github.token }} |
||||
state: failure |
||||
deployment_id: ${{ needs.deploy-aks.outputs.deployment_id }} |
@ -0,0 +1,114 @@
@@ -0,0 +1,114 @@
|
||||
# |
||||
# Deploy to Azure App Service as a containerized Web App |
||||
# Using Bicep for infrastructure as code |
||||
# |
||||
|
||||
name: CD Release - Webapp |
||||
|
||||
on: |
||||
workflow_dispatch: |
||||
inputs: |
||||
IMAGE_TAG: |
||||
description: "Image tag to be deployed" |
||||
required: true |
||||
default: "latest" |
||||
|
||||
# Note. Required secrets: CR_PAT & AZURE_CREDENTIALS |
||||
|
||||
env: |
||||
IMAGE_REG: ghcr.io |
||||
IMAGE_REPO: benc-uk/python-demoapp |
||||
APP_NAME: python-demoapp |
||||
ARM_SUB_ID: 52512f28-c6ed-403e-9569-82a9fb9fec91 |
||||
ARM_REGION: westeurope |
||||
ARM_RES_GROUP: apps |
||||
|
||||
jobs: |
||||
# |
||||
# Deploy Azure infra (App Service) using Bicep |
||||
# |
||||
deploy-infra: |
||||
name: "Deploy Infra" |
||||
runs-on: ubuntu-latest |
||||
outputs: |
||||
deployment_id: ${{ steps.deploy.outputs.deployment_id }} |
||||
|
||||
steps: |
||||
- name: "Checkout" |
||||
uses: actions/checkout@v2 |
||||
|
||||
- name: "Start deployment" |
||||
id: deploy |
||||
uses: chrnorm/deployment-action@v1.2.0 |
||||
with: |
||||
ref: ${{ github.event.ref }} |
||||
token: ${{ github.token }} |
||||
environment: App Service - ${{ env.APP_NAME }} |
||||
|
||||
- name: "Run Bicep compiler" |
||||
run: | |
||||
wget https://github.com/Azure/bicep/releases/download/v0.1.37-alpha/bicep-linux-x64 -qO bicep |
||||
chmod +x bicep |
||||
./bicep build webapp.bicep |
||||
working-directory: ./infra |
||||
|
||||
- name: "Login to Azure" |
||||
uses: azure/login@v1 |
||||
with: |
||||
creds: ${{ secrets.AZURE_CREDENTIALS }} |
||||
|
||||
- name: "Create resource group" |
||||
run: az group create --name ${{ env.ARM_RES_GROUP }} --location ${{ env.ARM_REGION }} |
||||
|
||||
- name: "Deploy resources" |
||||
uses: azure/arm-deploy@v1 |
||||
with: |
||||
subscriptionId: ${{ env.ARM_SUB_ID }} |
||||
resourceGroupName: ${{ env.ARM_RES_GROUP }} |
||||
template: ./infra/webapp.json |
||||
parameters: webappName=${{ env.APP_NAME }} webappImage=${{ env.IMAGE_REG }}/${{ env.IMAGE_REPO }}:${{ github.event.inputs.IMAGE_TAG }} weatherKey=${{ secrets.WEATHER_API_KEY }} |
||||
deploymentName: webapp-deploy-${{ github.run_id }} |
||||
|
||||
- name: "End deployment - failure" |
||||
if: ${{ failure() }} |
||||
uses: chrnorm/deployment-status@v1.0.0 |
||||
with: |
||||
token: ${{ github.token }} |
||||
state: failure |
||||
deployment_id: ${{ needs.deploy-bicep.outputs.deployment_id }} |
||||
|
||||
# |
||||
# Post deployment testing stage |
||||
# |
||||
validate-deployment: |
||||
name: "Run Deployment Tests" |
||||
needs: deploy-infra |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- name: "Checkout" |
||||
uses: actions/checkout@v2 |
||||
|
||||
- name: "Validate site is running" |
||||
run: .github/scripts/url-check.sh -u https://${{ env.APP_NAME }}.azurewebsites.net/ -s "Python" -t 200 |
||||
|
||||
# - name: "Run API tests" |
||||
# run: | |
||||
# npm install newman --silent |
||||
# node_modules/newman/bin/newman.js run src/tests/postman_collection.json --global-var apphost=${{ env.APP_NAME }}.azurewebsites.net |
||||
|
||||
- name: "End deployment - success" |
||||
if: ${{ success() }} |
||||
uses: chrnorm/deployment-status@v1.0.0 |
||||
with: |
||||
token: ${{ github.token }} |
||||
state: success |
||||
deployment_id: ${{ needs.deploy-infra.outputs.deployment_id }} |
||||
environment_url: https://${{ env.APP_NAME }}.azurewebsites.net/ |
||||
|
||||
- name: "End deployment - failure" |
||||
if: ${{ failure() }} |
||||
uses: chrnorm/deployment-status@v1.0.0 |
||||
with: |
||||
token: ${{ github.token }} |
||||
state: failure |
||||
deployment_id: ${{ needs.deploy-infra.outputs.deployment_id }} |
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
name: CI Build App |
||||
|
||||
on: |
||||
push: |
||||
branches: [master] |
||||
paths-ignore: |
||||
- ".github/**" |
||||
pull_request: |
||||
|
||||
env: |
||||
IMAGE_REG: ghcr.io |
||||
IMAGE_REPO: benc-uk/python-demoapp |
||||
|
||||
jobs: |
||||
test: |
||||
name: "Tests & Linting" |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- name: "Checkout" |
||||
uses: actions/checkout@v2 |
||||
|
||||
- name: "Test/linting stub" |
||||
run: echo "Nothing here 😐" |
||||
|
||||
# - name: "Upload test results" |
||||
# uses: actions/upload-artifact@v2 |
||||
# with: |
||||
# name: test-results |
||||
# path: ./src/test-results.xml |
||||
# - name: "Report on test results" |
||||
# uses: ashley-taylor/junit-report-annotations-action@master |
||||
# if: always() |
||||
# with: |
||||
# access-token: ${{ secrets.GITHUB_TOKEN }} |
||||
# path: ./src/test-results.xml |
||||
# name: Unit test results |
||||
|
||||
build: |
||||
name: "Build & Push Image" |
||||
needs: test |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- name: "Checkout" |
||||
uses: actions/checkout@v2 |
||||
|
||||
- name: "Create datestamp image tag" # Nicer than using github runid, I think |
||||
run: echo "IMAGE_TAG=$(date +%d-%m-%Y.%H%M)" >> $GITHUB_ENV |
||||
|
||||
- name: "Docker build image" |
||||
run: docker build . -t $IMAGE_REG/$IMAGE_REPO:$IMAGE_TAG |
||||
|
||||
- name: "Login to GitHub container registry" |
||||
if: github.ref == 'refs/heads/master' |
||||
uses: docker/login-action@v1 |
||||
with: |
||||
registry: ${{ env.IMAGE_REG }} |
||||
username: ${{ github.repository_owner }} |
||||
password: ${{ secrets.CR_PAT }} |
||||
|
||||
- name: "Docker push image to ${{ env.IMAGE_REG }}" |
||||
if: github.ref == 'refs/heads/master' |
||||
run: docker push $IMAGE_REG/$IMAGE_REPO |
||||
|
||||
- name: "Trigger AKS release pipeline" |
||||
if: github.ref == 'refs/heads/master' |
||||
uses: benc-uk/workflow-dispatch@v1 |
||||
with: |
||||
workflow: "CD Release - AKS" |
||||
token: ${{ secrets.CR_PAT }} |
||||
inputs: '{ "IMAGE_TAG": "${{ env.IMAGE_TAG }}" }' |
||||
|
||||
- name: "Trigger Azure web app release pipeline" |
||||
if: github.ref == 'refs/heads/master' |
||||
uses: benc-uk/workflow-dispatch@v1 |
||||
with: |
||||
workflow: "CD Release - Webapp" |
||||
token: ${{ secrets.CR_PAT }} |
||||
inputs: '{ "IMAGE_TAG": "${{ env.IMAGE_TAG }}" }' |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
name: Release Versioned Image |
||||
|
||||
on: |
||||
workflow_dispatch: |
||||
release: |
||||
types: [published] |
||||
|
||||
env: |
||||
IMAGE_REG: ghcr.io |
||||
IMAGE_REPO: benc-uk/python-demoapp |
||||
|
||||
jobs: |
||||
publish-image: |
||||
name: "Build & Publish" |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- name: "Checkout" |
||||
uses: actions/checkout@v2 |
||||
|
||||
- name: "Docker build image with version tag" |
||||
run: docker build . -t $IMAGE_REG/$IMAGE_REPO:latest -t $IMAGE_REG/$IMAGE_REPO:${{ github.event.release.tag_name }} |
||||
|
||||
- name: "Login to GitHub container registry" |
||||
uses: docker/login-action@v1 |
||||
with: |
||||
registry: ${{ env.IMAGE_REG }} |
||||
username: ${{ github.repository_owner }} |
||||
password: ${{ secrets.CR_PAT }} |
||||
|
||||
- name: "Docker push image to ${{ env.IMAGE_REG }}" |
||||
run: docker push $IMAGE_REG/$IMAGE_REPO |