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

Chore/containerization #6

Merged
merged 28 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3ab0882
chore: docker-compose fixes
Skipper-116 Jan 27, 2025
0979a44
chore: fixing emr-api errors
Skipper-116 Jan 27, 2025
6297a92
chore: fixing the activesupport bug
Skipper-116 Jan 27, 2025
bad47b0
fix: dde active support
Skipper-116 Jan 27, 2025
d1affd8
chore: reconfiguring
Skipper-116 Jan 28, 2025
62c7883
chore: adding HIS Core to the mix
Skipper-116 Jan 28, 2025
02d846a
chore: restructuring emc
Skipper-116 Jan 28, 2025
d6153a7
chore: handle configuration management
Skipper-116 Jan 28, 2025
c34a9e2
chore: refinements
Skipper-116 Jan 28, 2025
0d097d5
chore: emdeck config updates
Skipper-116 Jan 31, 2025
c1db1b1
chore: volume changes
Skipper-116 Jan 31, 2025
d1f18a7
chore: remove script
Skipper-116 Jan 31, 2025
c4e0076
chore: core reworked
Skipper-116 Jan 31, 2025
7e835e1
chore: more config validations
Skipper-116 Jan 31, 2025
cddd6bb
chore: deployment streamlined
Skipper-116 Jan 31, 2025
534e3da
chore: fixing core dockerfile
Skipper-116 Jan 31, 2025
62f6515
chore: refined based on OS
Skipper-116 Jan 31, 2025
c61eb87
chore: remove scripts updated
Skipper-116 Feb 1, 2025
05ed36c
fix: docker core bugs
Skipper-116 Feb 1, 2025
cd8f522
fix: cloning
Skipper-116 Feb 3, 2025
c854c4b
chore: deployment changes
Feb 5, 2025
f767147
chore: readme updates
Feb 5, 2025
3861bc3
chore: remove script added
Feb 5, 2025
708f133
chore: readme folder and file structure
Skipper-116 Feb 5, 2025
8d0bcc8
emr_dde refactors
KytonThaundi Feb 6, 2025
814dac6
chore: handling redis connection
Skipper-116 Feb 6, 2025
6c4797c
fix: dde sidekiq initializer
Skipper-116 Feb 6, 2025
1372dc6
chore: python environment
Skipper-116 Feb 6, 2025
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ docker/mysql/*.sql
docker/portainer-data/
docker-compose.override.yml

# Node modules (if any frontend dependencies are managed locally)
# Node modules (if any emc dependencies are managed locally)
node_modules/
npm-debug.log*
yarn-debug.log*
Expand Down Expand Up @@ -53,6 +53,9 @@ setup_project.sh
*.yml
*.msi
*.conf
!version.conf

# Ignore git clone folders
tmp/
bin/
config.json
42 changes: 35 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

1. **Highly Configurable Deployment**:

- Manage services (e.g., EMR, DDE, Frontend) via a centralized `config/emdec.conf` file.
- Manage services (e.g., EMR, DDE, EMC, HIS CORE, MySQL, Redis, Portainer) via a centralized `config/emdeck.conf` file.
- Enable or disable specific services during deployment.

2. **Seamless Database Integration**:
Expand Down Expand Up @@ -37,6 +37,7 @@
- Ubuntu 20.04 or later
- Python 3.2 or later
- Docker and Docker Compose
- dotenv Python package

---

Expand All @@ -52,7 +53,25 @@ cd emdeck

### 2. Deploy the System

Run the Python deployment script:
Setup the python environment by running the following command:

```bash
python3 -m venv emdeck_venv
```

Activate the python environment by running the following command:

```bash
source emdeck_venv/bin/activate
```

Install the required python packages by running the following command:

```bash
pip3 install -r requirements.txt
```

Run the deployment script:

```bash
./scripts/start.sh
Expand All @@ -77,7 +96,7 @@ Place custom configuration files (e.g., `database.yml`, `ait.yml`) in the respec

- **DDE Configurations**: `docker/dde/config/`
- **EMR Configurations**: `docker/emr/config/`
- **EMR-Frontend Configurations**: `docker/frontend/config/`
- **EMR-Emc Configurations**: `docker/emc/config/`

#### Rules:

Expand All @@ -89,8 +108,8 @@ Place custom configuration files (e.g., `database.yml`, `ait.yml`) in the respec
```
eMDeck/
├── config/
│ ├── emdeck.conf
├── deployment.py
│ ├── emdeck.conf.example
├── version.conf
├── docker/
│ ├── docker-compose.yml
│ ├── mysql/
Expand All @@ -108,14 +127,23 @@ eMDeck/
│ ├── nginx/
│ │ ├── Dockerfile
│ │ ├── default.conf
│ ├── frontend/
│ ├── emc/
│ │ ├── Dockerfile
├── scripts/
│ ├── add_service.py
│ ├── backup.sh
│ ├── restore.sh
│ ├── clean.sh
│ ├── migrate.sh
│ ├── restore.sh
│ ├── remove.sh
│ ├── setup.sh
│ ├── start.sh
├── deployment.py
├── .gitignore
├── main.py
├── README.md
├── requirements.txt
├── SECURITY.md
```

---
Expand Down
44 changes: 18 additions & 26 deletions config/emdeck.conf.example
Original file line number Diff line number Diff line change
@@ -1,39 +1,31 @@
# eMDeck Configuration File
# Modify values as needed for each release.
# Enable/Disable Services
[DEFAULT]
ENABLE_EMR_API=true
ENABLE_EMR=true
ENABLE_DDE=true
ENABLE_FRONTEND=false
ENABLE_EMC=true
ENABLE_MYSQL=true
ENABLE_REDIS=true
ENABLE_NGINX=true
ENABLE_PORTAINER=true
ENABLE_CORE=true

# Docker Network Name
DOCKER_NETWORK=emastercard-network
# GitHub Repositories (HTTPS)
# EMR_API_REPO=https://www.github.com/HISMalawi/BHT-EMR-API.git
# DDE_REPO=https://www.github.com/HISMalawi/Demographics-Data-Exchange.git
# EMC_REPO=https://www.github.com/EGPAFMalawiHIS/emc-new-arch.git
# CORE_REPO=https://www.github.com/HISMalawi/HIS-Core-release.git

# GitHub Repositories (SSH)
# [email protected]:HISMalawi/BHT-EMR-API.git
# [email protected]:HISMalawi/Demographics-Data-Exchange.git
# [email protected]:EGPAFMalawiHIS/emc-new-arch.git
# [email protected]:HISMalawi/HIS-Core-release.git

# GitHub Repositories
[email protected]:HISMalawi/BHT-EMR-API.git
[email protected]:HISMalawi/Demographics-Data-Exchange.git
[email protected]:EGPAFMalawiHIS/emc-new-arch.git

# RAILS Configuration
RAILS_MAX_THREADS=5
RAILS_MIN_THREADS=2

# MySQL Configuration (Shared)
MYSQL_ROOT_PASSWORD=root_password

# EMR Database Configuration
EMR_DATABASE=emr_db
EMR_DATABASE_USER=emr_user
EMR_DATABASE_PASSWORD=emr_password

# DDE Database Configuration
DDE_DATABASE=dde_db
DDE_DATABASE_USER=dde_user
DDE_DATABASE_PASSWORD=dde_password
# MySQL Configuration
MYSQL_ROOT_PASSWORD="root_password"

# Portainer Admin Credentials
PORTAINER_ADMIN_USERNAME=admin
PORTAINER_ADMIN_PASSWORD=admin123
PORTAINER_ADMIN_PASSWORD="Extremely1Secure!"
5 changes: 5 additions & 0 deletions config/version.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Source Code Repositories Tags
DDE_TAG=v4.1.3
EMR_TAG=v5.4.4
EMC_TAG=v2024.Q4.R2
CORE_TAG=v2024.Q4.R3
11 changes: 0 additions & 11 deletions config/version.conf.example

This file was deleted.

58 changes: 33 additions & 25 deletions deployment.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import os
import subprocess
from configparser import ConfigParser
from dotenv import load_dotenv

class Deployment:
def __init__(self):
self.config = self.load_config()
self.config_to_env()

def load_config(self):
config = ConfigParser()
config.read("./config/emdeck.conf")
config.read("./config/version.conf")
return config

def config_to_env(self):
"""Sets the configuration values as environment variables."""
for key, value in self.config.items("DEFAULT"):
os.environ[key.upper()] = value
print("Configuration loaded successfully!")
self.load_environment()

def load_environment(self):
"""Loads the environment variables from the configuration files."""
emdeck_path = os.path.join(os.getcwd(), "config/emdeck.conf")
version_path = os.path.join(os.getcwd(), "config/version.conf")
load_dotenv(emdeck_path)
load_dotenv(version_path)

def ensure_tmp_folder(self, service_path):
"""Ensures the tmp folder exists in the service path."""
Expand All @@ -28,8 +22,15 @@ def ensure_tmp_folder(self, service_path):
def get_tag(self, repo_path, tag):
"""Fetches the latest tag from the repository."""
try:
print(f"Fetching tags for repository at {repo_path}...")
os.chdir(repo_path)
# we need to get the results of the git describe command and compare it to the tag we want to checkout
# if the tag we want to checkout is in the list of tags, we can just checkout the tag
# if the tag we want to checkout is not in the list of tags, we need to fetch it
describe_output = subprocess.run(["git", "describe"], check=True, capture_output=True)
if tag in describe_output.stdout.decode("utf-8"):
print(f"Tag {tag} already checked out.")
return
print(f"Fetching tags for repository at {repo_path}...")
subprocess.run(["git", "fetch", "--depth=1", "origin", "tag", tag], check=True)
subprocess.run(["git", "checkout", tag], check=True)
subprocess.run(["git", "describe", "--tags"], check=True)
Expand All @@ -44,11 +45,11 @@ def clone_or_update_repo(self, service, repo, tag_format):
"""Clones the repository if it doesn't exist, otherwise fetches and checks out the latest tag."""
service_path = f"docker/{service}"
tmp_path = self.ensure_tmp_folder(service_path)
latest_tag = self.config.get("DEFAULT", f"{service.upper()}_TAG")
latest_tag = os.environ.get(f"{service.upper()}_TAG")

if os.path.exists(service_path + "tmp/.git"):
if os.path.exists(service_path + "/tmp/.git"):
print(f"Repository for {service} already exists. Checking the current tag...")
self.get_tag(service_path + "tmp", latest_tag)
self.get_tag(service_path + "/tmp", latest_tag)
else:
print(f"Cloning {service} repository...")
subprocess.run(["git", "clone", "--depth=1", "--single-branch", repo, tmp_path], check=True)
Expand All @@ -57,12 +58,16 @@ def clone_or_update_repo(self, service, repo, tag_format):
def clone_repos(self):
"""Clones or updates repositories based on the configuration."""
repos = [
("emr", self.config.get("DEFAULT", "EMR_API_REPO"), "semantic"),
("dde", self.config.get("DEFAULT", "DDE_REPO"), "semantic"),
("frontend", self.config.get("DEFAULT", "FRONTEND_REPO"), "quarterly"),
("emr", os.environ.get("EMR_API_REPO"), "semantic"),
("dde", os.environ.get("DDE_REPO"), "semantic"),
("emc", os.environ.get("EMC_REPO"), "quarterly"),
('core', os.environ.get("CORE_REPO"), "quarterly"),
]
for service, repo, tag_format in repos:
self.clone_or_update_repo(service, repo, tag_format)
if os.environ.get(f"ENABLE_{service.upper()}").lower() == "true":
self.clone_or_update_repo(service, repo, tag_format)
else:
print(f"Skipping {service} repository (disabled in configuration)")

def deploy(self):
"""
Expand All @@ -71,12 +76,15 @@ def deploy(self):
- Copies necessary configurations.
- Builds and starts the Docker Compose services.
"""
print("Starting deployment...")
self.clone_repos()
try:
print("Starting deployment...")
self.clone_repos()
print("Building and starting Docker Compose services...")
subprocess.run(["docker-compose", "-f", "docker/docker-compose.yml", "up", "-d"], check=True)
print("Deployment completed successfully!")
except subprocess.CalledProcessError as e:
print(f"Error during deployment: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
exit(1)

19 changes: 19 additions & 0 deletions docker/core/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM nginx:stable

# Create the directory for the application
RUN mkdir /opt/CORE

# Set the working directory
WORKDIR /opt/CORE

# Copy the application code from the tmp folder
COPY ./tmp/ /usr/share/nginx/html

# Copy configuration files (excluding *.example files)
COPY ./config/ /usr/share/nginx/html/

# Expose the Nginx port
EXPOSE 80

# Start Nginx
CMD ["nginx", "-g", "daemon off;"]
45 changes: 45 additions & 0 deletions docker/core/config/config.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"apiURL": "127.0.0.1",
"apiPort": "8081",
"apiProtocol": "http",
"appConf": {
"devMode": true,
"enableVersionLocking": true,
"promptFullScreenDialog": false,
"showUpdateNotifications": true,
"dataCaching": true
},
"apps": [
{ "name": "TB", "available": true },
{ "name": "EMC", "available": true },
{ "name": "ART", "available": true },
{ "name": "LOS", "available": true },
{ "name": "OPD", "available": true },
{ "name": "ANC", "available": true },
{ "name": "CxCa", "available": true },
{ "name": "SPINE", "available": true },
{ "name": "AETC", "available": true },
{ "name": "RADIOLOGY", "available": true },
{ "name": "CRVS", "available": true },
{ "name": "Registration", "available": true}
],
"otherApps": [
{ "name": "Test App", "IP": "127.0.0.1", "port": "8080", "protocol": "http"}
],
"platformProfiles": {
"Desktop": {
"profileName": "Desktop",
"fileExport": "WEB",
"scanner": "BARCODE_SCANNER",
"printer": "WEB",
"keyboard": "NATIVE_AND_HIS_KEYBOARD"
},
"Mobile" : {
"profileName": "Mobile",
"fileExport": "FILE_SYSTEM",
"scanner": "CAMERA_SCANNER",
"printer": "BLUETOOTH",
"keyboard": "HIS_KEYBOARD_ONLY"
}
}
}
Loading