Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
42 changes: 21 additions & 21 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
# For more information see: https://docs.github.com/en/actions/automating-builds-and-testing-java-with-maven
Comment thread
tcezard marked this conversation as resolved.
Outdated
Comment thread
tcezard marked this conversation as resolved.
Outdated

name: Java CI with Maven

Expand All @@ -9,25 +9,12 @@ on:
branches: ["main"]

jobs:
build:

unit-tests:
name: Unit Tests (H2 in-memory)
runs-on: ubuntu-latest
permissions:
contents: read

services:
postgres:
image: postgres
env:
POSTGRES_USER: haroune
POSTGRES_PASSWORD: 123
POSTGRES_DB: seqcol_db
ports:
# will assign a random free host port
- 5432/tcp
# needed because the postgres container does not provide a healthcheck
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

steps:
- uses: actions/checkout@v3
- name: Set up JDK 8
Expand All @@ -36,9 +23,22 @@ jobs:
java-version: '8'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Run Unit Tests
run: mvn -B clean test --file pom.xml -Dtest='!*IntegrationTest'

env:
ADMIN_USER: test
ADMIN_PASSWORD: test
integration-tests:
name: Integration Tests (Docker/Testcontainers)
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- uses: actions/checkout@v3
- name: Set up JDK 8
uses: actions/setup-java@v3
with:
java-version: '8'
distribution: 'temurin'
cache: maven
- name: Run Integration Tests
run: mvn -B clean test --file pom.xml -Dtest='*IntegrationTest'
189 changes: 189 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@

stages:
- build
- deploy

variables:
DOCKER_REGISTRY: dockerhub.ebi.ac.uk
DOCKER_IMAGE: dockerhub.ebi.ac.uk/ebivariation/eva-seqcol
K8S_REPO_URL: https://github.com/EBIvariation/eva-k8s.git

# REQUIRED GitLab CI/CD Variables (Global):
# DOCKER_REGISTRY_USER: Username for Docker registry authentication
# DOCKER_REGISTRY_PASSWORD: Password for Docker registry authentication
# K8S_REPO_DEPLOY_KEY: Base64-encoded SSH private key for cloning/pushing to eva-k8s GitHub repo
#
# DEVELOPMENT ENVIRONMENT VARIABLES (suffixed with _DEV):
# METADATA_DB_HOST_DEV: PostgreSQL host
# METADATA_DB_PORT_DEV: PostgreSQL port
# METADATA_DB_USER_DEV: PostgreSQL username
# METADATA_DB_PASSWORD_DEV: PostgreSQL password
# METADATA_DB_SPRING_DATASOURCE_URL_DEV: JDBC connection string
# SEQCOL_ADMIN_USER_DEV: Admin username
# SEQCOL_ADMIN_PASSWORD_DEV: Admin password
# KUBECONFIG_CONTENT_DEV: Base64-encoded kubeconfig for dev cluster
#
# PRODUCTION ENVIRONMENT VARIABLES (suffixed with _PROD):
# METADATA_DB_HOST_PROD: PostgreSQL host
# METADATA_DB_PORT_PROD: PostgreSQL port
# METADATA_DB_USER_PROD: PostgreSQL username
# METADATA_DB_PASSWORD_PROD: PostgreSQL password
# METADATA_DB_SPRING_DATASOURCE_URL_PROD: JDBC connection string
# SEQCOL_ADMIN_USER_PROD: Admin username
# SEQCOL_ADMIN_PASSWORD_PROD: Admin password
# KUBECONFIG_CONTENT_PROD: Base64-encoded kubeconfig for prod cluster

# ============================================================================
# Base Job Templates
# ============================================================================

.build_docker:
stage: build
image: docker:latest
services:
- docker:dind
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
before_script:
- echo $DOCKER_REGISTRY_PASSWORD | docker login -u $DOCKER_REGISTRY_USER --password-stdin $DOCKER_REGISTRY
script:
- docker build -t $DOCKER_IMAGE:$IMAGE_TAG -t $DOCKER_IMAGE:$IMAGE_LATEST .
- docker push $DOCKER_IMAGE:$IMAGE_TAG
- docker push $DOCKER_IMAGE:$IMAGE_LATEST
- echo "IMAGE_TAG=$IMAGE_TAG" > build.env
artifacts:
reports:
dotenv: build.env

.update_in_github:
stage: deploy
image: alpine/git:latest
before_script:
- apk add --no-cache bash curl
- mkdir -p ~/.ssh
- echo "$K8S_REPO_DEPLOY_KEY" | base64 -d > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts 2>/dev/null
- git config --global user.email "gitlab-ci@ebi.ac.uk"
- git config --global user.name "GitLab CI"
script:
- git clone --depth 1 git@github.com:EBIvariation/eva-k8s.git k8s-repo
- cd k8s-repo
- |
sed -i "s/newTag:.*/newTag: $IMAGE_TAG/" $K8S_MANIFEST_PATH/kustomization.yaml
grep "newTag:" $K8S_MANIFEST_PATH/kustomization.yaml
- git add $K8S_MANIFEST_PATH/kustomization.yaml
- git commit -m "chore: update eva-seqcol $ENV image tag to $IMAGE_TAG"
- git push origin main

.deploy_to_cluster:
stage: deploy
image: alpine/k8s:latest
before_script:
- mkdir -p ~/.kube
- echo "$KUBECONFIG_CONTENT" | base64 -d > ~/.kube/config
- chmod 600 ~/.kube/config
# Generate secrets from CI variables into manifest path
- mkdir -p $K8S_MANIFEST_PATH
- printf "host=%s\nport=%s\nusername=%s\npassword=%s\n" "$DB_HOST" "$DB_PORT" "$DB_USER" "$DB_PASSWORD" > $K8S_MANIFEST_PATH/secrets-db.env
- printf "username=%s\npassword=%s\n" "$ADMIN_USER" "$ADMIN_PASSWORD" > $K8S_MANIFEST_PATH/secrets-admin.env
- printf "SPRING_DATASOURCE_URL=%s\nDDL_BEHAVIOUR=update\nSPRING_JPA_PROPERTIES_HIBERNATE_DEFAULT_SCHEMA=seqcol\nFTP_PROXY_HOST=%s\nFTP_PROXY_PORT=%s\nSCAFFOLDS_ENABLED=%s\n" "$SPRING_DATASOURCE_URL" "${FTP_PROXY_HOST:-}" "${FTP_PROXY_PORT:-0}" "${SCAFFOLDS_ENABLED:-true}" > $K8S_MANIFEST_PATH/config.env
script:
- kubectl apply -k $K8S_MANIFEST_PATH
- kubectl rollout status deployment/eva-seqcol -n $K8S_NAMESPACE --timeout=5m
environment:
kubernetes:
namespace: $K8S_NAMESPACE

# ============================================================================
# PRODUCTION: Deploy on git tags
# ============================================================================

build:docker:prod:
extends: .build_docker
variables:
IMAGE_TAG: $CI_COMMIT_TAG
IMAGE_LATEST: latest
only:
- tags

update-in-github:prod:
extends: .update_in_github
needs:
- build:docker:prod
variables:
ENV: prod
K8S_MANIFEST_PATH: k8s-manifests/eva-seqcol/overlays/prod
environment:
name: prod
only:
- tags

deploy-to-cluster:prod:
extends: .deploy_to_cluster
needs:
- update-in-github:prod
variables:
K8S_MANIFEST_PATH: k8s-manifests/eva-seqcol/overlays/prod
K8S_NAMESPACE: eva-seqcol-prod
# Production-specific environment variables
DB_HOST: $METADATA_DB_HOST_PROD
DB_PORT: $METADATA_DB_PORT_PROD
DB_USER: $METADATA_DB_USER_PROD
DB_PASSWORD: $METADATA_DB_PASSWORD_PROD
SPRING_DATASOURCE_URL: $METADATA_DB_SPRING_DATASOURCE_URL_PROD
ADMIN_USER: $SEQCOL_ADMIN_USER_PROD
ADMIN_PASSWORD: $SEQCOL_ADMIN_PASSWORD_PROD
KUBECONFIG_CONTENT: $KUBECONFIG_CONTENT_PROD
environment:
name: prod
only:
- tags

# ============================================================================
# DEVELOPMENT: Deploy on main branch (manual trigger)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What's the reasoning behind making the dev deploys manual, but the prod ones automatic?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The initial rational was to allow more deployments on dev prior to merging while not deploying for every push. However After looking into it I realised that I could not deploy any code from forks because they are not mirrored. I'll remove the manual trigger.

# ============================================================================

build:docker:dev:
extends: .build_docker
variables:
IMAGE_TAG: dev-$CI_COMMIT_SHORT_SHA
IMAGE_LATEST: dev-latest
only:
- main

update-in-github:dev:
extends: .update_in_github
needs:
- build:docker:dev
variables:
ENV: dev
K8S_MANIFEST_PATH: k8s-manifests/eva-seqcol/overlays/dev
environment:
name: dev
when: manual
only:
- main

deploy-to-cluster:dev:
extends: .deploy_to_cluster
needs:
- update-in-github:dev
variables:
K8S_MANIFEST_PATH: k8s-manifests/eva-seqcol/overlays/dev
K8S_NAMESPACE: eva-seqcol-dev
# Development-specific environment variables
DB_HOST: $METADATA_DB_HOST_DEV
DB_PORT: $METADATA_DB_PORT_DEV
DB_USER: $METADATA_DB_USER_DEV
DB_PASSWORD: $METADATA_DB_PASSWORD_DEV
SPRING_DATASOURCE_URL: $METADATA_DB_SPRING_DATASOURCE_URL_DEV
ADMIN_USER: $SEQCOL_ADMIN_USER_DEV
ADMIN_PASSWORD: $SEQCOL_ADMIN_PASSWORD_DEV
KUBECONFIG_CONTENT: $KUBECONFIG_CONTENT_DEV
environment:
name: dev
when: manual
only:
- main
26 changes: 4 additions & 22 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,25 +1,7 @@
# Build stage
# Build stage - Build without embedding credentials
FROM maven:3.8-eclipse-temurin-8 AS build
WORKDIR /app

# Build arguments for Maven filtering (with defaults for Docker build)
ARG SERVER_IP=localhost
ARG POSTGRES_PORT=5432
ARG ADMIN_USER=admin
ARG ADMIN_PASSWORD=admin
ARG DDL_BEHAVIOUR=update
ARG FTP_PROXY_HOST=
ARG FTP_PROXY_PORT=

# Set as environment variables for Maven
ENV SERVER_IP=${SERVER_IP} \
POSTGRES_PORT=${POSTGRES_PORT} \
ADMIN_USER=${ADMIN_USER} \
ADMIN_PASSWORD=${ADMIN_PASSWORD} \
DDL_BEHAVIOUR=${DDL_BEHAVIOUR} \
FTP_PROXY_HOST=${FTP_PROXY_HOST} \
FTP_PROXY_PORT=${FTP_PROXY_PORT}

# Copy pom.xml first to leverage Docker cache for dependencies
COPY pom.xml .
RUN mvn dependency:go-offline -B
Expand All @@ -42,8 +24,8 @@ RUN groupadd -r seqcol && useradd -r -g seqcol seqcol
# Create tmp directory for file downloads
RUN mkdir -p /tmp && chown seqcol:seqcol /tmp

# Copy the WAR file from build stage
COPY --from=build /app/target/*.war app.war
# Copy the JAR file from build stage
COPY --from=build /app/target/*.jar app.jar

# Copy service-info.json
COPY --from=build /app/src/main/resources/static/service-info.json /app/service-info.json
Expand All @@ -62,4 +44,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
# JVM options for containerized environments
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC"

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.war"]
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
24 changes: 2 additions & 22 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<groupId>uk.ac.ebi.eva</groupId>
<artifactId>eva-seqcol</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<packaging>jar</packaging>
<name>eva-seqcol</name>
<description>eva-seqcol</description>
<properties>
Expand Down Expand Up @@ -216,31 +216,11 @@
<resources>
<resource>
<directory>src/main/resources/</directory>
<filtering>true</filtering>
<filtering>false</filtering>
</resource>
</resources>
Comment thread
tcezard marked this conversation as resolved.
Outdated
</build>

<profiles>
<profile>
<id>seqcol</id>
<properties>
<spring.profiles.active>seqcol</spring.profiles.active>
<seqcol.db-url>jdbc:postgresql://${env.SERVER_IP}:${env.POSTGRES_PORT}/seqcol_db</seqcol.db-url>
<seqcol.db-username>${env.POSTGRES_USER}</seqcol.db-username>
<seqcol.db-password>${env.POSTGRES_PASS}</seqcol.db-password>
<seqcol.ddl-behaviour>${env.DDL_BEHAVIOUR}</seqcol.ddl-behaviour>
<seqcol.admin-user>${env.ADMIN_USER}</seqcol.admin-user>
<seqcol.admin-password>${env.ADMIN_PASSWORD}</seqcol.admin-password>
<ftp.proxy.host>null</ftp.proxy.host>
<ftp.proxy.port>0</ftp.proxy.port>
<contig-alias.scaffolds-enabled>true</contig-alias.scaffolds-enabled>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
</profiles>


</project>
23 changes: 12 additions & 11 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@

spring.profiles.active=@spring.profiles.active@

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a way to use a single environment variable to select the profile within settings.xml (pulled from the configuration repo), and use that to set the relevant properties? I see Spring uses a SPRING_PROFILES_ACTIVE env variable, presumably it could be hooked into the Gitlab/K8s deployment similar to the other variables.

The approach here is probably fine, I'm just not sure I like the idea of storing so many properties in the Gitlab CI, it seems less legible and harder to maintain.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I agree that this is not optimal and that the number of env variable in Gitlab will quickly get out of hands
The main problem with the maven file + profile is that this is not supported by k8s deployment. We could still make it work:

  • as you said we could download the maven file at run time and apply it with a profile provided as an environment variable
  • we could also provide the maven file as an environment variable blob and write it to file before starting java
    These solution would apply to any java web service but not to non java ones.
    I think the reason k8s is using environment variable is because it is almost always available as a way of injecting parameters.

We could also look into how we can manage env variable outside of gitlab variables
This sounds like a longer conversation is needed.

spring.profiles.active=seqcol
management.endpoints.web.exposure.include=info,health
management.endpoints.web.base-path=/
management.info.git.mode=full

logging.level.uk.ac.ebi.eva.contigalias=DEBUG

controller.auth.admin.username=@seqcol.admin-user@
controller.auth.admin.password=@seqcol.admin-password@
# Admin credentials - override via environment variables
controller.auth.admin.username=${ADMIN_USER:admin}
controller.auth.admin.password=${ADMIN_PASSWORD:admin}

# Database configuration
spring.datasource.url=@seqcol.db-url@
spring.datasource.username=@seqcol.db-username@
spring.datasource.password=@seqcol.db-password@
spring.jpa.hibernate.ddl-auto=@seqcol.ddl-behaviour@
# Database configuration - override via environment variables
spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/seqcol_db}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME:postgres}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:password}
spring.jpa.hibernate.ddl-auto=${SPRING_JPA_HIBERNATE_DDL_AUTO:update}
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.generate-ddl=true

server.servlet.context-path=/eva/webservices/seqcol
server.port=8081

ftp.proxy.host=@ftp.proxy.host@
ftp.proxy.port=@ftp.proxy.port@
ftp.proxy.host=${FTP_PROXY_HOST:}
ftp.proxy.port=${FTP_PROXY_PORT:0}

config.scaffolds.enabled=@contig-alias.scaffolds-enabled@
config.scaffolds.enabled=${SCAFFOLDS_ENABLED:true}

asm.file.download.dir=/tmp
service.info.file.path=src/main/resources/static/service-info.json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;


@SpringBootTest
@ActiveProfiles("test")
class EvaSeqcolApplicationTests {

@Test
Expand Down
Loading
Loading