-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Payomeke
committed
May 23, 2022
0 parents
commit ba648a1
Showing
18 changed files
with
726 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
name: srechallenge CI | ||
|
||
# Controls when the workflow will run | ||
on: | ||
# Triggers the workflow on push or pull request events but only for the main branch | ||
push: | ||
branches: [ main ] | ||
pull_request: | ||
branches: [ main ] | ||
|
||
# Allows you to run this workflow manually from the Actions tab | ||
workflow_dispatch: | ||
|
||
jobs: | ||
testing: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it | ||
- uses: actions/checkout@v3 | ||
|
||
# Configuring Python 3.9 for testing | ||
- name: Set up Python 3.9 | ||
uses: actions/setup-python@v2 | ||
with: | ||
python-version: 3.9 | ||
|
||
# Installing dependencies. Only flake8 for linting and flask for the app. | ||
- name: Install dependencies | ||
run: | | ||
python -m pip install --upgrade pip | ||
pip install flake8 | ||
pip install -r requirements.txt | ||
# Linting with flake8. Leaving mistakes on purpose. | ||
- name: Lint with flake8 | ||
run: | | ||
# stop the build if there are Python syntax errors or undefined names | ||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics | ||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide | ||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics | ||
# Running Unit test using unittest module. Only 1 test. | ||
- name: Test with unittest | ||
run: | | ||
python -m unittest | ||
# Building the image and pushing it to dockerhub | ||
docker: | ||
runs-on: ubuntu-latest | ||
# Job depending of testing before building the docker image. | ||
needs: testing | ||
steps: | ||
|
||
# Can be useful if you want to add emulation support with QEMU to be able to build against more platforms. | ||
- | ||
name: Set up QEMU | ||
uses: docker/setup-qemu-action@v2 | ||
|
||
# Action will create and boot a builder using by default the docker-container builder driver. This is not required but recommended using it to be able to build multi-platform images, export cache, etc. | ||
- | ||
name: Set up Docker Buildx | ||
uses: docker/setup-buildx-action@v2 | ||
|
||
# Login to dockerhub with my account. | ||
- | ||
name: Login to DockerHub | ||
uses: docker/login-action@v2 | ||
with: | ||
username: ${{ secrets.DOCKERHUB_USERNAME }} | ||
password: ${{ secrets.DOCKERHUB_TOKEN }} | ||
- | ||
name: Build and push | ||
uses: docker/build-push-action@v3 | ||
with: | ||
push: true | ||
tags: jmpalomares/srechallenge:latest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
|
||
# C extensions | ||
*.so | ||
|
||
# Distribution / packaging | ||
bin/ | ||
build/ | ||
develop-eggs/ | ||
dist/ | ||
eggs/ | ||
lib/ | ||
lib64/ | ||
parts/ | ||
sdist/ | ||
var/ | ||
*.egg-info/ | ||
.installed.cfg | ||
*.egg | ||
|
||
# Installer logs | ||
pip-log.txt | ||
pip-delete-this-directory.txt | ||
|
||
# Unit test / coverage reports | ||
.tox/ | ||
.coverage | ||
.cache | ||
nosetests.xml | ||
coverage.xml | ||
|
||
# Translations | ||
*.mo | ||
|
||
# Mr Developer | ||
.mr.developer.cfg | ||
.project | ||
.pydevproject | ||
|
||
# Rope | ||
.ropeproject | ||
|
||
# Django stuff: | ||
*.log | ||
*.pot | ||
|
||
# Sphinx documentation | ||
docs/_build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
FROM python:slim-bullseye | ||
|
||
WORKDIR /srechallenge | ||
|
||
COPY requirements.txt requirements.txt | ||
COPY app.py app.py | ||
RUN pip3 install -r requirements.txt | ||
|
||
CMD [ "python", "-m" , "flask", "run", "--host=0.0.0.0"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
# Srechallenge flask app | ||
Simple app in flask to GET a response based in a query parameter defined in the URL. | ||
|
||
The customer will connect to the endpoint with a saludation query in the URL and it will receive a response. | ||
|
||
|
||
curl -X GET http://localhost:5000/srechallenge?saludation=Hi | ||
{"data":"Hi there!"} | ||
|
||
HTTP/1.1 200 OK | ||
Server: Werkzeug/2.1.2 Python/3.9.2 | ||
Date: Sun, 22 May 2022 09:51:36 GMT | ||
Content-Type: application/json | ||
Content-Length: 30 | ||
Connection: close | ||
|
||
|
||
If saludation is not the correct one, it will return an error with status code 500: | ||
|
||
|
||
curl -X GET http://localhost:5000/srechallenge?saludation=Hello | ||
{"data":"Customer saludation Hello not found."} | ||
|
||
HTTP/1.1 500 INTERNAL SERVER ERROR | ||
Server: Werkzeug/2.1.2 Python/3.9.2 | ||
Date: Sun, 22 May 2022 09:51:26 GMT | ||
Content-Type: application/json | ||
Content-Length: 48 | ||
Connection: close | ||
|
||
I did some changes because the task was asking for an individual deployment for each customer. | ||
|
||
As the app is not using databases queries, there is not risk for SQL injections attacks. | ||
|
||
Probably would be better to have a proper database with customers details and select from the table the right response based in the saludation. So we will have a single app for all customer and we can have a table in a database for all customer details or multiples databases, one for each customer individually. | ||
|
||
Unitests for each scenario are ran in the CI pipeline. | ||
|
||
## Deploying with helm | ||
I've used helm charts for the deployment in kubernetes. | ||
|
||
Just running following command: | ||
|
||
helm install srechallenge-chart srechallenge-helm/ --values srechallenge-helm/values.yaml | ||
|
||
NAME: srechallenge-chart | ||
LAST DEPLOYED: Mon May 23 03:44:38 2022 | ||
NAMESPACE: default | ||
STATUS: deployed | ||
REVISION: 1 | ||
NOTES: | ||
1. Get the application URL by running these commands: | ||
NOTE: It may take a few minutes for the LoadBalancer IP to be available. | ||
You can watch the status of by running 'kubectl get --namespace default svc -w srechallenge-chart' | ||
export SERVICE_IP=$(kubectl get svc --namespace default srechallenge-chart --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}") | ||
echo http://$SERVICE_IP:5000 | ||
|
||
I've used minikube for testing in local. As the service type is LoadBalancer, I'd to run minikube tunnel to enable my localhost as external IP: | ||
|
||
kubectl get service | ||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE | ||
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 53m | ||
srechallenge-chart LoadBalancer 10.101.164.151 <pending> 5000:32201/TCP 4m50s | ||
|
||
|
||
minikube tunnel | ||
✅ Tunnel successfully started | ||
|
||
📌 NOTE: Please do not close this terminal as this process must stay alive for the tunnel to be accessible ... | ||
|
||
🏃 Starting tunnel for service srechallenge-chart. | ||
|
||
|
||
kubectl get service | ||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE | ||
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 55m | ||
srechallenge-chart LoadBalancer 10.101.164.151 127.0.0.1 5000:32201/TCP 7m31s | ||
|
||
|
||
curl -X GET http://localhost:5000/srechallenge?saludation=Hi | ||
{"data":"Hi there!"} | ||
|
||
|
||
Of course in a production environment we will use cert-manager, an ingress controller (I like nginx) and configure let's encrypt as issuer. Example configuration: | ||
|
||
## Nginx Ingress | ||
apiVersion: networking.k8s.io/v1 | ||
kind: Ingress | ||
metadata: | ||
annotations: | ||
kubernetes.io/ingress.class: nginx | ||
cert-manager.io/cluster-issuer: "letsencrypt-{{ .Values.environment }}" | ||
name: app | ||
namespace: {{ .Values.namespace }} | ||
spec: | ||
tls: | ||
- hosts: | ||
- {{ .Values.fqdn }} | ||
secretName: srechallenge-certificate-{{ .Values.environment }} | ||
rules: | ||
- host: {{ .Values.fqdn }} | ||
http: | ||
paths: | ||
- path: / | ||
pathType: Prefix | ||
backend: | ||
service: | ||
name: web | ||
port: | ||
number: {{ .Values.app }} | ||
More details: | ||
- [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/deploy/) | ||
- [How to Install Kubernetes Cert-Manager and Configure Let’s Encrypt](https://www.howtogeek.com/devops/how-to-install-kubernetes-cert-manager-and-configure-lets-encrypt/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
from flask import Flask, request, jsonify | ||
|
||
app = Flask(__name__) | ||
|
||
|
||
@app.route('/srechallenge', methods=['GET']) | ||
def get_response(): | ||
try: | ||
# Request customer query in url Example: http://localhost:5000/srechalenge?saludation=Hi | ||
saludation = request.args.get('saludation') | ||
# If saludation in URL is equal to Hi, Dear Sir or Madam or Moin it returns a customised message and status code 200. | ||
if saludation == "Hi": | ||
data={"data":"Hi there!"} | ||
resp = jsonify(data) | ||
resp.status_code = 200 | ||
return resp | ||
if saludation == "Dear Sir or Madam": | ||
data={"data":"Yours faithfully"} | ||
resp = jsonify(data) | ||
resp.status_code = 200 | ||
return resp | ||
if saludation == "Moin": | ||
data={"data":"Moin moin!"} | ||
resp = jsonify(data) | ||
resp.status_code = 200 | ||
return resp | ||
# If saludation is not the correct one show error message with status code 500 | ||
else: | ||
data={"data":'Customer saludation {} not found.'.format(saludation)} | ||
resp = jsonify(data) | ||
resp.status_code = 500 | ||
return resp | ||
except Exception as e: | ||
print(e) | ||
|
||
|
||
if __name__ == '__main__': | ||
app.run(debug=False, port=5000) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Flask==2.1.2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Patterns to ignore when building packages. | ||
# This supports shell glob matching, relative path matching, and | ||
# negation (prefixed with !). Only one pattern per line. | ||
.DS_Store | ||
# Common VCS dirs | ||
.git/ | ||
.gitignore | ||
.bzr/ | ||
.bzrignore | ||
.hg/ | ||
.hgignore | ||
.svn/ | ||
# Common backup files | ||
*.swp | ||
*.bak | ||
*.tmp | ||
*.orig | ||
*~ | ||
# Various IDEs | ||
.project | ||
.idea/ | ||
*.tmproj | ||
.vscode/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
apiVersion: v2 | ||
name: srechallenge-helm | ||
description: A Helm chart for Kubernetes | ||
|
||
# A chart can be either an 'application' or a 'library' chart. | ||
# | ||
# Application charts are a collection of templates that can be packaged into versioned archives | ||
# to be deployed. | ||
# | ||
# Library charts provide useful utilities or functions for the chart developer. They're included as | ||
# a dependency of application charts to inject those utilities and functions into the rendering | ||
# pipeline. Library charts do not define any templates and therefore cannot be deployed. | ||
type: application | ||
|
||
# This is the chart version. This version number should be incremented each time you make changes | ||
# to the chart and its templates, including the app version. | ||
# Versions are expected to follow Semantic Versioning (https://semver.org/) | ||
version: 0.1.0 | ||
|
||
# This is the version number of the application being deployed. This version number should be | ||
# incremented each time you make changes to the application. Versions are not expected to | ||
# follow Semantic Versioning. They should reflect the version the application is using. | ||
# It is recommended to use it with quotes. | ||
appVersion: "1.16.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
1. Get the application URL by running these commands: | ||
{{- if .Values.ingress.enabled }} | ||
{{- range $host := .Values.ingress.hosts }} | ||
{{- range .paths }} | ||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} | ||
{{- end }} | ||
{{- end }} | ||
{{- else if contains "NodePort" .Values.service.type }} | ||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "srechallenge-helm.fullname" . }}) | ||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") | ||
echo http://$NODE_IP:$NODE_PORT | ||
{{- else if contains "LoadBalancer" .Values.service.type }} | ||
NOTE: It may take a few minutes for the LoadBalancer IP to be available. | ||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "srechallenge-helm.fullname" . }}' | ||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "srechallenge-helm.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") | ||
echo http://$SERVICE_IP:{{ .Values.service.port }} | ||
{{- else if contains "ClusterIP" .Values.service.type }} | ||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "srechallenge-helm.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") | ||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") | ||
echo "Visit http://127.0.0.1:8080 to use your application" | ||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT | ||
{{- end }} |
Oops, something went wrong.