Skip to content

Commit

Permalink
Merge pull request #14 from ADPRO-C11/subbox-management
Browse files Browse the repository at this point in the history
Subbox management
  • Loading branch information
pesolosep authored May 25, 2024
2 parents 6e18199 + 7be027e commit dd1fe1c
Show file tree
Hide file tree
Showing 27 changed files with 757 additions and 394 deletions.
57 changes: 33 additions & 24 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@

name: Java CI/CD Pipeline
name: GCD CI/CD Pipeline

on:
push:
branches:
- master
- staging

jobs:
build:
name: Build
Expand All @@ -27,12 +28,19 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Make gradlew executable
run: chmod +x ./gradlew
- name: Build with Gradle

- name: Replace placeholders in application-prod.properties
run: |
./gradlew assemble
# (Optional) Add steps for running tests and generating reports
sed -i 's|${PRODUCTION}|'"${{ secrets.PRODUCTION }}"'|g' src/main/resources/application.properties
sed -i 's|${JDBC_DATABASE_URL}|'"${{ secrets.JDBC_DATABASE_URL }}"'|g' src/main/resources/application-prod.properties
sed -i 's|${JDBC_DATABASE_USERNAME}|'"${{ secrets.JDBC_DATABASE_USERNAME }}"'|g' src/main/resources/application-prod.properties
sed -i 's|${JDBC_DATABASE_PASSWORD}|'"${{ secrets.JDBC_DATABASE_PASSWORD }}"'|g' src/main/resources/application-prod.properties
sed -i 's|${JWT_SECRET}|'"${{ secrets.JWT_SECRET }}"'|g' src/main/resources/application-prod.properties
- name: Build with Gradle
run: ./gradlew assemble

- name: Upload Artifact
uses: actions/upload-artifact@v4
Expand All @@ -54,8 +62,10 @@ jobs:
distribution: "temurin"
java-version: "21"
cache: "gradle"

- name: Make gradlew executable
run: chmod +x ./gradlew

- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
Expand All @@ -64,39 +74,37 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Test with Gradle
run: |
./gradlew check --info --stacktrace
./gradlew test
./gradlew jacocoTestReport
# (Optional) Add steps for generating coverage report and other post-test tasks

publish:
name: Publish Docker Image
runs-on: ubuntu-latest
needs: test
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: check directory
run: ls -al

- name: Download Artifact
uses: actions/download-artifact@v4
with:
name: java-app
- name: check directory
run: ls -al
- name: Login to Docker Hub
run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.REGISTRY_USER }} --password-stdin docker.io

- name: Docker login
run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.REGISTRY_USER }} --password-stdin docker.io

- name: Set Docker BuildKit
run: export DOCKER_BUILDKIT=1

- name: Build Docker Image
run: |
docker build --build-arg PRODUCTION=${{ secrets.PRODUCTION }} --build-arg JDBC_DATABASE_PASSWORD=${{ secrets.JDBC_DATABASE_PASSWORD }} --build-arg JDBC_DATABASE_URL=${{ secrets.JDBC_DATABASE_URL }} --build-arg JDBC_DATABASE_USERNAME=${{ secrets.JDBC_DATABASE_USERNAME }} -t ${{ secrets.REGISTRY_USER }}/${{ secrets.IMAGE_NAME }}:${{ secrets.IMAGE_TAG }} .
docker build \
--build-arg PRODUCTION=${{ secrets.PRODUCTION }} \
--build-arg JDBC_DATABASE_PASSWORD=${{ secrets.JDBC_DATABASE_PASSWORD }} \
--build-arg JDBC_DATABASE_URL=${{ secrets.JDBC_DATABASE_URL }} \
--build-arg JDBC_DATABASE_USERNAME=${{ secrets.JDBC_DATABASE_USERNAME }} \
--build-arg JWT_SECRET=${{ secrets.JWT_SECRET }} \
-t ${{ secrets.REGISTRY_USER }}/${{ secrets.IMAGE_NAME }}:${{ secrets.IMAGE_TAG }} \
.
docker push ${{ secrets.REGISTRY_USER }}/${{ secrets.IMAGE_NAME }}:${{ secrets.IMAGE_TAG }}
deploy:
name: Deploy to GCP
runs-on: ubuntu-latest
Expand All @@ -108,14 +116,15 @@ jobs:
- name: Install SSH client
run: sudo apt-get install openssh-client

- name: create ssh key
- name: Create SSH key
run: echo "${{ secrets.SSH_KEY }}" > ssh-key.pem

- name: update permission
- name: Update permission
run: chmod 400 ssh-key.pem

- name: Deploy to GCP
run: |
ssh -o StrictHostKeyChecking=no -i ssh-key.pem ${{ secrets.GCP_USERNAME }}@${{ secrets.GCP_STATIC_IP }} "
sudo docker container rm -f ${{ secrets.CONTAINER_NAME }} || true &&
sudo docker image rm -f ${{ secrets.REGISTRY_USER }}/${{ secrets.IMAGE_NAME }}:${{ secrets.IMAGE_TAG }} || true &&
sudo docker run --name ${{ secrets.CONTAINER_NAME }} -d -p 80:8080 ${{ secrets.REGISTRY_USER }}/${{ secrets.IMAGE_NAME }}:${{ secrets.IMAGE_TAG }}"
sudo docker run --name ${{ secrets.CONTAINER_NAME }} -d -p 80:8080 ${{ secrets.REGISTRY_USER }}/${{ secrets.IMAGE_NAME }}:${{ secrets.IMAGE_TAG }}"
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,12 @@ out/

### VS Code ###
.vscode/

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
19 changes: 19 additions & 0 deletions .monitoring/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: '3.7'

services:
prometheus:
image: prom/prometheus:v2.44.0
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml

grafana:
image: grafana/grafana:9.5.2
container_name: grafana
ports:
- "3000:3000"
restart: unless-stopped
volumes:
- ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources
7 changes: 7 additions & 0 deletions .monitoring/grafana/provisioning/datasources/datasources.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
12 changes: 12 additions & 0 deletions .monitoring/prometheus/prometheus.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
global:
scrape_interval: 15s # By default, scrape targets every 15 seconds.
evaluation_interval: 15s # By default, scrape rules every 15 seconds.

scrape_configs:
- job_name: 'MyAppMetrics'
metrics_path: '/actuator/prometheus'
scrape_interval: 3s
static_configs:
- targets: ['34.124.168.155:80']
labels:
application: 'snackscription_subscriptionbox'
62 changes: 50 additions & 12 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
plugins {
java
id("org.springframework.boot") version "3.2.5"
jacoco
id("org.springframework.boot") version "3.2.5"
id("io.spring.dependency-management") version "1.1.4"
id("org.sonarqube") version "4.4.1.3373"
}

group = "id.ac.ui.cs.advprog"
group = "snackscription"
version = "0.0.1-SNAPSHOT"

java {
Expand All @@ -22,36 +23,73 @@ repositories {
mavenCentral()
}

val springBootVersion = "2.5.0"
val micrometerVersion = "1.12.5"
val dotenvVersion = "4.0.0"
val jwtVersion = "0.12.5"

dependencies {
implementation("org.springframework.boot:spring-boot-starter-logging")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator:$springBootVersion")
implementation("me.paulschwarz:spring-dotenv:$dotenvVersion")
implementation("io.jsonwebtoken:jjwt-api:$jwtVersion")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-webflux")
compileOnly("org.projectlombok:lombok")
developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("org.postgresql:postgresql")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
annotationProcessor("org.projectlombok:lombok")
developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("org.postgresql:postgresql")
runtimeOnly("io.micrometer:micrometer-registry-prometheus:$micrometerVersion")
runtimeOnly("io.jsonwebtoken:jjwt-impl:$jwtVersion")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:$jwtVersion")
testImplementation("org.springframework.boot:spring-boot-starter-test")
runtimeOnly("io.micrometer:micrometer-registry-prometheus")
implementation("org.springframework.boot:spring-boot-starter-actuator")
}

tasks.withType<Test> {
useJUnitPlatform()

tasks.register<Test>("unitTest") {
description = "Runs unit tests."
group = "verification"

filter {
excludeTestsMatching("*FunctionalTest")
}
}

tasks.register<Test>("functionalTest") {
description = "Runs functional tests."
group = "verification"

tasks.test {
filter {
includeTestsMatching("*FunctionalTest")
}
}

tasks.withType<Test>().configureEach {
useJUnitPlatform()
finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run
}

tasks.test{
filter{
excludeTestsMatching("*FunctionalTest")
}

finalizedBy(tasks.jacocoTestReport)
}

tasks.jacocoTestReport {
classDirectories.setFrom(files(classDirectories.files.map {
fileTree(it) { exclude("**/*Application**") }
}))
dependsOn(tasks.test) // tests are required to run before generating the report
reports {
xml.required.set(false)
csv.required.set(false)
xml.required.set(true)
csv.required.set(true)
html.outputLocation.set(layout.buildDirectory.dir("jacocoHtml"))
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package id.ac.ui.cs.advprog.snackscription_subscriptionbox.config;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import id.ac.ui.cs.advprog.snackscription_subscriptionbox.utils.JWTUtils;

import java.io.IOException;
import java.util.Collections;

@Component
public class JWTAuthFilter extends OncePerRequestFilter {

private final JWTUtils jwtUtils;

public JWTAuthFilter(JWTUtils jwtUtils) {
this.jwtUtils = jwtUtils;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
final String jwtToken;

if (authHeader == null || authHeader.isBlank()) {
filterChain.doFilter(request, response);
return;
}

jwtToken = authHeader.substring(7);

if (jwtUtils.isTokenValid(jwtToken)) {
String role = jwtUtils.extractRole(jwtToken);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
null, null, Collections.singletonList(() -> role));
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}

filterChain.doFilter(request, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package id.ac.ui.cs.advprog.snackscription_subscriptionbox.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import id.ac.ui.cs.advprog.snackscription_subscriptionbox.utils.JWTUtils;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

private final JWTUtils jwtUtils;

public SecurityConfig(JWTUtils jwtUtils) {
this.jwtUtils = jwtUtils;
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests
.requestMatchers("/actuator/prometheus").permitAll() // Allow unauthenticated access
.requestMatchers("/subscription-box/**", "/public/**").permitAll()
.anyRequest().authenticated())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(new JWTAuthFilter(jwtUtils), UsernamePasswordAuthenticationFilter.class);

return httpSecurity.build();
}
}
Loading

0 comments on commit dd1fe1c

Please sign in to comment.