This project is nothing special, just simply and example REST Service written in python.
This service is used as a seed project for creating a drone CI/CD pipeline.
The development environment uses Docker and Docker Compose. This makes it super easy to get up and running with a configuration that would closely mirror production.
With Docker/Compose installed, use the following steps to launch for the first time:
-
docker-compose upto start the web app. This will download and provision two containers: one running PostgreSQL and one running the Flask app. This will take a while, but once it completes subsequent launches will be much faster. (NOTE: if you are using the Vagrant VM that was provisioned in the first step, change into the/vagrantdirectory before runningdocker-compose up.) -
When
docker-compose upcompletes, the app should be accessible at http://127.0.0.1:5000.
Note, as the project tutorial progresses, you will see the value of understanding docker-compose, and docker in the usage of drone to build a pipeline. Drone mirrors the syntax of docker-compose almost exactly - with little variation
There are just a couple of configurations managed as environment variables. In the development environment, these are injected by Docker Compose and managed in the docker-compose.yml file.
DATABASE_URL- This is the connection URL for the PostgreSQL database. It is not used in the development environment.DEBUG- This toggle debug mode for the app to True/False.SECRET_KEY- This is a secret string that you make up. It is used to encrypt and verify the authentication token on routes that require authentication.
These are also injected when using the docker-compose.test.yml file.
- Application-wide settings are stored in
config.pyat the root of the repository. These items are accessible on theconfigdictionary property of theappobject. Example:debug = app.config['DEBUG'] - The directory
service/appcontains the API application - URL mapping is managed in
service/app/routes.py - Functionality is organized in packages. Example:
service/app/usersorservice/app/utils. - Tests are contained in each package. Example:
service/app/users/tests.py - The directory
integ-tests\*contains integration tests leveragingnosetests- which are to be run from a SUT system. migrations\*contains alembic migration code for the database which can also be run from the SUT system.docker\Dockerfilecontains the logic to build up a binary distribution of the service in a docker containerdocker-compose.ymlfile contains the basic services - and will allow you to bring up the environment for use.docker-compose.test.ymlfile contains the basic services plus a SUT instances - while allows for integration testing.MANIFEST.incontains distribution packaging requirements; which work withsetup.pyand therequirements.txt, andtest-requirements.txtrespectively.
Tests are ran with nose from inside the docker-compose web container:
docker-compose -f docker-compose.yml run web /env/bin/nosetests -v /app/test/unit
Tests are ran with nose from inside the docker-compose web container:
docker-compose -f docker-compose.yml run web /env/bin/nosetests -v /app/test/functional
NOTE you might need to run this twice as the DB might take a second to init...
Integration Tests are ran from a System Under Test (SUT) with nose from inside the docker-compose.test sut container:
docker-compose -f docker-compose.yml -f docker-compose.test.yml run sut /env/bin/nosetests -v /app/test/integration
This simulates making API calls from outside of the web service container. Note the web service container will use both the database and redis service containers actually simulating an e2e test as well.
Migrations for the provided models are part of the project. To generate new migrations use Flask-Migrate:
docker-compose -f docker-compose.yml -f docker-compose.test.yml run sut /env/bin/python /app/service/run.py db upgrade
docker-compose -f docker-compose.yml -f docker-compose.test.yml run sut /env/bin/python /app/service/run.py db migrate
This API uses token-based authentication. A token is obtained by registering a new user (/api/v1/user) or authenticating an existing user (/api/v1/authenticate). Once the client has the token, it must be included in the Authorization header of all requests.
POST:
/api/v1/user
Body:
{
"email": "something@email.com",
"password": "123456"
}Response:
{
"id": 2,
"token": "eyJhbGciOiJIUzI1NiIsImV4cCI6MTQxMDk2ODA5NCwiaWF0IjoxNDA5NzU4NDk0fQ.eyJpc19hZG1pbiI6ZmFsc2UsImlkIjoyLCJlbWFpbCI6InRlc3QyQHRlc3QuY29tIn0.goBHisCajafl4a93jfal0sD5pdjeYd5se_a9sEkHs"
}Status Codes:
201if successful400if incorrect data provided409if email is in use
Example:
curl -H "Content-Type: application/json" -X POST -d '{"email": "something@email.com","password": "123456"}' http://localhost:5000/api/v1/user
POST:
/api/v1/authenticate
Body:
{
"email": "something@email.com",
"password": "123456"
}Response:
{
"id": 2,
"token": "eyJhbGciOiJIUzI1NiIsImV4cCI6MTQxMDk2ODA5NCwiaWF0IjoxNDA5NzU4NDk0fQ.eyJpc19hZG1pbiI6ZmFsc2UsImlkIjoyLCJlbWFpbCI6InRlc3QyQHRlc3QuY29tIn0.goBHisCajafl4a93jfal0sD5pdjeYd5se_a9sEkHs"
}Status Codes:
200if successful401if invalid credentials
Example:
curl -H "Content-Type: application/json" -X POST -d '{"email": "something@email.com","password": "123456"}' http://localhost:5000/api/v1/authenticate
GET:
/api/v1/user
Response:
{
"id": 2,
"email": "test2@test.com",
}Status Codes:
200if successful401if not authenticated
Example:
The token show below is returned from the previous POST authorize API call
curl -H "Authorization: eyJhbGciOiJIUzI1NiIsImV4cCI6MTUwNDQ5NjcwOSwiaWF0IjoxNTAzMjg3MTA5fQ.eyJpc19hZG1pbiI6ZmFsc2UsImlkIjoxLCJlbWFpbCI6InNvbWV0aGluZ0BlbWFpbC5jb20ifQ.TcD7N62bfcDEyOzS4_8KnT9v9iQwZCJipxxtSiPf5tQ" -X GET http://localhost:5000/api/v1/user