Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Docker setup #208

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
LICENSE
README.md
__pycache__
dockerfile
docs
venv
.git
.vscode
.github
27 changes: 27 additions & 0 deletions .github/ENV_SETUP_INSTRUCTIONS_DOCKER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
BridgeInTech Development Setup Instructions (Docker)

Before you start, you need to have the following installed:
- [Docker-desktop](https://www.docker.com/products/docker-desktop)

If you have these already installed, you are ready to start.

## 1st, Fork, Clone, and Remote
1. Follow the instruction on the [Fork, Clone, and Remote](https://github.com/anitab-org/bridge-in-tech-backend/wiki/Fork,-Clone-&-Remote) page for this step.

<!--This can be removed once the changes to the BIT branch have been merged-->
## 2nd, Clone mentorship-backend for BIT
1.Follow the instruction on the mentorship system [Fork, Clone, and Remote](https://github.com/anitab-org/mentorship-backend/wiki/Fork,-Clone-&-Remote) page for this step. Make sure the two projects are cloned in the same directory.

## 3rd, Create .env file from .env.template

You can ignore all the environment variables below flask_app(included) as they have already been set up in docker. <!--TODO add guide to environment variables-->

## 4th running the app locally
Run the command `docker-compose up`.If you can use Makefiles then you can also run `make docker_dev`. If this is your first time it may take a while to download the images and get everything set up properly. Once this is complete you should be able to see the app running on http://localhost:5000 and the mentorship system running on http://localhost:4000. You can also connect to the Postgres server using `port 5432`.

## 5th Running test cases
Run the command `docker-compose -f docker-compose.test.yml up --exit-code-from bit` to run the test cases. If you can use Makefiles then you can also run `make docker_test`. Linux and MacOS support make out of the box, but you can also use makefiles on windows by installing MinGW.




18 changes: 18 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM python:latest
COPY ./requirements.txt /dockerBuild/requirements.txt
WORKDIR /dockerBuild
RUN pip install --no-cache-dir -r requirements.txt
COPY . /dockerBuild
ENV DB_TYPE=postgresql
ENV DB_USERNAME=postgres
ENV DB_PASSWORD=postgres
ENV DB_ENDPOINT=postgres:5432
ENV DB_TEST_ENDPOINT=test_postgres:5432
ENV DB_NAME=bit_schema
ENV DB_TEST_NAME=bit_schema_test
ENV POSTGRES_HOST=postgres
ENV POSTGRES_PORT=5432
ENV MS_URL=http://MS:5000
ENV FLASK_APP=run.py
ENTRYPOINT ["make"]
CMD ["docker_host_dev"]
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
dev:
python run.py
docker_host_dev:
flask run --host 0.0.0.0
python_tests:
python -m unittest discover tests
docker_test:
docker-compose -f docker-compose.test.yml up --build --exit-code-from bit --remove-orphans
docker_dev:
docker-compose up --build --remove-orphans




59 changes: 34 additions & 25 deletions app/api/request_api_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
import requests
from app import messages
from app.utils.decorator_utils import http_response_namedtuple_converter
from config import BaseConfig


BASE_MS_API_URL = "http://127.0.0.1:4000"
BASE_MS_API_URL = BaseConfig.MS_URL
AUTH_COOKIE = cookies.SimpleCookie()


def post_request(request_string, data):
request_url = f"{BASE_MS_API_URL}{request_string}"
request_url = f"{BASE_MS_API_URL}{request_string}"
try:
response = requests.post(
request_url, json=data, headers={"Accept": "application/json"}
Expand All @@ -37,55 +37,62 @@ def post_request(request_string, data):
access_expiry_cookie = response_message.get("access_expiry")
AUTH_COOKIE["Authorization"] = f"Bearer {access_token_cookie}"
AUTH_COOKIE["Authorization"]["expires"] = access_expiry_cookie
set_user = http_response_checker(get_user(AUTH_COOKIE["Authorization"].value))
set_user = http_response_checker(
get_user(AUTH_COOKIE["Authorization"].value)
)
if set_user.status_code != 200:
response_message = set_user.message
response_code = set_user.status_code
else:
response_message = {"access_token": response_message.get("access_token"), "access_expiry": response_message.get("access_expiry")}
response_message = {
"access_token": response_message.get("access_token"),
"access_expiry": response_message.get("access_expiry"),
}
logging.fatal(f"{response_message}")
return response_message, response_code


def get_user(token):
request_url = "/user"
request_url = "/user"
return get_request(request_url, token, params=None)


def get_headers(request_string, params):
if request_string == "/user":
return {"Authorization": AUTH_COOKIE["Authorization"].value, "Accept": "application/json"}
return {
"Authorization": AUTH_COOKIE["Authorization"].value,
"Accept": "application/json",
}
if request_string == "/users/verified":
return {
"Authorization": AUTH_COOKIE["Authorization"].value,
"Authorization": AUTH_COOKIE["Authorization"].value,
"search": params["search"],
"page": str(params["page"]),
"per_page": str(params["per_page"]),
"Accept": "application/json"
"Accept": "application/json",
}
if request_string == "/organizations":
return {
"Authorization": AUTH_COOKIE["Authorization"].value,
"Authorization": AUTH_COOKIE["Authorization"].value,
"name": params["name"],
"page": str(params["page"]),
"per_page": str(params["per_page"]),
"Accept": "application/json"
"Accept": "application/json",
}
return {
"Authorization": AUTH_COOKIE["Authorization"].value,
"Accept": "application/json"
"Accept": "application/json",
}


def get_request(request_string, token, params):
request_url = f"{BASE_MS_API_URL}{request_string}"
request_url = f"{BASE_MS_API_URL}{request_string}"
is_wrong_token = validate_token(token)

if not is_wrong_token:
try:
try:
response = requests.get(
request_url,
headers=get_headers(request_string, params)
request_url, headers=get_headers(request_string, params)
)
response.raise_for_status()
response_message = response.json()
Expand All @@ -110,15 +117,18 @@ def get_request(request_string, token, params):


def put_request(request_string, token, data):
request_url = f"{BASE_MS_API_URL}{request_string}"
request_url = f"{BASE_MS_API_URL}{request_string}"
is_wrong_token = validate_token(token)

if not is_wrong_token:
try:
try:
response = requests.put(
request_url,
json=data,
headers={"Authorization": AUTH_COOKIE["Authorization"].value, "Accept": "application/json"},
request_url,
json=data,
headers={
"Authorization": AUTH_COOKIE["Authorization"].value,
"Accept": "application/json",
},
)
response.raise_for_status()
response_message = response.json()
Expand Down Expand Up @@ -146,18 +156,19 @@ def validate_token(token):
if AUTH_COOKIE:
if token != AUTH_COOKIE["Authorization"].value:
return messages.TOKEN_IS_INVALID, HTTPStatus.UNAUTHORIZED



@http_response_namedtuple_converter
def http_response_checker(result):
# TO DO: REMOVE ALL IF CONDITIONS ONCE ALL BIT-MS HTTP ERROR ISSUES ON MS ARE FIXED
# TO DO: REMOVE ALL IF CONDITIONS ONCE ALL BIT-MS HTTP ERROR ISSUES ON MS ARE FIXED
# if result.status_code == HTTPStatus.OK:
# result = http_ok_status_checker(result)
# # if result.status_code == HTTPStatus.BAD_REQUEST:
# result = http_bad_request_status_checker(result)
# if result.status_code == HTTPStatus.NOT_FOUND:
# result = http_not_found_status_checker(result)
# if result.status_code == json.dumps(HTTPStatus.INTERNAL_SERVER_ERROR) and not AUTH_COOKIE:
# if result.status_code == json.dumps(HTTPStatus.INTERNAL_SERVER_ERROR) and not AUTH_COOKIE:
# # if not AUTH_COOKIE:
# return messages.TOKEN_IS_INVALID, HTTPStatus.UNAUTHORIZED
return result
Expand All @@ -182,5 +193,3 @@ def http_response_checker(result):
# # TO DO: REMOVE ONCE ISSUE#624 ON MS BACKEND IS FIXED
# if result.message == messages.WRONG_USERNAME_OR_PASSWORD:
# return result._replace(status_code = HTTPStatus.UNAUTHORIZED)


92 changes: 53 additions & 39 deletions config.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import os
from datetime import timedelta


def get_mock_email_config() -> bool:
MOCK_EMAIL = os.getenv("MOCK_EMAIL")

#if MOCK_EMAIL env variable is set
if MOCK_EMAIL:
# if MOCK_EMAIL env variable is set
if MOCK_EMAIL:
# MOCK_EMAIL is case insensitive
MOCK_EMAIL = MOCK_EMAIL.lower()
if MOCK_EMAIL=="true":

if MOCK_EMAIL == "true":
return True
elif MOCK_EMAIL=="false":
elif MOCK_EMAIL == "false":
return False
else:
else:
# if MOCK_EMAIL env variable is set a wrong value
raise ValueError(
"MOCK_EMAIL environment variable is optional if set, it has to be valued as either 'True' or 'False'"
Expand All @@ -22,11 +23,15 @@ def get_mock_email_config() -> bool:
# Default behaviour is to send the email if MOCK_EMAIL is not set
return False


class BaseConfig(object):
DEBUG = False
TESTING = False
SQLALCHEMY_TRACK_MODIFICATIONS = False

SQLALCHEMY_TRACK_MODIFICATIONS = False

# Mentorship System url
MS_URL = os.getenv("MS_URL", "http://localhost:4000")

# Flask JWT settings
JWT_ACCESS_TOKEN_EXPIRES = timedelta(weeks=1)
JWT_REFRESH_TOKEN_EXPIRES = timedelta(weeks=4)
Expand All @@ -36,7 +41,7 @@ class BaseConfig(object):
SECRET_KEY = os.getenv("SECRET_KEY", None)
BCRYPT_LOG_ROUNDS = 13
WTF_CSRF_ENABLED = True

# mail settings
MAIL_SERVER = os.getenv("MAIL_SERVER")
MAIL_PORT = 465
Expand All @@ -50,67 +55,75 @@ class BaseConfig(object):
# mail accounts
MAIL_DEFAULT_SENDER = os.getenv("MAIL_DEFAULT_SENDER")

DB_TYPE = os.getenv("DB_TYPE"),
DB_USERNAME = os.getenv("DB_USERNAME"),
DB_PASSWORD = os.getenv("DB_PASSWORD"),
DB_ENDPOINT = os.getenv("DB_ENDPOINT"),
DB_NAME = os.getenv("DB_NAME")
DB_TEST_NAME = os.getenv("DB_TEST_NAME")
DB_TYPE = os.getenv("DB_TYPE", "")
DB_USERNAME = os.getenv("DB_USERNAME", "")
DB_PASSWORD = os.getenv("DB_PASSWORD", "")
DB_ENDPOINT = os.getenv("DB_ENDPOINT", "")
DB_NAME = os.getenv("DB_NAME", "")
DB_TEST_NAME = os.getenv("DB_TEST_NAME", "")
DB_TEST_ENDPOINT = os.getenv("DB_TEST_ENDPOINT", DB_ENDPOINT)

@staticmethod
def build_db_uri(
db_type_arg = os.getenv("DB_TYPE"),
db_user_arg = os.getenv("DB_USERNAME"),
db_password_arg = os.getenv("DB_PASSWORD"),
db_endpoint_arg = os.getenv("DB_ENDPOINT"),
db_name_arg = os.getenv("DB_NAME"),
db_type_arg=DB_TYPE,
db_user_arg=DB_USERNAME,
db_password_arg=DB_PASSWORD,
db_endpoint_arg=DB_ENDPOINT,
db_name_arg=DB_NAME,
):
return f"{db_type_arg}://{db_user_arg}:{db_password_arg}@{db_endpoint_arg}/{db_name_arg}"

@staticmethod
def build_db_test_uri(
db_type_arg = os.getenv("DB_TYPE"),
db_user_arg = os.getenv("DB_USERNAME"),
db_password_arg = os.getenv("DB_PASSWORD"),
db_endpoint_arg = os.getenv("DB_ENDPOINT"),
db_name_arg = os.getenv("DB_TEST_NAME"),
db_type_arg=DB_TYPE,
db_user_arg=DB_USERNAME,
db_password_arg=DB_PASSWORD,
db_endpoint_arg=DB_TEST_ENDPOINT,
db_name_arg=DB_TEST_NAME,
):
return f"{db_type_arg}://{db_user_arg}:{db_password_arg}@{db_endpoint_arg}/{db_name_arg}"


class LocalConfig(BaseConfig):
"""Local configuration."""

DEBUG = True

# Using a local postgre database
SQLALCHEMY_DATABASE_URI = "postgresql:///bit_schema"

# SQLALCHEMY_DATABASE_URI = BaseConfig.build_db_uri()

# SQLALCHEMY_DATABASE_URI = "postgresql:///bit_schema"

SQLALCHEMY_DATABASE_URI = BaseConfig.build_db_uri()


class DevelopmentConfig(BaseConfig):
DEBUG = True

SQLALCHEMY_DATABASE_URI = BaseConfig.build_db_uri()

# SQLALCHEMY_DATABASE_URI = "postgresql://postgres:postgres@postgres:5432/bit_schema"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this hard-coded string still?



class TestingConfig(BaseConfig):
TESTING = True
MOCK_EMAIL = True

# Using a local postgre database
SQLALCHEMY_DATABASE_URI = "postgresql:///bit_schema_test"
mtreacy002 marked this conversation as resolved.
Show resolved Hide resolved

# SQLALCHEMY_DATABASE_URI = BaseConfig.build_db_test_uri()
# SQLALCHEMY_DATABASE_URI = (
# "postgresql:///bit_schema_test"
# )

SQLALCHEMY_DATABASE_URI = BaseConfig.build_db_test_uri()


class StagingConfig(BaseConfig):
"""Staging configuration."""

DEBUG = True
SQLALCHEMY_DATABASE_URI = BaseConfig.build_db_uri()


class ProductionConfig(BaseConfig):
SQLALCHEMY_DATABASE_URI = BaseConfig.build_db_uri()


def get_env_config() -> str:
flask_config_name = os.getenv("FLASK_ENVIRONMENT_CONFIG", "dev")
Expand All @@ -120,10 +133,11 @@ def get_env_config() -> str:
)
return CONFIGURATION_MAPPER[flask_config_name]


CONFIGURATION_MAPPER = {
"dev": "config.DevelopmentConfig",
"prod": "config.ProductionConfig",
"stag": "config.StagingConfig",
"local": "config.LocalConfig",
"test": "config.TestingConfig",
}
}
Loading