From c0c7637a86fbec4d87126f7fd51085c4915232dc Mon Sep 17 00:00:00 2001 From: Vladimir Sakhonchik Date: Fri, 5 May 2023 11:20:45 +0200 Subject: [PATCH 1/3] init commit --- CONTRIBUTING.md | 64 ---- LICENSE | 7 - README.md | 108 +----- build/Dockerfile | 16 - deploy/kubernetes/aks-live.yaml | 15 - deploy/kubernetes/app.sample.yaml | 23 -- deploy/kubernetes/readme.md | 15 - deploy/webapp.bicep | 33 -- makefile | 85 ----- src/app/__init__.py | 9 - src/app/apis.py | 75 ----- src/app/conftest.py | 10 - src/app/static/css/main.css | 42 --- src/app/static/img/docker-whale.svg | 1 - src/app/static/img/favicon.ico | Bin 167233 -> 0 bytes src/app/static/img/flask.png | Bin 6490 -> 0 bytes src/app/static/img/github-2.svg | 1 - src/app/static/img/python.svg | 1 - src/app/static/js/monitor.js | 117 ------- src/app/static/js/sorttable.js | 496 ---------------------------- src/app/templates/base.html | 68 ---- src/app/templates/index.html | 36 -- src/app/templates/info.html | 47 --- src/app/templates/monitor.html | 40 --- src/app/tests/test_api.py | 29 -- src/app/tests/test_views.py | 19 -- src/app/views.py | 30 -- src/requirements.txt | 7 - src/run.py | 10 - tests/postman_collection.json | 140 -------- 30 files changed, 1 insertion(+), 1543 deletions(-) delete mode 100644 CONTRIBUTING.md delete mode 100644 LICENSE delete mode 100644 build/Dockerfile delete mode 100644 deploy/kubernetes/aks-live.yaml delete mode 100644 deploy/kubernetes/app.sample.yaml delete mode 100644 deploy/kubernetes/readme.md delete mode 100644 deploy/webapp.bicep delete mode 100644 makefile delete mode 100644 src/app/__init__.py delete mode 100644 src/app/apis.py delete mode 100644 src/app/conftest.py delete mode 100644 src/app/static/css/main.css delete mode 100644 src/app/static/img/docker-whale.svg delete mode 100644 src/app/static/img/favicon.ico delete mode 100644 src/app/static/img/flask.png delete mode 100644 src/app/static/img/github-2.svg delete mode 100644 src/app/static/img/python.svg delete mode 100644 src/app/static/js/monitor.js delete mode 100644 src/app/static/js/sorttable.js delete mode 100644 src/app/templates/base.html delete mode 100644 src/app/templates/index.html delete mode 100644 src/app/templates/info.html delete mode 100644 src/app/templates/monitor.html delete mode 100644 src/app/tests/test_api.py delete mode 100644 src/app/tests/test_views.py delete mode 100644 src/app/views.py delete mode 100644 src/requirements.txt delete mode 100644 src/run.py delete mode 100644 tests/postman_collection.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 3a38870..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,64 +0,0 @@ -# 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) diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 694825f..0000000 --- a/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright 2021 Ben Coleman - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index de1a501..98221b9 100644 --- a/README.md +++ b/README.md @@ -1,107 +1 @@ -# Python Flask - Demo Web Application - -This is a simple Python Flask web application. The app provides system information and a realtime monitoring screen with dials showing CPU, memory, IO and process information. - -The app has been designed with cloud native demos & containers in mind, in order to provide a real working application for deployment, something more than "hello-world" but with the minimum of pre-reqs. It is not intended as a complete example of a fully functioning architecture or complex software design. - -Typical uses would be deployment to Kubernetes, demos of Docker, CI/CD (build pipelines are provided), deployment to cloud (Azure) monitoring, auto-scaling - -## Screenshot - -![screen](https://user-images.githubusercontent.com/14982936/30533171-db17fccc-9c4f-11e7-8862-eb8c148fedea.png) - -# Status - -![](https://img.shields.io/github/last-commit/benc-uk/python-demoapp) ![](https://img.shields.io/github/release-date/benc-uk/python-demoapp) ![](https://img.shields.io/github/v/release/benc-uk/python-demoapp) ![](https://img.shields.io/github/commit-activity/y/benc-uk/python-demoapp) - -Live instances: - -[![](https://img.shields.io/website?label=Hosted%3A%20Azure%20App%20Service&up_message=online&url=https%3A%2F%2Fpython-demoapp.azurewebsites.net%2F)](https://python-demoapp.azurewebsites.net/) -[![](https://img.shields.io/website?label=Hosted%3A%20Kubernetes&up_message=online&url=https%3A%2F%2Fpython-demoapp.kube.benco.io%2F)](https://python-demoapp.kube.benco.io/) - -## Building & Running Locally - -### Pre-reqs - -- Be using Linux, WSL or MacOS, with bash, make etc -- [Python 3.8+](https://www.python.org/downloads/) - for running locally, linting, running tests etc -- [Docker](https://docs.docker.com/get-docker/) - for running as a container, or image build and push -- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-linux) - for deployment to Azure - -Clone the project to any directory where you do development work - -``` -git clone https://github.com/benc-uk/python-demoapp.git -``` - -### Makefile - -A standard GNU Make file is provided to help with running and building locally. - -```text -help ๐Ÿ’ฌ This help message -lint ๐Ÿ”Ž Lint & format, will not fix but sets exit code on error -lint-fix ๐Ÿ“œ Lint & format, will try to fix errors and modify code -image ๐Ÿ”จ Build container image from Dockerfile -push ๐Ÿ“ค Push container image to registry -run ๐Ÿƒ Run the server locally using Python & Flask -deploy ๐Ÿš€ Deploy to Azure Web App -undeploy ๐Ÿ’€ Remove from Azure -test ๐ŸŽฏ Unit tests for Flask app -test-report ๐ŸŽฏ Unit tests for Flask app (with report output) -test-api ๐Ÿšฆ Run integration API tests, server must be running -clean ๐Ÿงน Clean up project -``` - -Make file variables and default values, pass these in when calling `make`, e.g. `make image IMAGE_REPO=blah/foo` - -| Makefile Variable | Default | -| ----------------- | ---------------------- | -| IMAGE_REG | ghcr.io | -| IMAGE_REPO | benc-uk/python-demoapp | -| IMAGE_TAG | latest | -| AZURE_RES_GROUP | temp-demoapps | -| AZURE_REGION | uksouth | -| AZURE_SITE_NAME | pythonapp-{git-sha} | - -The app runs under Flask and listens on port 5000 by default, this can be changed with the `PORT` environmental variable. - -# Containers - -Public container image is [available on GitHub Container Registry](https://github.com/users/benc-uk/packages/container/package/python-demoapp) - -Run in a container with: - -```bash -docker run --rm -it -p 5000:5000 ghcr.io/benc-uk/python-demoapp:latest -``` - -Should you want to build your own container, use `make image` and the above variables to customise the name & tag. - -## Kubernetes - -The app can easily be deployed to Kubernetes using Helm, see [deploy/kubernetes/readme.md](deploy/kubernetes/readme.md) for details - -# GitHub Actions CI/CD - -A working set of CI and CD release GitHub Actions workflows are provided `.github/workflows/`, automated builds are run in GitHub hosted runners - -### [GitHub Actions](https://github.com/benc-uk/python-demoapp/actions) - -[![](https://img.shields.io/github/workflow/status/benc-uk/python-demoapp/CI%20Build%20App)](https://github.com/benc-uk/python-demoapp/actions?query=workflow%3A%22CI+Build+App%22) - -[![](https://img.shields.io/github/workflow/status/benc-uk/python-demoapp/CD%20Release%20-%20AKS?label=release-kubernetes)](https://github.com/benc-uk/python-demoapp/actions?query=workflow%3A%22CD+Release+-+AKS%22) - -[![](https://img.shields.io/github/workflow/status/benc-uk/python-demoapp/CD%20Release%20-%20Webapp?label=release-azure)](https://github.com/benc-uk/python-demoapp/actions?query=workflow%3A%22CD+Release+-+Webapp%22) - -[![](https://img.shields.io/github/last-commit/benc-uk/python-demoapp)](https://github.com/benc-uk/python-demoapp/commits/master) - -## Running in Azure App Service (Linux) - -If you want to deploy to an Azure Web App as a container (aka Linux Web App), a Bicep template is provided in the [deploy](deploy/) directory - -For a super quick deployment, use `make deploy` which will deploy to a resource group, temp-demoapps and use the git ref to create a unique site name - -```bash -make deploy -``` \ No newline at end of file +# cicd-pipeline \ No newline at end of file diff --git a/build/Dockerfile b/build/Dockerfile deleted file mode 100644 index ae0775a..0000000 --- a/build/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM python:3.9-slim-buster - -LABEL Name="Python Flask Demo App" Version=1.4.2 -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"] \ No newline at end of file diff --git a/deploy/kubernetes/aks-live.yaml b/deploy/kubernetes/aks-live.yaml deleted file mode 100644 index 1a5f9e2..0000000 --- a/deploy/kubernetes/aks-live.yaml +++ /dev/null @@ -1,15 +0,0 @@ -image: - repository: ghcr.io/benc-uk/python-demoapp - pullPolicy: Always - -service: - targetPort: 5000 - -ingress: - enabled: true - host: python-demoapp.kube.benco.io - annotations: - nginx.ingress.kubernetes.io/ssl-redirect: "true" - tls: - enabled: true - secretName: kube-benco-io-cert diff --git a/deploy/kubernetes/app.sample.yaml b/deploy/kubernetes/app.sample.yaml deleted file mode 100644 index 5731f0b..0000000 --- a/deploy/kubernetes/app.sample.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# -# See this Helm chart for all options -# https://github.com/benc-uk/helm-charts/tree/master/webapp -# - -image: - repository: ghcr.io/benc-uk/python-demoapp - tag: latest - pullPolicy: Always - -service: - targetPort: 5000 - type: LoadBalancer - -# -# If you have an ingress controller set up -# -# ingress: -# enabled: true -# host: changeme.example.net -# tls: -# enabled: true -# secretName: changeme-cert-secret diff --git a/deploy/kubernetes/readme.md b/deploy/kubernetes/readme.md deleted file mode 100644 index e67e885..0000000 --- a/deploy/kubernetes/readme.md +++ /dev/null @@ -1,15 +0,0 @@ -# Kubernetes - -Deployment into Kubernetes is simple using a [generic Helm chart for deploying web apps](https://github.com/benc-uk/helm-charts/tree/master/webapp) - -Make sure you have [Helm installed first](https://helm.sh/docs/intro/install/) - -First add the Helm repo -```bash -helm repo add benc-uk https://benc-uk.github.io/helm-charts -``` - -Make a copy of `app.sample.yaml` to `myapp.yaml` and modify the values to suit your environment. If you're in a real hurry you can use the file as is and make no changes. -```bash -helm install demo benc-uk/webapp --values myapp.yaml -``` \ No newline at end of file diff --git a/deploy/webapp.bicep b/deploy/webapp.bicep deleted file mode 100644 index 36f23ca..0000000 --- a/deploy/webapp.bicep +++ /dev/null @@ -1,33 +0,0 @@ -param location string = resourceGroup().location - -param planName string = 'app-plan-linux' -param planTier string = 'P1v2' - -param webappName string = 'python-demoapp' -param webappImage string = 'ghcr.io/benc-uk/python-demoapp:latest' -param weatherKey string = '' -param releaseInfo string = 'Released on ${utcNow('f')}' - -resource appServicePlan 'Microsoft.Web/serverfarms@2020-10-01' = { - name: planName - location: location - kind: 'linux' - sku: { - name: planTier - } - properties: { - reserved: true - } -} - -resource webApp 'Microsoft.Web/sites@2020-10-01' = { - name: webappName - location: location - properties: { - serverFarmId: appServicePlan.id - siteConfig: { - appSettings: [] - linuxFxVersion: 'DOCKER|${webappImage}' - } - } -} diff --git a/makefile b/makefile deleted file mode 100644 index cebf7c5..0000000 --- a/makefile +++ /dev/null @@ -1,85 +0,0 @@ -# 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 $(AZURE_RES_GROUP) ๐Ÿ˜ฒ" - az group delete -n $(AZURE_RES_GROUP) -o table --no-wait - -test: venv ## ๐ŸŽฏ Unit tests for Flask app - . $(SRC_DIR)/.venv/bin/activate \ - && pytest -v - -test-report: venv ## ๐ŸŽฏ Unit tests for Flask app (with report output) - . $(SRC_DIR)/.venv/bin/activate \ - && pytest -v --junitxml=test-results.xml - -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* - rm -rf test-results.xml - rm -rf $(SRC_DIR)/app/__pycache__ - rm -rf $(SRC_DIR)/app/tests/__pycache__ - rm -rf .pytest_cache - rm -rf $(SRC_DIR)/.pytest_cache - -# ============================================================================ - -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 diff --git a/src/app/__init__.py b/src/app/__init__.py deleted file mode 100644 index 3bab841..0000000 --- a/src/app/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from flask import Flask - - -def create_app(): - app = Flask(__name__) - with app.app_context(): - from . import views # noqa: E402,F401 - from . import apis # noqa: E402,F401 - return app diff --git a/src/app/apis.py b/src/app/apis.py deleted file mode 100644 index 8543c57..0000000 --- a/src/app/apis.py +++ /dev/null @@ -1,75 +0,0 @@ -from flask import jsonify, current_app as app -import psutil - -olddata = {} -olddata["disk_write"] = 0 -olddata["disk_read"] = 0 -olddata["net_sent"] = 0 -olddata["net_recv"] = 0 - - -# -# This route returns real time process information as a REST API -# -@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) - - -# -# This route returns real time system metrics as a REST API -# -@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) diff --git a/src/app/conftest.py b/src/app/conftest.py deleted file mode 100644 index 9d065eb..0000000 --- a/src/app/conftest.py +++ /dev/null @@ -1,10 +0,0 @@ -from . import create_app -import pytest - -app = create_app() - - -@pytest.fixture -def client(): - with app.test_client() as client: - yield client diff --git a/src/app/static/css/main.css b/src/app/static/css/main.css deleted file mode 100644 index 21e5885..0000000 --- a/src/app/static/css/main.css +++ /dev/null @@ -1,42 +0,0 @@ - -h1 { - padding-top: 1rem; -} - -.logotext { - font-size: 1.5em !important; -} -.jumbotron { - padding: 2rem !important; -} -.gauges { - width: 100%; - height: 25vw; - margin: 0 auto; -} - -/* Sortable tables */ -table.sortable th { - background-color: #eeeeee; - color:#666666; - font-weight: bold; - cursor: pointer; - padding: 10px; -} - -td { - padding-right: 50px !important; - font-size: 18px; - border-bottom: 1px solid #dddddd; - cursor: default; -} - -.icon { - width: 40px; -} - -.dimmed-box { - background-color: rgba(0,0,0,0.2); - padding: 1rem; - border-radius: 0.3rem; -} \ No newline at end of file diff --git a/src/app/static/img/docker-whale.svg b/src/app/static/img/docker-whale.svg deleted file mode 100644 index dff737c..0000000 --- a/src/app/static/img/docker-whale.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/app/static/img/favicon.ico b/src/app/static/img/favicon.ico deleted file mode 100644 index 3cf51c654fad5c6aca6a09fde89b9ae7606b499a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167233 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

AN$uE+6-9wbmj0=<3}`&1;ejIOb<+bM#@E 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 \ No newline at end of file diff --git a/src/app/static/img/python.svg b/src/app/static/img/python.svg deleted file mode 100644 index 250b7fa..0000000 --- a/src/app/static/img/python.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/app/static/js/monitor.js b/src/app/static/js/monitor.js deleted file mode 100644 index 503980e..0000000 --- a/src/app/static/js/monitor.js +++ /dev/null @@ -1,117 +0,0 @@ -var data_memcpu; -var data_disk; -var data_net; -var chart_memcpu; -var chart_disk; -var chart_net; -var options_percent; -var options_io; - -var refresh_sec = 3.0; - -function initCharts() { - data_memcpu = google.visualization.arrayToDataTable([ - ['Label', 'Value'], - ['CPU', 0], - ['Memory', 0], - ]); - data_disk = google.visualization.arrayToDataTable([ - ['Label', 'Value'], - ['Disk read', 0], - ['Disk write', 0], - ]); - data_net = google.visualization.arrayToDataTable([ - ['Label', 'Value'], - ['Net sent', 0], - ['Net recv', 0], - ]); - - options_percent = { - //width: 1200, height: 600, - redFrom: 90, redTo: 100, - yellowFrom: 75, yellowTo: 90, - greenFrom: 0, greenTo: 75, - minorTicks: 5, animation:{ duration: 950, easing: 'inAndOut' } - }; - options_io = { - max: 200, - minorTicks: 10, animation:{ duration: 950, easing: 'inAndOut' } - }; - - chart_memcpu = new google.visualization.Gauge(document.getElementById('chart1')); - chart_disk = new google.visualization.Gauge(document.getElementById('chart2')); - chart_net = new google.visualization.Gauge(document.getElementById('chart3')); - - refreshCharts(); - refreshProcesses(); - setRefresh(refresh_sec); - - $('#refrate').text(refresh_sec); - $('#refslider').val(refresh_sec); - $(document).on('input', '#refslider', function() { - setRefresh($(this).val()) - }); -} - -var proc_timer; -var chart_timer; -function setRefresh(new_secs) { - refresh_sec = parseFloat(new_secs); - $('#refrate').text(refresh_sec); - clearInterval(proc_timer); - clearInterval(chart_timer); - proc_timer = setInterval(function () { - refreshProcesses(); - }, refresh_sec * 1000); - chart_timer = setInterval(function () { - refreshCharts(); - }, refresh_sec * 1000); -} -function refreshCharts() { - $.ajax({ - url: '/api/monitor', - type: 'GET', - dataType: 'json', - success: function (apidata) { - //console.dir(apidata); - data_memcpu.setValue(0, 1, apidata.cpu); - data_memcpu.setValue(1, 1, apidata.mem); - data_disk.setValue(0, 1, apidata.disk_read / (1024000*refresh_sec)); - data_disk.setValue(1, 1, apidata.disk_write / (1024000*refresh_sec)); - data_net.setValue(0, 1, apidata.net_sent / (1024000*refresh_sec)); - data_net.setValue(1, 1, apidata.net_recv / (1024000*refresh_sec)); - - chart_memcpu.draw(data_memcpu, options_percent); - chart_disk.draw(data_disk, options_io); - chart_net.draw(data_net, options_io); - }, - error: function (request, error) { - console.log("API Request: " + JSON.stringify(request)); - } - }); -} - -function refreshProcesses() { - $.ajax({ - url: '/api/process', - type: 'GET', - dataType: 'json', - success: function (apidata) { - $('#process_tab').empty(); - $('#proc_count').text(apidata.processes.length); - for(var p = 0; p < apidata.processes.length; p++) { - $('#process_tab').append(''+apidata.processes[p].pid+''+ - ''+apidata.processes[p].name+''+ - ''+apidata.processes[p].memory_percent.toFixed(2)+''+ - ''+(apidata.processes[p].cpu_times[0]+apidata.processes[p].cpu_times[1]).toFixed(2)+''+ - ''+apidata.processes[p].num_threads+''+ - '') - } - var myTH = document.getElementsByTagName("th")[2]; - sorttable.innerSortFunction.apply(myTH, []); - }, - error: function (request, error) { - console.log("API Request: " + JSON.stringify(request)); - } - }); -} \ No newline at end of file diff --git a/src/app/static/js/sorttable.js b/src/app/static/js/sorttable.js deleted file mode 100644 index 8e0883a..0000000 --- a/src/app/static/js/sorttable.js +++ /dev/null @@ -1,496 +0,0 @@ -/* - SortTable - version 2 - 7th April 2007 - Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/ - - Instructions: - Download this file - Add to your HTML - Add class="sortable" to any table you'd like to make sortable - Click on the headers to sort - - Thanks to many, many people for contributions and suggestions. - Licenced as X11: http://www.kryogenix.org/code/browser/licence.html - This basically means: do what you want with it. -*/ - - -var stIsIE = /*@cc_on!@*/false; - -sorttable = { - init: function() { - // quit if this function has already been called - if (arguments.callee.done) return; - // flag this function so we don't do the same thing twice - arguments.callee.done = true; - // kill the timer - if (_timer) clearInterval(_timer); - - if (!document.createElement || !document.getElementsByTagName) return; - - sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/; - - forEach(document.getElementsByTagName('table'), function(table) { - if (table.className.search(/\bsortable\b/) != -1) { - sorttable.makeSortable(table); - } - }); - - }, - - makeSortable: function(table) { - if (table.getElementsByTagName('thead').length == 0) { - // table doesn't have a tHead. Since it should have, create one and - // put the first table row in it. - the = document.createElement('thead'); - the.appendChild(table.rows[0]); - table.insertBefore(the,table.firstChild); - } - // Safari doesn't support table.tHead, sigh - if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0]; - - if (table.tHead.rows.length != 1) return; // can't cope with two header rows - - // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as - // "total" rows, for example). This is B&R, since what you're supposed - // to do is put them in a tfoot. So, if there are sortbottom rows, - // for backwards compatibility, move them to tfoot (creating it if needed). - sortbottomrows = []; - for (var i=0; i5' : ' ▴'; - this.appendChild(sortrevind); - return; - } - if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) { - // if we're already sorted by this column in reverse, just - // re-reverse the table, which is quicker - sorttable.reverse(this.sorttable_tbody); - this.className = this.className.replace('sorttable_sorted_reverse', - 'sorttable_sorted'); - this.removeChild(document.getElementById('sorttable_sortrevind')); - sortfwdind = document.createElement('span'); - sortfwdind.id = "sorttable_sortfwdind"; - sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; - this.appendChild(sortfwdind); - return; - } - - // remove sorttable_sorted classes - theadrow = this.parentNode; - forEach(theadrow.childNodes, function(cell) { - if (cell.nodeType == 1) { // an element - cell.className = cell.className.replace('sorttable_sorted_reverse',''); - cell.className = cell.className.replace('sorttable_sorted',''); - } - }); - sortfwdind = document.getElementById('sorttable_sortfwdind'); - if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); } - sortrevind = document.getElementById('sorttable_sortrevind'); - if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); } - - this.className += ' sorttable_sorted'; - sortfwdind = document.createElement('span'); - sortfwdind.id = "sorttable_sortfwdind"; - sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; - this.appendChild(sortfwdind); - - // build an array to sort. This is a Schwartzian transform thing, - // i.e., we "decorate" each row with the actual sort key, - // sort based on the sort keys, and then put the rows back in order - // which is a lot faster because you only do getInnerText once per row - row_array = []; - col = this.sorttable_columnindex; - rows = this.sorttable_tbody.rows; - for (var j=0; j 12) { - // definitely dd/mm - return sorttable.sort_ddmm; - } else if (second > 12) { - return sorttable.sort_mmdd; - } else { - // looks like a date, but we can't tell which, so assume - // that it's dd/mm (English imperialism!) and keep looking - sortfn = sorttable.sort_ddmm; - } - } - } - } - return sortfn; - }, - - getInnerText: function(node) { - // gets the text we want to use for sorting for a cell. - // strips leading and trailing whitespace. - // this is *not* a generic getInnerText function; it's special to sorttable. - // for example, you can override the cell text with a customkey attribute. - // it also gets .value for fields. - - if (!node) return ""; - - hasInputs = (typeof node.getElementsByTagName == 'function') && - node.getElementsByTagName('input').length; - - if (node.getAttribute("sorttable_customkey") != null) { - return node.getAttribute("sorttable_customkey"); - } - else if (typeof node.textContent != 'undefined' && !hasInputs) { - return node.textContent.replace(/^\s+|\s+$/g, ''); - } - else if (typeof node.innerText != 'undefined' && !hasInputs) { - return node.innerText.replace(/^\s+|\s+$/g, ''); - } - else if (typeof node.text != 'undefined' && !hasInputs) { - return node.text.replace(/^\s+|\s+$/g, ''); - } - else { - switch (node.nodeType) { - case 3: - if (node.nodeName.toLowerCase() == 'input') { - return node.value.replace(/^\s+|\s+$/g, ''); - } - case 4: - return node.nodeValue.replace(/^\s+|\s+$/g, ''); - break; - case 1: - case 11: - var innerText = ''; - for (var i = 0; i < node.childNodes.length; i++) { - innerText += sorttable.getInnerText(node.childNodes[i]); - } - return innerText.replace(/^\s+|\s+$/g, ''); - break; - default: - return ''; - } - } - }, - - reverse: function(tbody) { - // reverse the rows in a tbody - newrows = []; - for (var i=0; i=0; i--) { - tbody.appendChild(newrows[i]); - } - delete newrows; - }, - - /* sort functions - each sort function takes two parameters, a and b - you are comparing a[0] and b[0] */ - sort_numeric: function(a,b) { - aa = parseFloat(a[0].replace(/[^0-9.-]/g,'')); - if (isNaN(aa)) aa = 0; - bb = parseFloat(b[0].replace(/[^0-9.-]/g,'')); - if (isNaN(bb)) bb = 0; - return aa-bb; - }, - sort_alpha: function(a,b) { - if (a[0]==b[0]) return 0; - if (a[0] 0 ) { - var q = list[i]; list[i] = list[i+1]; list[i+1] = q; - swap = true; - } - } // for - t--; - - if (!swap) break; - - for(var i = t; i > b; --i) { - if ( comp_func(list[i], list[i-1]) < 0 ) { - var q = list[i]; list[i] = list[i-1]; list[i-1] = q; - swap = true; - } - } // for - b++; - - } // while(swap) - } -} - -/* ****************************************************************** - Supporting functions: bundled here to avoid depending on a library - ****************************************************************** */ - -// Dean Edwards/Matthias Miller/John Resig - -/* for Mozilla/Opera9 */ -if (document.addEventListener) { - document.addEventListener("DOMContentLoaded", sorttable.init, false); -} - -/* for Internet Explorer */ -/*@cc_on @*/ -/*@if (@_win32) - document.write(" - - - - -

{% block content %}{% endblock %}
- v1.4.2 [Ben Coleman, 2018-2021]     - - diff --git a/src/app/templates/index.html b/src/app/templates/index.html deleted file mode 100644 index 6ac2a90..0000000 --- a/src/app/templates/index.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends "base.html" %} {% block content %} -
-
-

Python & Flask Demo App

- -
- 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. -
-
- -
-

- - GitHub Project - -     - - - Docker Images - -

-
-

- - - Get started with Azure & Python - -

- -
-

Microsoft โค Open Source

-
-
-{% endblock %} diff --git a/src/app/templates/info.html b/src/app/templates/info.html deleted file mode 100644 index c5d9e42..0000000 --- a/src/app/templates/info.html +++ /dev/null @@ -1,47 +0,0 @@ -{% extends "base.html" %} {% block content %} - -
-

๐Ÿ›  System Information

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Hostname{{ info.plat.node() }}
Boot Time{{ info.boottime }}
OS Platform{{ info.plat.system() }}
OS Version{{ info.plat.version() }}
Python Version{{ info.plat.python_version() }}
Processor & Cores{{ info.cpu.count }} x {{ info.cpu.brand }}
System Memory{{ (info.mem.total / (1024*1024*1024)) | round(0,'ceil') |int }}GB ({{info.mem.percent}}% used)
Network Interfaces - {% for iface, snics in info.net.items() %} {% for snic in snics if (snic.family == 2) %} -
  • {{ iface }} - {{ snic.address }}
  • - {% endfor %} {% endfor %} -
    -
    - -{% endblock %} diff --git a/src/app/templates/monitor.html b/src/app/templates/monitor.html deleted file mode 100644 index 59aa069..0000000 --- a/src/app/templates/monitor.html +++ /dev/null @@ -1,40 +0,0 @@ -{% extends "base.html" %} {% block content %} - - - - - - - - - Refresh Rate: secs - -
    -

    ๐Ÿ‘“ Running Processes ()

    -
    - - - - - - - - - - - -
    PIDNameMemCPU TimeThreads
    -
    - -
    - -

    ๐ŸŒก Performance Monitor

    -
    -
    -
    - - -{% endblock %} diff --git a/src/app/tests/test_api.py b/src/app/tests/test_api.py deleted file mode 100644 index 8b0ba9e..0000000 --- a/src/app/tests/test_api.py +++ /dev/null @@ -1,29 +0,0 @@ -import json - - -# Test the process API returns JSON results we expect -def test_api_process(client): - resp = client.get("/api/process") - - assert resp.status_code == 200 - assert resp.headers["Content-Type"] == "application/json" - resp_payload = json.loads(resp.data) - assert len(resp_payload["processes"]) > 0 - assert resp_payload["processes"][0]["memory_percent"] > 0 - assert len(resp_payload["processes"][0]["name"]) > 0 - - -# Test the monitor API returns JSON results we expect -def test_api_monitor(client): - resp = client.get("/api/monitor") - - assert resp.status_code == 200 - assert resp.headers["Content-Type"] == "application/json" - resp_payload = json.loads(resp.data) - assert resp_payload["cpu"] >= 0 - assert resp_payload["disk"] >= 0 - assert resp_payload["disk_read"] >= 0 - assert resp_payload["disk_write"] >= 0 - assert resp_payload["mem"] >= 0 - assert resp_payload["net_recv"] >= 0 - assert resp_payload["net_sent"] >= 0 diff --git a/src/app/tests/test_views.py b/src/app/tests/test_views.py deleted file mode 100644 index 24e68a0..0000000 --- a/src/app/tests/test_views.py +++ /dev/null @@ -1,19 +0,0 @@ -def test_home(client): - resp = client.get("/") - - assert resp.status_code == 200 - assert b"Python" in resp.data - - -def test_page_content(client): - resp = client.get("/") - - assert resp.status_code == 200 - assert b"Coleman" in resp.data - - -def test_info(client): - resp = client.get("/info") - - assert resp.status_code == 200 - assert b"Hostname" in resp.data diff --git a/src/app/views.py b/src/app/views.py deleted file mode 100644 index 055975c..0000000 --- a/src/app/views.py +++ /dev/null @@ -1,30 +0,0 @@ -from flask import render_template, current_app as 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") diff --git a/src/requirements.txt b/src/requirements.txt deleted file mode 100644 index de4fe88..0000000 --- a/src/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -Flask==1.1.2 -py-cpuinfo==7.0.0 -psutil==5.8.0 -gunicorn==20.1.0 -black==20.8b1 -flake8==3.9.0 -pytest==6.2.2 \ No newline at end of file diff --git a/src/run.py b/src/run.py deleted file mode 100644 index d2f8133..0000000 --- a/src/run.py +++ /dev/null @@ -1,10 +0,0 @@ -import os -from app import create_app - -app = create_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) diff --git a/tests/postman_collection.json b/tests/postman_collection.json deleted file mode 100644 index 36f4e2f..0000000 --- a/tests/postman_collection.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "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": "http://{{apphost}}/api/process", - "protocol": "http", - "host": ["{{apphost}}"], - "path": ["api", "process"] - } - }, - "response": [] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [""] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [""] - } - } - ] -} From 8546445bf661ddfe6d5b23bc8b1f2f8f21c1f0bc Mon Sep 17 00:00:00 2001 From: Vladimir Sakhonchik Date: Fri, 5 May 2023 11:22:31 +0200 Subject: [PATCH 2/3] init commit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 98221b9..25819bd 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# cicd-pipeline \ No newline at end of file +# cicd-pipeline From 7f33563aae962f740fe418899701ae2213cb8f7e Mon Sep 17 00:00:00 2001 From: Vladimir Sakhonchik Date: Fri, 5 May 2023 11:24:34 +0200 Subject: [PATCH 3/3] init commit --- .DS_Store | Bin 8196 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index df129d8fbc78455529d7dcd2ed9d33c018e22b4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeI1-HOvd6vxl>W1F&wy-?g6A>eIUTU{2#o0zsp!4KH1dZALAO?AU&Go?u_QVP9Q z-@#Y#(l_y4ywd-CY)R9umm+Zv%={-a=cJwAOp~0Hh?qMK+C+OqWT6Oc?4sIYVqBCB zE9ssFlp&rd=tum-kGQ|#YdQ=8L%h1>#Pz{O z5$b4NDwUTGR5AsCR?saCZIKR$7+b5Ob*U61Fs4GGDwOFIgQ;-z+uG03x>TydiRt8n z=}%@lLt*mMaeiC66LXZB+7K`VHVKHIOO05uwSKTtxl-M!Sv70ddgGn)nU@aJY2pqiuf)+Y=aGNW9{MlB(QMGT*5PqF4CB#6 z286)~F0WsPalmITpTgfRWQTfy=}|8S=SvQb1=vG6 z1I%}FhmLaoKXd+dELRWvwo6Z-Tb8OM7C5_9WlCs5C+p{xJO68D=WDNGERUW{ZegmF z0&{{o$-xl2I}0P@SqvbV3FV+*=Ee+xLJ}yeS?!7Zzjyij|3c2)Ov(^21QY>Q>3Urk z?TaVnJQ$H{+bC~QgbBZ;QW=73?L`2NSAQ6yZNpW>I9iuV(F6PIF9Now0jozJbN`pi JVyc1@_zjsKYMlT8