Automate deployment of pull requests #15

Merged
tozh4728 merged 3 commits from branch-deploy into develop 2024-12-19 10:44:49 +01:00
10 changed files with 281 additions and 9 deletions

View File

@ -0,0 +1,14 @@
name: Remove branch deployment from branch.dsv.su.se
on:
pull_request:
types:
- closed
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- uses: https://gitea.dsv.su.se/ansv7779/action-branch-deploy@v2
with:
cleanup-ssh-key: ${{ secrets.BRANCH_CLEANUP_KEY }}
compose-file: compose-branch-deploy.yaml
mode: 'cleanup'

View File

@ -0,0 +1,26 @@
name: Deploy to branch.dsv.su.se
on:
- pull_request
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- id: deploy
uses: https://gitea.dsv.su.se/ansv7779/action-branch-deploy@v2
with:
ssh-key: ${{ secrets.BRANCH_DEPLOY_KEY }}
compose-file: compose-branch-deploy.yaml
- name: Post URL to deployment as comment
uses: actions/github-script@v7
if: github.event.action == 'opened'
env:
BRANCH_URL: ${{ steps.deploy.outputs.url }}
with:
script: |
const url = process.env.BRANCH_URL;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Deployed to ${url}`
})

39
Dockerfile Normal file
View File

@ -0,0 +1,39 @@
FROM debian:bookworm AS build
RUN apt-get update && apt-get install -y openjdk-17-jdk-headless
WORKDIR /app
COPY pom.xml .
COPY .mvn/ .mvn/
COPY mvnw .
COPY api/pom.xml api/pom.xml
COPY core/pom.xml core/pom.xml
COPY view/pom.xml view/pom.xml
COPY war/pom.xml war/pom.xml
COPY daisy-integration/pom.xml daisy-integration/pom.xml
# Download dependencies in a separate layer to allow caching for future builds
RUN ./mvnw dependency:go-offline \
--batch-mode \
--define includeScope=compile \
--activate-profiles docker-dependencies
COPY api/src/ api/src/
COPY core/src/ core/src/
COPY view/src/ view/src/
COPY war/src/ war/src/
COPY daisy-integration/src/ daisy-integration/src/
RUN ./mvnw package \
--offline \
--define skipTests \
--activate-profiles branch,DEV \
--define skip.npm \
--define skip.installnodenpm
FROM tomcat:10 AS run
COPY --from=build /app/war/target/*.war /usr/local/tomcat/webapps/ROOT.war
EXPOSE 8080

View File

@ -39,3 +39,13 @@ can be performed.
Go to `Settings -> Language & Frameworks -> JavaScript -> Prettier` and then check
`Automatic Prettier Configuration`, set `Run for files` to `**/*.{java}`,
and finally check `Run on save`.
## Test servers
All pull requests are automatically deployed to a test server.
The URL to the test server will be posted as a comment in the pull request once deployed.
Prepare test data in the `DataInitializer` class to help others test your changes.
Document (in the pull request) which users to log in as and what to do to see the changes.
If you want to reset the data to its original state you can re-run the "deploy-branch.yaml"
workflow at https://gitea.dsv.su.se/DMC/scipro/actions for the branch you want to reset.

View File

@ -0,0 +1,72 @@
services:
scipro:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
depends_on:
db:
condition: service_healthy
oauth2:
condition: service_started
environment:
- JDBC_DATABASE_URL=jdbc:mariadb://db:3306/scipro
- JDBC_DATABASE_USERNAME=scipro
- JDBC_DATABASE_PASSWORD=scipro
- OAUTH2_AUTHORIZATION_URI=https://oauth2-${VHOST}/authorize
- OAUTH2_TOKEN_URI=https://oauth2-${VHOST}/exchange
- OAUTH2_USER_INFO_URI=https://oauth2-${VHOST}/verify
- OAUTH2_CLIENT_ID=scipro_client
- OAUTH2_CLIENT_SECRET=scipro_secret
- OAUTH2_RESOURCE_SERVER_ID=scipro_api_client
- OAUTH2_RESOURCE_SERVER_SECRET=scipro_api_secret
- OAUTH2_RESOURCE_SERVER_INTROSPECTION_URI=https://oauth2-${VHOST}/introspect
networks:
- traefik
- internal
labels:
- "traefik.enable=true"
- "traefik.http.routers.${COMPOSE_PROJECT_NAME}.rule=Host(`${VHOST}`)"
- "traefik.http.routers.${COMPOSE_PROJECT_NAME}.tls.certresolver=letsencrypt"
db:
image: mariadb:10.11
restart: unless-stopped
networks:
- internal
environment:
MARIADB_ROOT_PASSWORD: root
MARIADB_DATABASE: scipro
MARIADB_USER: scipro
MARIADB_PASSWORD: scipro
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect"]
start_period: 10s
interval: 10s
timeout: 5s
retries: 6
oauth2:
build:
context: https://github.com/dsv-su/toker.git
dockerfile: embedded.Dockerfile
restart: unless-stopped
environment:
- CLIENT_ID=scipro_client
- CLIENT_SECRET=scipro_secret
- CLIENT_REDIRECT_URI=https://${VHOST}/login/oauth2/code/scipro
- RESOURCE_SERVER_ID=scipro_api_client
- RESOURCE_SERVER_SECRET=scipro_api_secret
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.oauth2-${COMPOSE_PROJECT_NAME}.rule=Host(`oauth2-${VHOST}`)"
- "traefik.http.routers.oauth2-${COMPOSE_PROJECT_NAME}.tls.certresolver=letsencrypt"
networks:
traefik:
name: traefik
external: true
internal:
name: ${COMPOSE_PROJECT_NAME}_internal

View File

@ -12,6 +12,10 @@
<artifactId>war</artifactId>
<packaging>war</packaging>
<properties>
<spring.profile.active>tomcat</spring.profile.active>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -88,7 +92,26 @@
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<propertiesEncoding>${project.build.sourceEncoding}</propertiesEncoding>
<delimiters>
<!-- delimiter for resource filtering is changed since Spring hijacks ${...} -->
<delimiter>@</delimiter>
</delimiters>
<useDefaultDelimiters>false</useDefaultDelimiters>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
@ -110,4 +133,53 @@
</plugins>
</build>
<profiles>
<profile>
<id>branch</id>
<properties>
<spring.profile.active>branch</spring.profile.active>
</properties>
</profile>
<profile>
<id>docker-dependencies</id>
<!--
Some dependencies are not discovered by default when running dependency:go-offline.
They are added here manually to allow Docker build layers to be cached properly.
-->
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml</groupId>
<artifactId>classmate</artifactId>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
<classifier>jakarta</classifier>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</profile>
</profiles>
</project>

View File

@ -16,12 +16,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilderCustomizer;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.orm.jpa.SharedEntityManagerCreator;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
import org.springframework.web.filter.ForwardedHeaderFilter;
import se.su.dsv.scipro.CoreConfig;
import se.su.dsv.scipro.FileSystemStore;
import se.su.dsv.scipro.RepositoryConfiguration;
@ -84,6 +87,22 @@ public class Main extends SpringBootServletInitializer implements ServletContain
return currentProfile;
}
/**
* Spring runs on HTTP and is protected by a HTTPS proxy.
* This filter takes the `X-Forwarded-*` headers and updates the request to reflect the original HTTP request.
* <p>
* Note: This is not needed when we're running behind Apache as a proxy since it uses an AJP connector that has a
* built-in mechanism for handling this,
* see <a href="https://tomcat.apache.org/connectors-doc/common_howto/proxy.html#AJP_as_a_Solution">AJP proxy documentation</a>.
* So this is only for the temporary test servers that are running behind Traefik and use regular HTTP.
*/
@Bean
public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
var filterRegistrationBean = new FilterRegistrationBean<>(new ForwardedHeaderFilter());
filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filterRegistrationBean;
}
@Bean
public FileStore fileStore() {
return new FileSystemStore();

View File

@ -0,0 +1,19 @@
spring.datasource.url=${JDBC_DATABASE_URL}
spring.datasource.username=${JDBC_DATABASE_USERNAME}
spring.datasource.password=${JDBC_DATABASE_PASSWORD}
profile=DEV
# No secrets available for branch deployment to branch.dsv.su.se
# Will have to set up some mock API for this later
service.grading.url=
oauth.uri=
oauth.clientId=
oauth.clientSecret=
oauth.redirectUri=
# No secrets available for branch deployment to branch.dsv.su.se
# Will have to set up some mock API for this later
daisy.api.url=
daisy.api.username=
daisy.api.password=

View File

@ -0,0 +1 @@
spring.datasource.jndi-name=java:/comp/env/jdbc/sciproDS

View File

@ -1,4 +1,4 @@
spring.datasource.jndi-name=java:/comp/env/jdbc/sciproDS
spring.profiles.active=@spring.profile.active@
spring.flyway.baseline-version=2
spring.flyway.baseline-on-migrate=true
@ -16,17 +16,17 @@ springdoc.swagger-ui.path=/swagger
springdoc.swagger-ui.persist-authorization=true
# These will be overwritten by configuration in the environment of servers it is deployed to
spring.security.oauth2.resourceserver.opaquetoken.client-id=scipro-api-client
spring.security.oauth2.resourceserver.opaquetoken.client-secret=scipro-api-secret
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=http://localhost:59733/introspect
spring.security.oauth2.resourceserver.opaquetoken.client-id=${OAUTH2_RESOURCE_SERVER_ID:scipro-api-client}
spring.security.oauth2.resourceserver.opaquetoken.client-secret=${OAUTH2_RESOURCE_SERVER_SECRET:scipro-api-secret}
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=${OAUTH2_RESOURCE_SERVER_INTROSPECTION_URI:http://localhost:59733/introspect}
# Log in via local OAuth 2 authorization server
spring.security.oauth2.client.provider.docker.user-info-uri=http://localhost:59734/verify
spring.security.oauth2.client.provider.docker.user-info-uri=${OAUTH2_USER_INFO_URI:http://localhost:59734/verify}
spring.security.oauth2.client.provider.docker.user-name-attribute=sub
spring.security.oauth2.client.provider.docker.token-uri=http://localhost:59734/exchange
spring.security.oauth2.client.provider.docker.authorization-uri=http://localhost:59734/authorize
spring.security.oauth2.client.provider.docker.token-uri=${OAUTH2_TOKEN_URI:http://localhost:59734/exchange}
spring.security.oauth2.client.provider.docker.authorization-uri=${OAUTH2_AUTHORIZATION_URI:http://localhost:59734/authorize}
spring.security.oauth2.client.registration.scipro.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.scipro.provider=docker
spring.security.oauth2.client.registration.scipro.client-id=scipro
spring.security.oauth2.client.registration.scipro.client-secret=s3cr3t
spring.security.oauth2.client.registration.scipro.client-id=${OAUTH2_CLIENT_ID:scipro}
spring.security.oauth2.client.registration.scipro.client-secret=${OAUTH2_CLIENT_SECRET:s3cr3t}
spring.security.oauth2.client.registration.scipro.authorization-grant-type=authorization_code