diff --git a/.github/workflows/publish-docker-images.yml b/.github/workflows/publish-docker-images.yml new file mode 100644 index 00000000..56937861 --- /dev/null +++ b/.github/workflows/publish-docker-images.yml @@ -0,0 +1,64 @@ +# +name: Publish Docker images + +# Configures this workflow to run every time a change is pushed to the branch. +on: + push: + branches: ['main'] + +# Custom environment variables for the workflow. +env: + REGISTRY: atnog-harbor.av.it.pt + PROJECT: route25 + latest-branch: main + +# Jobs in this workflow. +jobs: + build-and-push-docker-images: + runs-on: ubuntu-24.04 + + # Matrix to run job multiple times with different configurations. + strategy: + fail-fast: true # Stops the job as soon as one of the matrix entries fails. + matrix: + include: + - dir: backend + file: Dockerfile.backend + repository: backend + - dir: backend + file: Dockerfile.report + repository: report + + # Steps in this job. + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Log in to the Registry + uses: docker/login-action@v3.3.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5.5.1 + with: + images: ${{ env.REGISTRY }}/${{ env.PROJECT }}/${{ matrix.repository }} + tags: | + type=semver,pattern={{version}} + type=ref,event=branch + type=sha + + - name: Build and push Docker image + uses: docker/build-push-action@v6.8.0 + with: + context: ${{ matrix.dir }} + file: ${{ matrix.dir }}/${{ matrix.file }} + tags: | # Tags for the Docker image. latest for the main branch, branch name for the lastest of each branch, and commit hash for each commit. + ${{ github.ref_name == env.latest-branch && format('{0}/{1}/{2}:latest', env.REGISTRY, env.PROJECT, matrix.repository) || '' }} + ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + push: true + \ No newline at end of file diff --git a/.github/workflows/publish-helm-chart.yml b/.github/workflows/publish-helm-chart.yml new file mode 100644 index 00000000..3ba954cc --- /dev/null +++ b/.github/workflows/publish-helm-chart.yml @@ -0,0 +1,85 @@ +# +name: Publish Helm Chart + +# Configures this workflow to run every time a change is pushed to the branch. +on: + push: + branches: ['main'] + +# Custom environment variables for the workflow. +env: + REGISTRY: atnog-harbor.av.it.pt + PROJECT: route25 + +# Jobs in this workflow. +jobs: + package-and-push-helm-chart: + runs-on: ubuntu-24.04 + + # Matrix to run job multiple times with different configurations. + strategy: + fail-fast: true # Stops the job as soon as one of the matrix entries fails. + matrix: + include: + - dir: helm-chart + + # Steps in this job. + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Log in to the Registry + uses: docker/login-action@v3.3.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Helm Chart Package and Push + shell: bash + run: | + # Package the Helm Chart and capture the path + CHART_PATH=$(helm package ${{ matrix.dir }} -u | awk '{print $NF}') + + # Run the helm push command and capture both stdout and stderr + OUTPUT=$(helm push $CHART_PATH oci://${{ env.REGISTRY }}/${{ env.PROJECT }} 2>&1) + echo "Raw Output: $OUTPUT" + + # Check if the helm push command was successful + if [ $? -ne 0 ]; then + echo "Helm push failed." + exit 1 + fi + + # Extract the Digest from the output + DIGEST=$(echo "$OUTPUT" | grep "Digest:" | awk '{print $2}') + + # Extract the Chart Name from the output + CHART_NAME=$(echo "$OUTPUT" | grep "Pushed:" | awk '{print $2}' | awk -F '/' '{print $NF}'| cut -d':' -f1) + + # Print the results + echo "Digest: $DIGEST" + echo "Chart Name: $CHART_NAME" + + # Add tags to the Helm Chart + for tag in ${{ github.ref_name == 'main' && 'latest' || '' }} ${{ github.ref_name }} ${{ github.sha }} ; do + # if tag is '' or empty, skip the tagging + if [ -z "$tag" ]; then + continue + fi + + echo "Tagging $CHART_NAME with $tag" + + curl -u '${{ secrets.REGISTRY_USERNAME }}:${{ secrets.REGISTRY_PASSWORD }}' -X 'POST' \ + "https://${{ env.REGISTRY }}/api/v2.0/projects/${{ env.PROJECT }}/repositories/$CHART_NAME/artifacts/$DIGEST/tags" \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "id": 0, + "repository_id": 0, + "artifact_id": 0, + "name": "'$tag'", + "immutable": true + }' + + done diff --git a/Makefile b/Makefile index a592fdab..01d635e4 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,11 @@ SHELL := /bin/bash +# Function to determine Docker Compose command +define docker_compose_cmd + $(if $(shell command -v docker-compose 2> /dev/null),docker-compose,$(if $(shell command -v docker compose 2> /dev/null),docker compose,)) +endef + + # Prepare DEVELOPMENT environment prepare-dev-env: @@ -9,40 +15,40 @@ prepare-dev-env: # docker-compose TASKS up: - docker-compose --profile dev up + $(call docker_compose_cmd) --profile dev up upd: - docker-compose --profile dev up -d + docker compose --profile dev up -d debug-up: - docker-compose --profile debug up + $(call docker_compose_cmd) --profile debug up debug-upd: - docker-compose --profile debug up -d + $(call docker_compose_cmd) --profile debug up -d down: - docker-compose down + $(call docker_compose_cmd) --profile dev down down-v: # also removes volumes - docker-compose down -v + $(call docker_compose_cmd) --profile dev down -v stop: - docker-compose stop + $(call docker_compose_cmd) stop build: - docker-compose --profile debug build + $(call docker_compose_cmd) --profile debug build build-no-cache: - docker-compose --profile debug build --no-cache + $(call docker_compose_cmd) --profile debug build --no-cache logs: - docker-compose logs -f + $(call docker_compose_cmd) logs -f logs-backend: - docker-compose logs -f backend + $(call docker_compose_cmd) logs -f backend logs-mongo: - docker-compose logs -f mongo + $(call docker_compose_cmd) logs -f mongo ps: docker ps -a @@ -62,8 +68,8 @@ db-init: #simple scenario with 3 UEs, 3 Cells, 1 gNB db-reset: - docker-compose exec db psql -h localhost -U postgres -d app -c 'TRUNCATE TABLE cell, gnb, monitoring, path, points, ue RESTART IDENTITY;' - docker-compose exec mongo /bin/bash -c 'mongo fastapi -u $$MONGO_USER -p $$MONGO_PASSWORD --authenticationDatabase admin --eval "db.dropDatabase();"' + $(call docker_compose_cmd) exec db psql -h localhost -U postgres -d app -c 'TRUNCATE TABLE cell, gnb, monitoring, path, points, ue RESTART IDENTITY;' + $(call docker_compose_cmd) exec mongo /bin/bash -c 'mongo fastapi -u $$MONGO_USER -p $$MONGO_PASSWORD --authenticationDatabase admin --eval "db.dropDatabase();"' db-reinit: db-reset db-init @@ -72,4 +78,4 @@ db-reinit: db-reset db-init #Individual logs logs-location: - docker-compose logs -f backend 2>&1 | grep -E "(handovers|monitoringType|'ack')" \ No newline at end of file + $(call docker_compose_cmd) logs -f backend 2>&1 | grep -E "(handovers|monitoringType|'ack')" \ No newline at end of file diff --git a/README.md b/README.md index 42e3780c..43bb6d80 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ >This is a local implentation of the NEF emulator proposed by [EVOLVED-5G](https://5g-ppp.eu/evolved-5g-builds-the-first-3gpp-nef-emulator-to-support-smes-on-5g-programmability/). ## ⚙ Setup locally +### Docker Compose + **Host prerequisites**: `docker`, `docker-compose 1.29.2`, `build-essential`\*, `jq`\*\* After cloning the repository, there are 4 more steps to do. For convinience, we have created a [`Makefile`](Makefile) that contains a command for each step + several common `docker-compose` tasks which you may find handy in the future. @@ -37,6 +39,19 @@ make db-init > \*\* 💡 Info: *The shell script used at step 4 (for adding test data) uses `jq` which is a lightweight and flexible command-line JSON processor. You can install it with `apt install jq`* +### k8s + +**Host prerequisites**: `docker`, `docker registry`\*, `kubernetes cluster`, `helm` + +After cloning the repository, there are more X steps to do. For convinience, we have created a bash script `run-helm.sh` that automatically builds the docker images, pushes them to the docker registry, and installs the helm chart. + +```bash +./run-helm.sh -n -r -d +``` +docker run -d -p 5000:5000 --name registry registry:2.7 + +>\* 💡 Info: *The default docker registry used in the helm values is a local docker registry created using `docker run -d -p 5000:5000 --name registry registry:2.7`.* + ### Try out your setup After the containers are up and running: @@ -203,4 +218,4 @@ Three possible ways to achieve the above approach: 3. with **docker network connect**, try adding your container to the bridge network: - docker network connect BRIDGE_NAME NETAPP_NAME \ No newline at end of file + docker network connect BRIDGE_NAME NETAPP_NAME diff --git a/backend/app/app/api/api_v1/endpoints/broker.py b/backend/app/app/api/api_v1/endpoints/broker.py index c056866b..bcea1613 100644 --- a/backend/app/app/api/api_v1/endpoints/broker.py +++ b/backend/app/app/api/api_v1/endpoints/broker.py @@ -7,6 +7,7 @@ from app.schemas import SinusoidalParameters import ast # Import the ast module from .qosMonitoring import signal_param_change +import os router = APIRouter() background_task = None @@ -57,7 +58,7 @@ def execute_custom_function_from_file_content(self, file_content): def run(self): try: # Connect to RabbitMQ server - self.connection = pika.BlockingConnection(pika.ConnectionParameters('rabbitmq')) + self.connection = pika.BlockingConnection(pika.ConnectionParameters(os.environ.get("RABBITMQ_HOST", "rabbitmq"))) self.channel = self.connection.channel() self.channel.queue_declare(queue='my_queue') diff --git a/backend/app/app/api/api_v1/endpoints/qosMonitoring.py b/backend/app/app/api/api_v1/endpoints/qosMonitoring.py index f9b1aff6..4d6a6b1f 100644 --- a/backend/app/app/api/api_v1/endpoints/qosMonitoring.py +++ b/backend/app/app/api/api_v1/endpoints/qosMonitoring.py @@ -13,6 +13,7 @@ from .qosInformation import qos_reference_match from .utils import ReportLogging import pika +import os router = APIRouter() router.route_class = ReportLogging @@ -174,7 +175,7 @@ def read_subscription( if change_behavior: # Create a connection to the RabbitMQ server - connection = pika.BlockingConnection(pika.ConnectionParameters('rabbitmq')) + connection = pika.BlockingConnection(pika.ConnectionParameters(os.environ.get("RABBITMQ_HOST", "rabbitmq"))) channel = connection.channel() channel.queue_declare(queue='my_queue') diff --git a/backend/app/app/db/init_simple.sh b/backend/app/app/db/init_simple.sh index f1473619..ea260b9e 100755 --- a/backend/app/app/db/init_simple.sh +++ b/backend/app/app/db/init_simple.sh @@ -1,13 +1,50 @@ #!/bin/bash -PORT=8888 URL=http://localhost +PORT=8888 REPORT_PORT=3000 +FIRST_SUPERUSER="admin@my-email.com" +FIRST_SUPERUSER_PASSWORD="pass" set -a # automatically export all variables source .env set +a +# help +for arg in "$@"; do + if [ "$arg" == "--help" ]; then + echo "Usage: cmd [-h URL] [-p PORT] [-r REPORT_PORT] [-n FIRST_SUPERUSER] [-u FIRST_SUPERUSER_PASSWORD] [--help]" + exit 0 + fi +done + +# get opts +while getopts ":h:p:r:n:u:s:" opt; do + case ${opt} in + h ) + URL=$OPTARG + ;; + p ) + PORT=$OPTARG + ;; + r ) + REPORT_PORT=$OPTARG + ;; + u ) + FIRST_SUPERUSER=$OPTARG + ;; + # Secret + s ) + FIRST_SUPERUSER_PASSWORD=$OPTARG + ;; + \? ) + echo "Invalid option: -$OPTARG" >&2 + echo "Usage: cmd [-h URL] [-p PORT] [-r REPORT_PORT] [-n FIRST_SUPERUSER] [-u FIRST_SUPERUSER_PASSWORD] [--help]" + exit 1 + ;; + esac +done + printf '\n==================================================\n' printf 'Create Report' printf '\n==================================================\n' @@ -452,12 +489,12 @@ curl -X 'POST' \ "path": 2 }' -printf '\n==================================================\n' -printf 'Delete Report' -printf '\n==================================================\n' +# printf '\n==================================================\n' +# printf 'Delete Report' +# printf '\n==================================================\n' -curl -X 'DELETE' \ - "${URL}:${REPORT_PORT}/report" \ +# curl -X 'DELETE' \ +# "${URL}:${REPORT_PORT}/report" \ -printf '\n==================================================\n\n' +# printf '\n==================================================\n\n' diff --git a/backend/app/app/db/session.py b/backend/app/app/db/session.py index 7b5b00be..42a41de7 100644 --- a/backend/app/app/db/session.py +++ b/backend/app/app/db/session.py @@ -2,9 +2,10 @@ from sqlalchemy.orm import sessionmaker from pymongo import MongoClient from app.core.config import settings +import os engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True, pool_size=150, max_overflow=20) #Create a db URL for SQLAlchemy in core/config.py/ Settings class SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) #Each instance is a db session -client = MongoClient("mongodb://mongo:27017", username='root', password='pass') +client = MongoClient(os.environ.get("MONGO_URL", "mongodb://mongo:27017"), username=os.environ.get("MONGO_USER", "root"), password=os.environ.get("MONGO_PASSWORD", "pass")) diff --git a/helm-chart/Chart.yaml b/helm-chart/Chart.yaml new file mode 100644 index 00000000..781c529e --- /dev/null +++ b/helm-chart/Chart.yaml @@ -0,0 +1,8 @@ +name: nef-emulator +description: Helm Chart for NEF emulator +version: 0.0.1 +apiVersion: v2 +keywords: + - nef-emulator +sources: +home: diff --git a/helm-chart/README.md b/helm-chart/README.md new file mode 100644 index 00000000..f4d56879 --- /dev/null +++ b/helm-chart/README.md @@ -0,0 +1 @@ +# NEF emulator Helm Chart diff --git a/helm-chart/templates/backend/deployment.yaml b/helm-chart/templates/backend/deployment.yaml new file mode 100644 index 00000000..6c06aede --- /dev/null +++ b/helm-chart/templates/backend/deployment.yaml @@ -0,0 +1,57 @@ +{{- if and .Values.backend.enabled .Values.rabbitmq.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-{{ .Values.backend.name }} +spec: + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + spec: + # depends on rabbitmq + initContainers: + - name: wait-for-rabbitmq + image: busybox + command: [ 'sh', '-c', 'until wget http://guest:guest@{{ .Release.Name }}-{{ .Values.rabbitmq.name }}.{{ .Release.Namespace }}.svc.cluster.local:{{ index .Values.rabbitmq.service.ports 1 "port" }}/api/aliveness-test/%2F; do echo waiting for rabbitmq; sleep 2; done;' ] + containers: + - name: {{ .Values.backend.name }} + image: {{ .Values.backend.deployment.image }} + command: ["/start-reload.sh"] + {{- if .Values.backend.deployment.ports }} + ports: + {{- toYaml .Values.backend.deployment.ports | nindent 12 }} + {{- end }} + volumeMounts: + # - name: backend-app + # mountPath: /app + - name: shared-data + mountPath: /shared + env: + - name: SERVER_NAME + valueFrom: + configMapKeyRef: + name: {{ .Release.Name }}-{{ .Values.configName }} + key: DOMAIN + - name: SERVER_HOST + value: "https://$(SERVER_NAME)" + - name: MONGO_URL + value: mongodb://{{ .Release.Name }}-{{ .Values.mongo.name }}:{{ index .Values.mongo.service.ports 0 "port" }}/ + - name: RABBITMQ_HOST + value: {{ .Release.Name }}-{{ .Values.rabbitmq.name }} + envFrom: + - secretRef: + name: {{ .Release.Name }}-{{ .Values.secretsName }} + - configMapRef: + name: {{ .Release.Name }}-{{ .Values.configName }} + volumes: + # - name: backend-app + # persistentVolumeClaim: + # claimName: backend-app + - name: shared-data + persistentVolumeClaim: + claimName: shared-data +{{- end }} diff --git a/helm-chart/templates/backend/service.yaml b/helm-chart/templates/backend/service.yaml new file mode 100644 index 00000000..691b18a4 --- /dev/null +++ b/helm-chart/templates/backend/service.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.backend.enabled .Values.rabbitmq.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.backend.name }} +spec: + type: {{ .Values.backend.service.type }} + {{- if .Values.backend.service.ports }} + ports: + {{- toYaml .Values.backend.service.ports | nindent 4 }} + {{- end }} + selector: + app: {{ .Release.Name }}-{{ .Values.backend.name }} +{{- end }} \ No newline at end of file diff --git a/helm-chart/templates/backend/volume.yaml b/helm-chart/templates/backend/volume.yaml new file mode 100644 index 00000000..696ac0d5 --- /dev/null +++ b/helm-chart/templates/backend/volume.yaml @@ -0,0 +1,12 @@ +{{- if and .Values.backend.enabled .Values.rabbitmq.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: shared-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +{{- end }} diff --git a/helm-chart/templates/configmap.yaml b/helm-chart/templates/configmap.yaml new file mode 100644 index 00000000..9c773d63 --- /dev/null +++ b/helm-chart/templates/configmap.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-{{ .Values.configName }} +data: + # ENVIROMENT VARIABLES + DOMAIN: {{ .Release.Name }}-{{ .Values.backend.name }} + DOCKER_IMAGE_BACKEND: "backend" + DOCKER_IMAGE_FRONTEND: "frontend" + DOCKER_IMAGE_REPORT: "report" + DOCKER_IMAGE_PRODUCER: "producer" + # Backend + SERVER_NAME: "" + SERVER_HOST: "{{ .Release.Name }}-{{ .Values.backend.name }}" + SERVER_PORT: '{{ index .Values.backend.deployment.ports 0 "containerPort" }}' + BACKEND_CORS_ORIGINS: '["https://5g-api-emulator.medianetlab.eu","http://localhost", "http://{{ .Release.Name }}-{{ .Values.backend.name }}"]' + PROJECT_NAME: "NEF_Emulator" + FIRST_SUPERUSER: "admin@my-email.com" + SMTP_TLS: "True" + SMTP_PORT: "465" + SMTP_HOST: "mail.host.com" + SMTP_USER: "user" + EMAILS_FROM_EMAIL: "user@my-email.com" + SENTRY_DSN: "" + USERS_OPEN_REGISTRATION: "true" + + # Postgres + # info: POSTGRES_USER value ('postgres') is hard-coded in /pgadmin/servers.json + POSTGRES_SERVER: {{ .Release.Name }}-{{ .Values.postgres.name }} + POSTGRES_USER: "postgres" + POSTGRES_DB: "app" + + # PgAdmin + PGADMIN_LISTEN_PORT: "{{ index .Values.pgadmin.deployment.ports 0 "containerPort" }}" + PGADMIN_DEFAULT_EMAIL: "admin@my-email.com" + + # Mongo + MONGO_USER: "root" + + # MongoExpress + MONGO_EXPRESS_ENABLE_ADMIN: "true" + + #Report + REPORT_PATH: "../shared/report.json" + \ No newline at end of file diff --git a/helm-chart/templates/mongo-express/deployment.yaml b/helm-chart/templates/mongo-express/deployment.yaml new file mode 100644 index 00000000..b44549ca --- /dev/null +++ b/helm-chart/templates/mongo-express/deployment.yaml @@ -0,0 +1,50 @@ +{{- if and .Values.mongo.enabled .Values.mongoExpress.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-{{ .Values.mongoExpress.name }} +spec: + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.mongoExpress.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.mongoExpress.name }} + spec: + # depends on mongo + initContainers: + - name: wait-for-mongo + image: {{ .Values.mongo.deployment.image }} + command: ['sh', '-c', 'until mongo --host {{ .Release.Name }}-{{ .Values.mongo.name }}.{{ .Release.Namespace }}.svc.cluster.local --port {{ index .Values.mongo.service.ports 0 "port" }} --eval "print(\"waited for connection\")"; do echo waiting for mongo; sleep 2; done;'] + containers: + - name: {{ .Values.mongoExpress.name }} + image: {{ .Values.mongoExpress.deployment.image }} + {{- if .Values.mongoExpress.deployment.ports }} + ports: + {{- toYaml .Values.mongoExpress.deployment.ports | nindent 12 }} + {{- end }} + env: + - name: ME_CONFIG_MONGODB_ADMINUSERNAME + valueFrom: + configMapKeyRef: + name: {{ .Release.Name }}-{{ .Values.configName }} + key: MONGO_USER + - name: ME_CONFIG_MONGODB_ADMINPASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-{{ .Values.secretsName }} + key: MONGO_PASSWORD + - name: ME_CONFIG_MONGODB_URL + value: mongodb://$(ME_CONFIG_MONGODB_ADMINUSERNAME):$(ME_CONFIG_MONGODB_ADMINPASSWORD)@{{ .Release.Name }}-{{ .Values.mongo.name }}:{{ index .Values.mongo.service.ports 0 "port" }}/ + - name: ME_CONFIG_MONGODB_ENABLE_ADMIN + valueFrom: + configMapKeyRef: + name: {{ .Release.Name }}-{{ .Values.configName }} + key: MONGO_EXPRESS_ENABLE_ADMIN + envFrom: + - secretRef: + name: {{ .Release.Name }}-{{ .Values.secretsName }} + - configMapRef: + name: {{ .Release.Name }}-{{ .Values.configName }} +{{- end }} \ No newline at end of file diff --git a/helm-chart/templates/mongo-express/service.yaml b/helm-chart/templates/mongo-express/service.yaml new file mode 100644 index 00000000..450526b6 --- /dev/null +++ b/helm-chart/templates/mongo-express/service.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.mongo.enabled .Values.mongoExpress.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.mongoExpress.name }} +spec: + type: {{ .Values.mongoExpress.service.type }} + {{ if .Values.mongoExpress.service.ports }} + ports: + {{- toYaml .Values.mongoExpress.service.ports | nindent 4 }} + {{- end }} + selector: + app: {{ .Release.Name }}-{{ .Values.mongoExpress.name }} +{{- end }} \ No newline at end of file diff --git a/helm-chart/templates/mongo/deployment.yaml b/helm-chart/templates/mongo/deployment.yaml new file mode 100644 index 00000000..8e404baf --- /dev/null +++ b/helm-chart/templates/mongo/deployment.yaml @@ -0,0 +1,45 @@ +{{ if .Values.mongo.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-{{ .Values.mongo.name }} +spec: + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.mongo.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.mongo.name }} + spec: + containers: + - name: {{ .Values.mongo.name }} + image: {{ .Values.mongo.deployment.image }} + {{- if .Values.mongo.deployment.ports }} + ports: + {{- toYaml .Values.mongo.deployment.ports | nindent 12 }} + {{- end }} + env: + - name: MONGO_INITDB_ROOT_USERNAME + valueFrom: + configMapKeyRef: + name: {{ .Release.Name }}-{{ .Values.configName }} + key: MONGO_USER + - name: MONGO_INITDB_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-{{ .Values.secretsName }} + key: MONGO_PASSWORD + envFrom: + - secretRef: + name: {{ .Release.Name }}-{{ .Values.secretsName }} + - configMapRef: + name: {{ .Release.Name }}-{{ .Values.configName }} + volumeMounts: + - name: app-mongo-db-data + mountPath: /data/db + volumes: + - name: app-mongo-db-data + persistentVolumeClaim: + claimName: app-mongo-db-data +{{- end }} diff --git a/helm-chart/templates/mongo/service.yaml b/helm-chart/templates/mongo/service.yaml new file mode 100644 index 00000000..43b47205 --- /dev/null +++ b/helm-chart/templates/mongo/service.yaml @@ -0,0 +1,14 @@ +{{ if .Values.mongo.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.mongo.name }} +spec: + type: {{ .Values.mongo.service.type }} + {{- if .Values.mongo.service.ports }} + ports: + {{- toYaml .Values.mongo.service.ports | nindent 4 }} + {{- end }} + selector: + app: {{ .Release.Name }}-{{ .Values.mongo.name }} +{{- end }} diff --git a/helm-chart/templates/mongo/volume.yaml b/helm-chart/templates/mongo/volume.yaml new file mode 100644 index 00000000..8a2243a3 --- /dev/null +++ b/helm-chart/templates/mongo/volume.yaml @@ -0,0 +1,12 @@ +{{- if .Values.mongo.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: app-mongo-db-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi +{{- end }} diff --git a/helm-chart/templates/pgadmin/configmap.yaml b/helm-chart/templates/pgadmin/configmap.yaml new file mode 100644 index 00000000..79649c7a --- /dev/null +++ b/helm-chart/templates/pgadmin/configmap.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.postgres.enabled .Values.pgadmin.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-{{ .Values.pgadmin.name }}-configmap +data: + servers.json: | + { + "Servers": { + "db": { + "Name": "{{ .Release.Name }}-{{ .Values.postgres.name }}", + "Group": "Servers", + "Port": 5432, + "Username": "postgres", + "Host": "{{ .Release.Name }}-{{ .Values.postgres.name }}", + "SSLMode": "prefer", + "MaintenanceDB": "postgres" + } + } + } +{{- end }} diff --git a/helm-chart/templates/pgadmin/deployment.yaml b/helm-chart/templates/pgadmin/deployment.yaml new file mode 100644 index 00000000..d7061435 --- /dev/null +++ b/helm-chart/templates/pgadmin/deployment.yaml @@ -0,0 +1,35 @@ +{{- if and .Values.postgres.enabled .Values.pgadmin.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-{{ .Values.pgadmin.name }} +spec: + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.pgadmin.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.pgadmin.name }} + spec: + containers: + - name: {{ .Values.pgadmin.name }} + image: {{ .Values.pgadmin.deployment.image }} + {{- if .Values.pgadmin.deployment.ports }} + ports: + {{- toYaml .Values.pgadmin.deployment.ports | nindent 12 }} + {{- end }} + envFrom: + - secretRef: + name: {{ .Release.Name }}-{{ .Values.secretsName }} + - configMapRef: + name: {{ .Release.Name }}-{{ .Values.configName }} + volumeMounts: + - name: pgadmin-config + mountPath: /pgadmin4/servers.json + subPath: servers.json + volumes: + - name: pgadmin-config + configMap: + name: {{ .Release.Name }}-{{ .Values.pgadmin.name }}-configmap +{{- end }} diff --git a/helm-chart/templates/pgadmin/service.yaml b/helm-chart/templates/pgadmin/service.yaml new file mode 100644 index 00000000..1780b245 --- /dev/null +++ b/helm-chart/templates/pgadmin/service.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.postgres.enabled .Values.pgadmin.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.pgadmin.name }} +spec: + type: {{ .Values.mongoExpress.service.type }} + {{ if .Values.pgadmin.service.ports }} + ports: + {{- toYaml .Values.pgadmin.service.ports | nindent 4 }} + {{- end }} + selector: + app: {{ .Release.Name }}-{{ .Values.pgadmin.name }} +{{- end }} \ No newline at end of file diff --git a/helm-chart/templates/postgres/deployment.yaml b/helm-chart/templates/postgres/deployment.yaml new file mode 100644 index 00000000..a068a5bb --- /dev/null +++ b/helm-chart/templates/postgres/deployment.yaml @@ -0,0 +1,35 @@ +{{- if .Values.postgres.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-{{ .Values.postgres.name }} +spec: + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + spec: + containers: + - name: {{ .Values.postgres.name }} + image: {{ .Values.postgres.deployment.image }} + command: ["docker-entrypoint.sh", "-c", "shared_buffers=256MB", "-c", "max_connections=200"] + {{- if .Values.postgres.deployment.env }} + env: + {{- toYaml .Values.postgres.deployment.env | nindent 12 }} + {{- end }} + envFrom: + - secretRef: + name: {{ .Release.Name }}-{{ .Values.secretsName }} + - configMapRef: + name: {{ .Release.Name }}-{{ .Values.configName }} + volumeMounts: + - name: app-postgres-data + mountPath: /var/lib/postgresql/data/pgdata + volumes: + - name: app-postgres-data + persistentVolumeClaim: + claimName: app-postgres-data +{{- end }} diff --git a/helm-chart/templates/postgres/service.yaml b/helm-chart/templates/postgres/service.yaml new file mode 100644 index 00000000..ae4d181a --- /dev/null +++ b/helm-chart/templates/postgres/service.yaml @@ -0,0 +1,15 @@ +{{- if .Values.postgres.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.postgres.name }} +spec: + type: {{ .Values.mongoExpress.service.type }} + {{- if .Values.postgres.service.ports }} + ports: + {{- toYaml .Values.postgres.service.ports | nindent 4 }} + {{- end }} + selector: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} +{{- end }} + diff --git a/helm-chart/templates/postgres/volume.yaml b/helm-chart/templates/postgres/volume.yaml new file mode 100644 index 00000000..2dd74f75 --- /dev/null +++ b/helm-chart/templates/postgres/volume.yaml @@ -0,0 +1,12 @@ +{{- if .Values.postgres.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: app-postgres-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi +{{- end }} diff --git a/helm-chart/templates/rabbitmq/deployment.yaml b/helm-chart/templates/rabbitmq/deployment.yaml new file mode 100644 index 00000000..bf65541d --- /dev/null +++ b/helm-chart/templates/rabbitmq/deployment.yaml @@ -0,0 +1,22 @@ +{{- if .Values.rabbitmq.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-{{ .Values.rabbitmq.name }} +spec: + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.rabbitmq.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.rabbitmq.name }} + spec: + containers: + - name: {{ .Values.rabbitmq.name }} + image: {{ .Values.rabbitmq.deployment.image }} + {{- if .Values.rabbitmq.deployment.ports }} + ports: + {{- toYaml .Values.rabbitmq.deployment.ports | nindent 12 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm-chart/templates/rabbitmq/service.yaml b/helm-chart/templates/rabbitmq/service.yaml new file mode 100644 index 00000000..da330dfd --- /dev/null +++ b/helm-chart/templates/rabbitmq/service.yaml @@ -0,0 +1,14 @@ +{{ if .Values.rabbitmq.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.rabbitmq.name }} +spec: + type: {{ .Values.rabbitmq.service.type }} + {{- if .Values.rabbitmq.service.ports }} + ports: + {{- toYaml .Values.rabbitmq.service.ports | nindent 4 }} + {{- end }} + selector: + app: {{ .Release.Name }}-{{ .Values.rabbitmq.name }} +{{- end }} diff --git a/helm-chart/templates/report/deployment.yaml b/helm-chart/templates/report/deployment.yaml new file mode 100644 index 00000000..00c3e11c --- /dev/null +++ b/helm-chart/templates/report/deployment.yaml @@ -0,0 +1,46 @@ +{{- if and .Values.report.enabled .Values.backend.enabled .Values.rabbitmq.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-{{ .Values.report.name }} +spec: + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.report.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.report.name }} + spec: + # Depends on backend + initContainers: + - name: wait-for-backend + image: curlimages/curl:latest + args: + - /bin/sh + - -c + - > + set -x; + while [ $(curl -sw '%{http_code}' "http://{{ .Release.Name }}-{{ .Values.backend.name }}:{{ index .Values.backend.service.ports 0 "port" }}/login" -o /dev/null) -ne 200 ]; do + sleep 2; + done; + containers: + - name: {{ .Values.report.name }} + image: {{ .Values.report.deployment.image }} + {{- if .Values.report.deployment.ports }} + ports: + {{- toYaml .Values.report.deployment.ports | nindent 12 }} + {{- end }} + envFrom: + - secretRef: + name: {{ .Release.Name }}-{{ .Values.secretsName }} + - configMapRef: + name: {{ .Release.Name }}-{{ .Values.configName }} + volumeMounts: + - name: shared-data + mountPath: /shared + volumes: + - name: shared-data + persistentVolumeClaim: + claimName: shared-data +{{- end }} diff --git a/helm-chart/templates/report/service.yaml b/helm-chart/templates/report/service.yaml new file mode 100644 index 00000000..23d891f6 --- /dev/null +++ b/helm-chart/templates/report/service.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.report.enabled .Values.backend.enabled .Values.rabbitmq.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.report.name }} +spec: + type: {{ .Values.report.service.type }} + {{- if .Values.report.service.ports }} + ports: + {{- toYaml .Values.report.service.ports | nindent 4 }} + {{- end }} + selector: + app: {{ .Release.Name }}-{{ .Values.report.name }} +{{- end }} diff --git a/helm-chart/templates/secrets.yaml b/helm-chart/templates/secrets.yaml new file mode 100644 index 00000000..f3839f1b --- /dev/null +++ b/helm-chart/templates/secrets.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-{{ .Values.secretsName }} +type: Opaque +data: + # Backend + SECRET_KEY: {{ randAlphaNum 32 | b64enc | quote }} + FIRST_SUPERUSER_PASSWORD: {{ randAlphaNum 32 | b64enc | quote }} + SMTP_PASSWORD: {{ randAlphaNum 32 | b64enc | quote }} + + # Postgres + POSTGRES_PASSWORD: {{ randAlphaNum 32 | b64enc | quote }} + + # PgAdmin + PGADMIN_DEFAULT_PASSWORD: {{ randAlphaNum 32 | b64enc | quote }} + + # Mongo + MONGO_PASSWORD: {{ randAlphaNum 32 | b64enc | quote }} diff --git a/helm-chart/values.yaml b/helm-chart/values.yaml new file mode 100644 index 00000000..0a4d2ac1 --- /dev/null +++ b/helm-chart/values.yaml @@ -0,0 +1,113 @@ +# Values for NEF Simulator Helm Chart + +# Global configuration +configName: nef-config +secretsName: nef-secrets + +# Postgres configuration +postgres: + enabled: true + name: postgres + deployment: + image: postgres:12 + env: + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + service: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + +# PgAdmin configuration +pgadmin: + enabled: true + name: pgadmin + deployment: + image: dpage/pgadmin4 + ports: + - containerPort: 5050 + service: + type: ClusterIP + ports: + - port: 5050 + targetPort: 5050 + +# Mongo configuration +mongo: + enabled: true + name: mongo + deployment: + image: mongo:4.4.10 + ports: + - containerPort: 27017 + service: + type: ClusterIP + ports: + - port: 27017 + targetPort: 27017 + +mongoExpress: + enabled: true + name: mongo-express + deployment: + image: mongo-express:1.0.0-alpha.4 + ports: + - containerPort: 8081 + service: + type: ClusterIP + ports: + - port: 8081 + targetPort: 8081 + protocol: TCP + +# RabbitMQ configuration +rabbitmq: + enabled: true + name: rabbitmq + deployment: + image: rabbitmq:management + ports: + - containerPort: 5672 + protocol: TCP + - containerPort: 15672 + protocol: TCP + service: + type: ClusterIP + ports: + - name: amqp + port: 5672 + targetPort: 5672 + - name: http + port: 15672 + targetPort: 15672 + +# Report configuration +report: + enabled: true + name: report + deployment: + image: localhost:5000/report:latest + ports: + - containerPort: 3000 + service: + type: LoadBalancer + ports: + - port: 3000 + targetPort: 3000 + protocol: TCP + +# Backend configuration +backend: + enabled: true + name: backend + deployment: + image: localhost:5000/backend:latest + ports: + - containerPort: 80 + service: + type: LoadBalancer + ports: + - port: 8888 + targetPort: 80 + protocol: TCP diff --git a/run-helm.sh b/run-helm.sh new file mode 100755 index 00000000..6d5d66e4 --- /dev/null +++ b/run-helm.sh @@ -0,0 +1,65 @@ +NAMESPACE=nef +RELEASE=nef +DOCKER_REGISTRY=localhost:5000 + +# help +for arg in "$@"; do + if [ "$arg" == "--help" ]; then + echo "Usage: cmd [-n NAMESPACE] [-r RELEASE] [-d DOCKER_REGISTRY] [--help]" + exit 0 + fi +done + +# get opts +while getopts "n:r:d" opt; do + case ${opt} in + n ) + NAMESPACE=$OPTARG + ;; + r ) + RELEASE=$OPTARG + ;; + d ) + DOCKER_REGISTRY=$OPTARG + ;; + \? ) + echo "Invalid option: -$OPTARG" >&2 + echo "Usage: cmd [-n NAMESPACE] [-r RELEASE] [-d DOCKER_REGISTRY] [--help]" + exit 1 + ;; + esac +done + +kubectl create namespace $NAMESPACE + +docker build -t $DOCKER_REGISTRY/backend -f backend/Dockerfile.backend --build-arg INSTALL_DEV=${INSTALL_DEV:-true} --build-arg INSTALL_JUPYTER=${INSTALL_JUPYTER:-true} backend +docker push $DOCKER_REGISTRY/backend +docker build -t $DOCKER_REGISTRY/report -f backend/Dockerfile.report --build-arg INSTALL_DEV=${INSTALL_DEV:-true} --build-arg INSTALL_JUPYTER=${INSTALL_JUPYTER:-true} backend +docker push $DOCKER_REGISTRY/report +helm -n $NAMESPACE upgrade --wait --install $RELEASE helm-chart --set backend.deployment.image=$DOCKER_REGISTRY/backend:latest --set report.deployment.image=$DOCKER_REGISTRY/report:latest + +# get the external IP address of the service nef-backend if it exists, otherwise the cluster IP +NEF_BACKEND_IP=$(kubectl get svc $RELEASE-backend -n $NAMESPACE -o jsonpath='{.status.loadBalancer.ingress[0].ip}') +if [ -z "$NEF_BACKEND_IP" ]; then + NEF_BACKEND_IP=$(kubectl get svc $RELEASE-backend -n $NAMESPACE -o jsonpath='{.spec.clusterIP}') +fi + +# Get the service port +NEF_BACKEND_PORT=$(kubectl get svc $RELEASE-backend -n $NAMESPACE -o jsonpath='{.spec.ports[0].port}') + +# Get the report service port +NEF_REPORT_PORT=$(kubectl get svc $RELEASE-report -n $NAMESPACE -o jsonpath='{.spec.ports[0].port}') + +# Get the super user name and password +FIRST_SUPERUSER="$(kubectl get configmap $RELEASE-nef-config -n $NAMESPACE -o jsonpath='{.data.FIRST_SUPERUSER}')" +FIRST_SUPERUSER_PASSWORD="$(kubectl get secret $RELEASE-nef-secrets -n $NAMESPACE -o jsonpath='{.data.FIRST_SUPERUSER_PASSWORD}' | base64 -d)" + +# Run the script to create the data +bash backend/app/app/db/init_simple.sh -h "http://$NEF_BACKEND_IP" -p "$NEF_BACKEND_PORT" -r "$NEF_REPORT_PORT" -u "$FIRST_SUPERUSER" -s "$FIRST_SUPERUSER_PASSWORD" + +# echo the variables +printf "\n\n" +echo "NEF IP: $NEF_BACKEND_IP" +echo "NEF port: $NEF_BACKEND_PORT" +echo "Super user name: $FIRST_SUPERUSER" +echo "Super user password: $FIRST_SUPERUSER_PASSWORD"