Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Payomeke committed May 23, 2022
0 parents commit ba648a1
Show file tree
Hide file tree
Showing 18 changed files with 726 additions and 0 deletions.
77 changes: 77 additions & 0 deletions .github/workflows/ci.yml
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
50 changes: 50 additions & 0 deletions .gitignore
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/
9 changes: 9 additions & 0 deletions Dockerfile
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"]
114 changes: 114 additions & 0 deletions README.md
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/)
38 changes: 38 additions & 0 deletions app.py
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)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Flask==2.1.2
23 changes: 23 additions & 0 deletions srechallenge-helm/.helmignore
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/
24 changes: 24 additions & 0 deletions srechallenge-helm/Chart.yaml
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"
22 changes: 22 additions & 0 deletions srechallenge-helm/templates/NOTES.txt
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 }}
Loading

0 comments on commit ba648a1

Please sign in to comment.