diff --git a/README.md b/README.md index 11eb790..de8360e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ :bar_chart:  There are currently **834** questions -:warning:  These are not interview questions and most of them shouldn't be used as interview questions. Please read [Q&A](common-qa.md) for more details +:warning:  You can use these for preparing for an interview but most of the questions and exercises don't represent an actual interview. Please read [Q&A](common-qa.md) for more details :thought_balloon:  If you wonder "How to prepare for a DevOps interview?", you might want to read some of my suggestions [here](prepare_for_interview.md) @@ -6125,11 +6125,12 @@ Below you can find several exercises * [Jenkins: writing pipelines](exercises/jenkins_pipelines.md) * [CI for open source project](exercises/ci_for_open_source_project.md) * [Flask, Containers and CI](exercises/flask_container_ci/README.md) +* [Flask, Containers and CI 2](exercises/flask_container_ci2/README.md) ## Credits -Thanks to all of our amazing [contributors](https://github.com/bregman-arie/devops-exercises/graphs/contributors) who make it easy for everyone to learn and prepare to their interviews. +Thanks to all of our amazing [contributors](https://github.com/bregman-arie/devops-exercises/graphs/contributors) who make it easy for everyone to learn new things :) Logos credits can be found [here](credits.md) diff --git a/exercises/flask_container_ci/README.md b/exercises/flask_container_ci/README.md index e1ce196..35c85ee 100644 --- a/exercises/flask_container_ci/README.md +++ b/exercises/flask_container_ci/README.md @@ -1,6 +1,8 @@ Your mission, should you choose to accept it, involves fixing the app in this directory, containerize it and set up a CI for it. Please read carefully all the instructions. +If any of the following steps is not working, it is expected from you to fix them + ## Installation 1. Create a virtual environment with `python3 -m venv challenge_venv` @@ -9,8 +11,6 @@ Please read carefully all the instructions. ## Run the app -If any of the following steps is not working, it is expected from you to fix them - 1. Move to `challenges/flask_container_ci` directory, if you are not already there 1. Run `export FLASK_APP=app/main.py` 1. To run the app execute `flask run`. If it doesn't works, fix it diff --git a/exercises/flask_container_ci/app/app.py b/exercises/flask_container_ci/app/app.py deleted file mode 100644 index e143571..0000000 --- a/exercises/flask_container_ci/app/app.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 - -from flask import Flask -from flask import make_response - -import json -from werkzeug.exceptions import NotFound - - -app = Flask(__name__) - -with open("./users.json", "r") as f: - users = json.load(f) - - -@app.routee("/", methods=['GET']) -def index(): - return pretty_json({ - "resources": { - "users": "/users", - "user": "/users/", - }, - "current_uri": "/" - }) - - -@app.route("/users", methods=['GET']) -def all_users(): - return pretty_json(users) - - -@app.route("/users/", methods=['GET']) -def user_data(username): - if username not in users: - raise NotFound - - return pretty_json(users[username]) - - -@app.route("/users//something", methods=['GET']) -def user_something(username): - raise NotImplementedError() - - -def pretty_json(arg): - response = make_response(json.dumps(arg, sort_keys=True, indent=4)) - response.headers['Content-type'] = "application/json" - return response - - -if __name__ == "__main__": - app.run(port=5000) diff --git a/exercises/flask_container_ci2/README.md b/exercises/flask_container_ci2/README.md new file mode 100644 index 0000000..5cb93ae --- /dev/null +++ b/exercises/flask_container_ci2/README.md @@ -0,0 +1,79 @@ +Your mission, should you choose to accept it, involves developing an app, containerize it and set up a CI for it. +Please read carefully all the instructions. + +If any of the following steps is not working, it is expected from you to fix them + +## Installation + +1. Create a virtual environment with `python3 -m venv challenge_venv` +2. Activate it with `source challenge_venv/bin/activate` +3. Install the requirements in this directory `pip install -r requirements.txt` + +## Run the app + +1. Move to `challenges/flask_container_ci` directory, if you are not already there +1. Run `export FLASK_APP=app/main.py` +1. To run the app execute `flask run`. If it doesn't works, fix it +3. Access `http://127.0.0.1:5000`. You should see the following + +``` +{ + "current_uri": "/", + "example": "/matrix/'123n456n789'", + "resources": { + "column": "/columns//", + "matrix": "/matrix/", + "row": "/rows//" + } +} +``` + +4. You should be able to access any of the resources and get the following data: + +* /matrix/\ + + for example, for /matrix/123n456n789 the user will get: + + 1 2 3 + 4 5 6 + 7 8 9 + +* /matrix/\/\ + + for example, for /matrix/123n456n789/2 the user will get: + + 2 + 5 + 8 + +* /matrix/\/\ + + for example, for /matrix/123n456n789/1 the user will get: + + 1 2 3 + +## Containers + +Using Docker or Podman, containerize the flask app so users can run the following two commands: + +``` +docker build -t app:latest /path/to/Dockerfile +docker run -d -p 5000:5000 app +``` + +1. You can use any image base you would like +2. Containerize only what you need for running the application, nothing else. + +## CI + +Great, now that we have a working app and also can run it in a container, let's set up a CI for it so it won't break again in the future +In current directory you have a file called tests.py which includes the tests for the app. What is required from you, is: + +1. Write a CI pipeline that will run the app tests. You are free to choose whatever CI system or service you prefer. Use `python tests.py` for running the tests. +2. There should be some kind of test for the Dockerfile you wrote +3. Add additional unit test (or any other level of tests) for testing the app + +### Guidelines + +* Except the app functionality, you can change whatever you want - structure, tooling, libraries, ... If possible, add `notes.md` file which explains reasons, logic, thoughts and anything else you would like to share +* The CI part should include the source code for the pipeline definition diff --git a/exercises/flask_container_ci2/app/__init__.py b/exercises/flask_container_ci2/app/__init__.py new file mode 100644 index 0000000..5c9136a --- /dev/null +++ b/exercises/flask_container_ci2/app/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python +# coding=utf-8 diff --git a/exercises/flask_container_ci2/app/config.py b/exercises/flask_container_ci2/app/config.py new file mode 100644 index 0000000..33e417f --- /dev/null +++ b/exercises/flask_container_ci2/app/config.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +# coding=utf-8 + +import os + +basedir = os.path.abspath(os.path.dirname(__file__)) + +SECRET_KEY = 'shhh' +CSRF_ENABLED = True + +SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db') diff --git a/exercises/flask_container_ci2/app/main.py b/exercises/flask_container_ci2/app/main.py new file mode 100644 index 0000000..b06a46b --- /dev/null +++ b/exercises/flask_container_ci2/app/main.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# coding=utf-8 + +from flask import Flask +from flask import make_response + +import json + +app = Flask(__name__) + + +@app.routee("/", methods=['GET']) +def index(): + return pretty_json({ + "resources": { + "matrix": "/matrix/", + "column": "/columns//", + "row": "/rows//", + }, + "current_uri": "/", + "example": "/matrix/'123n456n789'", + }) + + +@app.route("/matrix/", methods=['GET']) +def matrix(matrix): + # TODO: return matrix, each row in a new line + pass + + +@app.route("/matrix//", methods=['GET']) +def column(matrix, column_number): + # TODO: return column based on given column number + pass + + +@app.route("/matrix//", methods=['GET']) +def row(matrix, row_number): + # TODO: return row based on given row number + pass + + +def pretty_json(arg): + response = make_response(json.dumps(arg, sort_keys=True, indent=4)) + response.headers['Content-type'] = "application/json" + return response + + +if __name__ == "__main__": + app.run(port=5000) diff --git a/exercises/flask_container_ci2/app/tests.py b/exercises/flask_container_ci2/app/tests.py new file mode 100644 index 0000000..6fa91db --- /dev/null +++ b/exercises/flask_container_ci2/app/tests.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# coding=utf-8 + +import os +import unittest + +from config import basedir +from app import app +from app import db + + +class TestCase(unittest.TestCase): + + def setUp(self): + app.config['TESTING'] = True + app.config['WTF_CSRF_ENABLED'] = False + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join( + basedir, 'test.db') + self.app = app.test_client() + db.create_all() + + def tearDown(self): + db.session.remove() + db.drop_all() + + +if __name__ == '__main__': + unittest.main() diff --git a/exercises/flask_container_ci2/requirements.txt b/exercises/flask_container_ci2/requirements.txt new file mode 100644 index 0000000..7e10602 --- /dev/null +++ b/exercises/flask_container_ci2/requirements.txt @@ -0,0 +1 @@ +flask diff --git a/exercises/flask_container_ci2/tests.py b/exercises/flask_container_ci2/tests.py new file mode 100644 index 0000000..8e70455 --- /dev/null +++ b/exercises/flask_container_ci2/tests.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# coding=utf-8 + +import unittest + +from app import main + + +class TestCase(unittest.TestCase): + + def setUp(self): + self.app = main.app.test_client() + + def test_main_page(self): + response = self.app.get('/', follow_redirects=True) + self.assertEqual(response.status_code, 200) + + def test_matrix(self): + response = self.app.get('/matrix/123n459,789', follow_redirects=True) + # Change when the matrix route is fixed and returning the actual matrix + self.assertEqual(response.status_code, 500) + + +if __name__ == '__main__': + unittest.main()