Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
38 changes: 0 additions & 38 deletions .github/workflows/ci-build-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,44 +143,6 @@ jobs:
-DAZURE_DEVOPS_ARTIFACT_USERNAME=$AZURE_DEVOPS_ARTIFACT_USERNAME \
-DAZURE_DEVOPS_ARTIFACT_TOKEN=$AZURE_DEVOPS_ARTIFACT_TOKEN


Build-Docker:
needs: [ Provider-Deploy, Build, Artefact-Version ]
if: ${{ inputs.trigger_docker }}
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v5

- name: Download JAR Artefact
uses: actions/download-artifact@v6
with:
name: app-jar
path: build/libs

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Packages
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and Push Docker Image to GitHub
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ needs.Artefact-Version.outputs.artefact_version }}
build-args: |
BASE_IMAGE=eclipse-temurin:21-jdk
JAR_FILENAME=${{ needs.Build.outputs.artefact_name }}.jar


Deploy:
needs: [ Provider-Deploy, Build, Artefact-Version ]
if: ${{ inputs.trigger_deploy }}
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
gradle
/gradlew
/gradlew.bat
!gradle/wrapper/gradle-wrapper.properties
bin/*
!bin/run-in-docker.sh
.gradle
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,12 @@ This project uses a two-file approach for environment variable management with `
- Gradle property: `./gradlew integration -Pserver.port=8080`
- System property: `./gradlew integration -Dserver.port=8080`

**Azure Environment Variable:** To use azure blob, you should define following parameters on your env variables.
- Azure Storage Enable: `export AZURE_STORAGE_ENABLED=true `
- Account Name: `export AZURE_STORAGE_ACCOUNT_NAME= ... `
- Account Key: `export AZURE_STORAGE_ACCOUNT_KEY= ... `
**Azure Environment Variable:** To use Azure blob storage (upload only; no SAS URLs), set:
- Azure Storage Enable: `export AZURE_STORAGE_ENABLED=true`
- Account Name: `export AZURE_STORAGE_ACCOUNT_NAME=...`
- For local/integration (Azurite): `docker/docker-compose.integration.yml` defaults to Microsoft’s **public** Azurite key (`devstoreaccount1`); see **`docker/README.md`** for why that is safe to commit and why secret scanners may still flag it (we use `# gitleaks:allow` on that line). Override `AZURE_STORAGE_ACCOUNT_KEY` only for a real storage account.
- **Docker must be running** before `gradle composeBuild` / `integration`. The build runs `checkDockerDaemon` first so you get a short Gradle error instead of a vague compose failure. If you see `.../docker/desktop/docker.sock`: start **Docker Desktop**. If you use the Linux **docker.io** engine instead, run `unset DOCKER_HOST` and/or `export DOCKER_HOST=unix:///var/run/docker.sock` (or `docker context use default`).
- **Publishing Hub / APIM**: set `AZURE_LOCAL_DTS_APIMURL` (and other `AZURE_LOCAL_DTS_*` / `AZURE_REMOTE_DTS_*` as needed) in each environment; defaults in `application-azure.yml` no longer embed internal hostnames.

**Database information:** you can use postico to reach DB
- Database : `courtlistpublishing`
Expand Down
34 changes: 28 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-aspectj'

// Task Manager Service
implementation "uk.gov.hmcts.cp:task-manager-service:${jobManagerVersion}"

// --- Observability / Actuator / OTEL / Prometheus ---
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
Expand All @@ -111,16 +114,28 @@ dependencies {
// --- Data / DB ---
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.postgresql:postgresql'
implementation 'org.springframework.boot:spring-boot-starter-flyway'
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-database-postgresql'

implementation 'io.rest-assured:rest-assured:5.5.6'
implementation 'org.hibernate.validator:hibernate-validator:9.0.1.Final'
implementation 'org.apache.commons:commons-text:1.14.0'

// JSON Schema Validation (same as cp-framework-libraries context)
implementation "com.github.erosb:everit-json-schema:${everitJsonSchemaVersion}"

implementation 'com.google.guava:guava:33.3.1-jre'

// Azure Blob Storage
implementation 'com.azure:azure-storage-blob:12.25.1'
implementation 'com.azure:azure-identity:1.14.0'
// Azure Blob Storage – exclude Netty HTTP client (conflicts with Spring Boot 4's Netty 4.2.x)
// and use the JDK HttpClient backend instead (JDK 21)
implementation('com.azure:azure-storage-blob:12.33.2') {
exclude group: 'com.azure', module: 'azure-core-http-netty'
}
implementation('com.azure:azure-identity:1.18.2') {
exclude group: 'com.azure', module: 'azure-core-http-netty'
}
implementation 'com.azure:azure-core-http-jdk-httpclient:1.0.4'
// Keep only if you actually use JWT primitives yourself (not via Spring Security)
implementation 'io.jsonwebtoken:jjwt:0.13.0'

Expand All @@ -143,7 +158,6 @@ dependencies {
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

testImplementation 'io.micrometer:micrometer-tracing-test'
testImplementation 'com.h2database:h2'
testImplementation "org.springframework.boot:spring-boot-starter-webmvc-test"
testImplementation "org.springframework.boot:spring-boot-starter-data-jpa-test"

Expand All @@ -152,6 +166,12 @@ dependencies {
pactVerificationTestImplementation 'au.com.dius.pact.provider:junit5:4.6.17'
pactVerificationTestImplementation 'au.com.dius.pact.provider:spring6:4.6.17'

// Testcontainers (BOM)
testImplementation platform("org.testcontainers:testcontainers-bom:${testcontainersBomVersion}")
testImplementation 'org.testcontainers:postgresql'
testImplementation 'org.testcontainers:junit-jupiter'
implementation "org.testcontainers:testcontainers"

// integrationTestImplementation dependencies are need as they aren't always propagate as expected,
// especially with platform BOMs, test runtime dependencies, or custom tasks. Explicit dependencies eliminate that risk.
integrationTestImplementation platform('org.junit:junit-bom:6.0.0')
Expand All @@ -161,6 +181,7 @@ dependencies {
exclude group: 'junit', module: 'junit'
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
integrationTestImplementation 'org.wiremock:wiremock-standalone:3.9.1'
integrationTestRuntimeOnly 'org.junit.platform:junit-platform-launcher'
integrationTestCompileOnly "org.projectlombok:lombok:${lombokVersion}"
integrationTestAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}"
Expand Down Expand Up @@ -262,7 +283,7 @@ tasks.named('composeBuild') { dependsOn tasks.named('bootJar') }

dockerCompose {
useComposeFiles = ['docker/docker-compose.integration.yml']
startedServices = ['app', 'db']
startedServices = ['app', 'db', 'wiremock', 'azurite']

buildBeforeUp = true
waitForTcpPorts = true
Expand Down Expand Up @@ -337,9 +358,10 @@ tasks.register('integration', Test) {
useJUnitPlatform()

dependsOn tasks.composeUp
finalizedBy tasks.composeDown
//finalizedBy tasks.composeDown

systemProperty 'app.baseUrl', "http://localhost:${serverPort}/courtlistpublishing-service"
systemProperty 'wiremock.baseUrl', 'http://localhost:8089'

jvmArgs = ['-Xshare:off']
testLogging {
Expand Down
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ services:
- CP_CLP_DATASOURCE_URL=${CP_CLP_DATASOURCE_URL:-jdbc:postgresql://db:5432/courtlistpublishing}
- CP_CLP_DATASOURCE_USERNAME=${CP_CLP_DATASOURCE_USERNAME:-app}
- CP_CLP_DATASOURCE_PASSWORD=${CP_CLP_DATASOURCE_PASSWORD:-app}
- AZURE_STORAGE_ACCOUNT_NAME=${AZURE_STORAGE_ACCOUNT_NAME:test-account-name}
- AZURE_CLIENT_ID=${AZURE_CLIENT_ID:test-client-id}
- AZURE_TENANT_ID=${AZURE_TENANT_ID:test-tenant-id}
- AZURE_STORAGE_ACCOUNT_NAME=${AZURE_STORAGE_ACCOUNT_NAME:-test-account-name}
- AZURE_CLIENT_ID=${AZURE_CLIENT_ID:-test-client-id}
- AZURE_TENANT_ID=${AZURE_TENANT_ID:-test-tenant-id}
depends_on:
db:
condition: service_healthy
Expand Down
9 changes: 9 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Docker assets

## Integration compose and the Azurite “account key”

`docker-compose.integration.yml` sets a default `AZURE_STORAGE_ACCOUNT_KEY` that matches the **public, Microsoft-documented** default for the [Azurite](https://github.com/Azure/Azurite) blob emulator account `devstoreaccount1`.

- It is **not** a production secret and is **the same for every project** using the default emulator setup.
- Automated secret scanners (e.g. Gitleaks) may still report it because it **looks like** a high-entropy key; the repository marks that line with `# gitleaks:allow` and keeps this note for reviewers.
- To use **real** Azure Storage in integration runs, override `AZURE_STORAGE_ACCOUNT_KEY` (and related settings) via environment; do not use the Azurite default for non-emulator accounts.
46 changes: 45 additions & 1 deletion docker/docker-compose.integration.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
services:
azurite:
image: mcr.microsoft.com/azure-storage/azurite:3.34.0
container_name: CP_CLP_azurite
command: azurite-blob --blobHost 0.0.0.0 --skipApiVersionCheck
ports:
- "10000:10000"

db:
image: postgres:16-alpine
container_name: CP_CLP_CP_CDK_DB_it
Expand All @@ -14,6 +21,21 @@ services:
timeout: 3s
retries: 30

wiremock:
image: wiremock/wiremock:3.9.1
container_name: CP_CLP_wiremock
ports:
- "8089:8080"
volumes:
- ../src/integrationTest/resources/wiremock/mappings:/home/wiremock/mappings
- ../src/integrationTest/resources/wiremock/__files:/home/wiremock/__files
healthcheck:
test: [ "CMD-SHELL", "curl -f http://localhost:8080/__admin/health || exit 1" ]
interval: 5s
timeout: 3s
retries: 10
restart: unless-stopped

app:
container_name: CP_CLP_app_it
build:
Expand All @@ -30,15 +52,37 @@ services:
CP_CLP_DATASOURCE_DRIVER_CLASS_NAME: org.postgresql.Driver
JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
SPRING_PROFILES_ACTIVE: integration
AZURE_STORAGE_ENABLED: false
AZURE_STORAGE_ENABLED: "true"
AZURE_STORAGE_CONTAINER_NAME: courtpublisher-blob-container
AZURE_STORAGE_ACCOUNT_NAME: "devstoreaccount1"
# ------------------------------------------------------------------
# Azurite default account key (Microsoft-published, not confidential).
# Documented for all devs: https://github.com/Azure/Azurite#default-storage-account
# Scanners match it as “generic API key” by entropy; safe to commit — see docker/README.md
# Override AZURE_STORAGE_ACCOUNT_KEY for a real Azure storage account.
# ------------------------------------------------------------------
AZURE_STORAGE_ACCOUNT_KEY: ${AZURE_STORAGE_ACCOUNT_KEY:-Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==} # gitleaks:allow
AZURE_STORAGE_BLOB_ENDPOINT: "http://azurite:10000/devstoreaccount1"
AZURE_CLIENT_ID: "integration-test-client"
AZURE_TENANT_ID: "integration-test-tenant"
MANAGEMENT_HEALTH_JMS_ENABLED: "false"
MANAGEMENT_ENDPOINT_HEALTH_PROBES_ENABLED: "false"
CP_CLP_AUTHZ_ENABLED: "false"
CP_CLP_HTTP_AUDIT_ENABLED: "false"
CP_CLP_AUDIT_ENABLED: "false"
# System user id required for reference data + document generator calls
COURTLISTPUBLISHING_SYSTEM_USER_ID: "11111111-1111-1111-1111-111111111111"
# WireMock service running in Docker container
COMMON_PLATFORM_QUERY_API_BASE_URL: "http://wiremock:8080"
CATH_BASE_URL: "http://wiremock:8080"
CATH_PUBLISHING_ENABLED: "true"
depends_on:
db:
condition: service_healthy
wiremock:
condition: service_started
azurite:
condition: service_started
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER_PORT:-8082}/courtlistpublishing-service/actuator/health || exit 1"]
interval: 10s
Expand Down
7 changes: 5 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ org.gradle.jvmargs=-Dpath=/usr/local/bin:/usr/bin:/bin
cpAuthFilterVersion=1.0.7
cpAuditFilterVersion=1.0.5
#other versions##
apiSpecVersion=0.0.8
apiSpecVersion=0.1.22
jobManagerVersion=1.0.9
log4JVersion=2.24.3
logbackVersion=1.5.18
lombokVersion=1.18.38
lombokVersion=1.18.38
testcontainersBomVersion=1.20.4
everitJsonSchemaVersion=1.14.6

This file was deleted.

43 changes: 43 additions & 0 deletions src/integrationTest/java/uk/gov/hmcts/cp/http/AbstractTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package uk.gov.hmcts.cp.http;

import org.junit.jupiter.api.BeforeAll;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

/**
* Base class for integration test classes that use WireMock.
* Resets WireMock stub mappings before each test class runs so tests start from a clean state
* (reloads mappings from the static files under wiremock/mappings).
* <p>
* WireMock runs in Docker; from the host the admin API is on port 8089 (see docker-compose).
*/
public abstract class AbstractTest {

private static final String WIREMOCK_BASE_URL =
System.getProperty("wiremock.baseUrl", "http://localhost:8089");
private static final String RESET_MAPPINGS = WIREMOCK_BASE_URL + "/__admin/mappings/reset";

@BeforeAll
static void initTest() {
resetWireMock();
}

static void resetWireMock() {
RestTemplate rest = new RestTemplate();
ResponseEntity<String> response = rest.exchange(
RESET_MAPPINGS,
HttpMethod.POST,
new HttpEntity<>(new HttpHeaders()),
String.class
);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new IllegalStateException(
"WireMock reset failed: " + response.getStatusCode() + " from " + RESET_MAPPINGS);
}
}


}
Loading
Loading