diff --git a/.editorconfig b/.editorconfig index eff1274..f458c8c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,7 +4,7 @@ root = true insert_final_newline = true [{*.kt,*.kts}] -ident_size = 4 +indent_size = 4 max_line_length = 80 ktlint_code_style = ktlint_official ktlint_experimental = enabled diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d5ecc42..4c86567 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,15 +13,9 @@ jobs: contents: write steps: - name: Code Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: 'adopt' - java-version: '21' - - - name: Get Gradle Version + - name: Get release attributes run: | echo "TAGS=latest" >> $GITHUB_ENV SUFFIX="" @@ -35,12 +29,23 @@ jobs: run: | URL=$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/tags/${{ env.VERSION }} echo "$URL" - CODE=$(curl -s -o /dev/null -w "%{http_code}" "$URL") + CODE=$(curl -s -H "Authorization: Bearer ${{ github.token }}" -o /dev/null -w "%{http_code}" "$URL") if [ "$CODE" != 404 ]; then echo "Release '$VERSION' already exists. ($CODE)" exit 1 fi + - name: Setup Java + uses: actions/setup-java@v5 + with: + distribution: 'adopt' + java-version: '21' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-read-only: false + - name: Generating self-signed certificate run: | openssl req \ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2be47e1..63b2e20 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,14 +9,19 @@ jobs: timeout-minutes: 20 steps: - name: Code Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'adopt' java-version: '21' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-read-only: false + - name: Generating self-signed certificate run: | openssl req \ @@ -30,4 +35,4 @@ jobs: -subj "/CN=localhost" - name: Tests - run: ./gradlew test --no-daemon + run: ./gradlew test diff --git a/.gitignore b/.gitignore index d276e03..7cc3ff1 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ nb-configuration.xml # Certificates *.pem + +ktlint diff --git a/build-image.sh b/build-image.sh index f37a236..a856599 100755 --- a/build-image.sh +++ b/build-image.sh @@ -8,10 +8,15 @@ if [ ! -f "build/httpbucket-$VERSION-runner" ] then ./gradlew clean - ./gradlew build -Dquarkus.package.type=native \ - -Dquarkus.native.container-build=true + ./gradlew build -Dquarkus.package.jar.enabled=false \ + -Dquarkus.native.enabled=true \ + -Dquarkus.native.compression.level=5 fi docker build . -f src/main/docker/Dockerfile.native-micro \ -t "httpbucket-local:$VERSION" \ -t "httpbucket-local:latest" + +docker run --rm --name httpbucket \ + -p 8080:8080 -p 8443:8443 \ + "httpbucket-local:$VERSION" diff --git a/build.gradle.kts b/build.gradle.kts index 25c42ab..1f360d4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - kotlin("jvm") version "2.1.21" - kotlin("plugin.allopen") version "2.1.21" + kotlin("jvm") version "2.2.21" + kotlin("plugin.allopen") version "2.2.21" id("io.quarkus") } @@ -37,7 +37,7 @@ dependencies { } group = "com.testainers" -version = "0.2.0" +version = "0.3.0" java { sourceCompatibility = JavaVersion.VERSION_21 @@ -50,6 +50,8 @@ tasks.withType { "org.jboss.logmanager.LogManager", ) + jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED") + testLogging { events( "PASSED", diff --git a/coverage.sh b/coverage.sh index 9540964..8136b01 100755 --- a/coverage.sh +++ b/coverage.sh @@ -6,4 +6,4 @@ set -e /bin/cp -rf helpers/jacoco-report/* build/jacoco-report/. -# /opt/google/chrome/google-chrome build/coverage/index.html +# /opt/google/chrome/google-chrome "file://$(pwd)/build/jacoco-report/index.html" diff --git a/gradle.properties b/gradle.properties index 4509cfa..a8fe039 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,17 @@ -#Gradle properties +# Gradle properties + quarkusPluginId=io.quarkus -quarkusPluginVersion=3.23.4 +quarkusPluginVersion=3.30.6 quarkusPlatformGroupId=io.quarkus.platform quarkusPlatformArtifactId=quarkus-bom -quarkusPlatformVersion=3.23.4 +quarkusPlatformVersion=3.30.6 + +# Executa tasks em paralelo (ex: compila modulo A e B ao mesmo tempo) +org.gradle.parallel=true + +# Habilita o cache local de build (reutiliza outputs de tasks) +org.gradle.caching=true + +# Aumenta a memória da JVM (ajuste conforme a capacidade do runner do GitHub) +# O runner padrão tem 7GB de RAM total. Não aloque tudo para o Gradle. +org.gradle.jvmargs=-Xmx3g -XX:+UseParallelGC diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c05..c1962a7 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 19cfad9..af0e1ab 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +# https://gradle.org/release-checksums/ +distributionSha256Sum=f86344275d1b194688dd330abf9f6f2344cd02872ffee035f2d1ea2fd60cf7f3 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-all.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm index 14fed84..c826bc6 100644 --- a/src/main/docker/Dockerfile.jvm +++ b/src/main/docker/Dockerfile.jvm @@ -77,7 +77,7 @@ # accessed directly. (example: "foo.example.com,bar.example.com") # ### -FROM registry.access.redhat.com/ubi8/openjdk-21:1.21 +FROM registry.access.redhat.com/ubi9/openjdk-21:1.23 ENV LANGUAGE='en_US:en' @@ -88,9 +88,7 @@ COPY --chown=185 build/quarkus-app/app/ /deployments/app/ COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/ EXPOSE 8080 - USER 185 - ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" diff --git a/src/main/docker/Dockerfile.legacy-jar b/src/main/docker/Dockerfile.legacy-jar index 4a2fd38..b83dbd8 100644 --- a/src/main/docker/Dockerfile.legacy-jar +++ b/src/main/docker/Dockerfile.legacy-jar @@ -77,7 +77,7 @@ # accessed directly. (example: "foo.example.com,bar.example.com") # ### -FROM registry.access.redhat.com/ubi8/openjdk-21:1.21 +FROM registry.access.redhat.com/ubi9/openjdk-21:1.23 ENV LANGUAGE='en_US:en' @@ -85,9 +85,7 @@ COPY build/lib/* /deployments/lib/ COPY build/*-runner.jar /deployments/quarkus-run.jar EXPOSE 8080 - USER 185 - ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" diff --git a/src/main/docker/Dockerfile.native b/src/main/docker/Dockerfile.native index 0454814..ff85878 100644 --- a/src/main/docker/Dockerfile.native +++ b/src/main/docker/Dockerfile.native @@ -13,19 +13,17 @@ # # docker run -i --rm -p 8080:8080 quarkus/httpbucket # +# The ` registry.access.redhat.com/ubi9/ubi-minimal:9.7` base image is based on UBI 9. +# To use UBI 8, switch to `quay.io/ubi8/ubi-minimal:8.10`. ### -FROM registry.access.redhat.com/ubi9/ubi-minimal:9.5 - +FROM registry.access.redhat.com/ubi9/ubi-minimal:9.7 WORKDIR /work/ - RUN chown 1001 /work \ && chmod "g+rwX" /work \ && chown 1001:root /work - -COPY --chown=1001:root --chmod=0755 build/*-runner /work/application +COPY --chown=1001:root --chmod=0755 build/*-runner /work/httpbucket EXPOSE 8080 - USER 1001 -ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] +ENTRYPOINT ["./httpbucket", "-Dquarkus.http.host=0.0.0.0"] diff --git a/src/main/docker/Dockerfile.native-micro b/src/main/docker/Dockerfile.native-micro index dbec105..c2618f1 100644 --- a/src/main/docker/Dockerfile.native-micro +++ b/src/main/docker/Dockerfile.native-micro @@ -16,11 +16,11 @@ # # docker run -i --rm -p 8080:8080 quarkus/httpbucket # +# The `quay.io/quarkus/ubi9-quarkus-micro-image:2.0` base image is based on UBI 9. +# To use UBI 8, switch to `quay.io/quarkus/quarkus-micro-image:2.0`. ### -FROM quay.io/quarkus/quarkus-micro-image:2.0 - +FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0 WORKDIR /work/ - RUN chown 1001 /work \ && chmod "g+rwX" /work \ && chown 1001:root /work @@ -31,13 +31,12 @@ ADD --chown=1001:root \ RUN chmod +x /usr/bin/check -COPY --chown=1001:root --chmod=0755 build/*-runner /work/application +COPY --chown=1001:root --chmod=0755 build/*-runner /work/httpbucket COPY --chown=1001:root cert.pem /work/cert.pem COPY --chown=1001:root key.pem /work/key.pem EXPOSE 8080 EXPOSE 8443 - USER 1001 -ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] +ENTRYPOINT ["./httpbucket", "-Dquarkus.http.host=0.0.0.0"] diff --git a/src/main/kotlin/com/testainers/BasicAuthResource.kt b/src/main/kotlin/com/testainers/BasicAuthResource.kt index dcf06f2..adc7851 100644 --- a/src/main/kotlin/com/testainers/BasicAuthResource.kt +++ b/src/main/kotlin/com/testainers/BasicAuthResource.kt @@ -3,7 +3,10 @@ package com.testainers import io.quarkus.security.Authenticated import io.vertx.core.http.HttpServerRequest import jakarta.ws.rs.* -import jakarta.ws.rs.core.* +import jakarta.ws.rs.core.HttpHeaders +import jakarta.ws.rs.core.MediaType +import jakarta.ws.rs.core.Response +import jakarta.ws.rs.core.UriInfo import org.eclipse.microprofile.openapi.annotations.responses.APIResponse import org.eclipse.microprofile.openapi.annotations.responses.APIResponses import org.jboss.resteasy.reactive.RestHeader @@ -87,7 +90,7 @@ class BasicAuthResource( bodyMap["message"] = "Forbidden." bodyMap["body"] = body - if (auth.isNullOrBlank()) { + if (auth == null) { code = 401 bodyMap["message"] = "Authorization header not present." } else { diff --git a/src/main/kotlin/com/testainers/LengthResource.kt b/src/main/kotlin/com/testainers/LengthResource.kt index 5387506..f914860 100644 --- a/src/main/kotlin/com/testainers/LengthResource.kt +++ b/src/main/kotlin/com/testainers/LengthResource.kt @@ -1,7 +1,10 @@ package com.testainers +import io.quarkus.vertx.http.Uncompressed import jakarta.ws.rs.* -import jakarta.ws.rs.core.* +import jakarta.ws.rs.core.HttpHeaders +import jakarta.ws.rs.core.MediaType +import jakarta.ws.rs.core.Response import org.eclipse.microprofile.openapi.annotations.media.Content import org.eclipse.microprofile.openapi.annotations.media.Schema import org.eclipse.microprofile.openapi.annotations.parameters.Parameter @@ -26,6 +29,7 @@ import org.jboss.resteasy.reactive.RestHeader APIResponse(responseCode = "500", description = "Invalid size: X."), ) class LengthResource { + @Uncompressed @GET fun get( @RestHeader(HttpHeaders.ACCEPT) accept: String, @@ -40,6 +44,7 @@ class LengthResource { ) size: Int, ): Response = internal(accept, size) + @Uncompressed @POST fun post( @RestHeader(HttpHeaders.ACCEPT) accept: String, @@ -54,6 +59,7 @@ class LengthResource { ) size: Int, ): Response = internal(accept, size) + @Uncompressed @PUT fun put( @RestHeader(HttpHeaders.ACCEPT) accept: String, @@ -68,6 +74,7 @@ class LengthResource { ) size: Int, ): Response = internal(accept, size) + @Uncompressed @PATCH fun patch( @RestHeader(HttpHeaders.ACCEPT) accept: String, @@ -82,6 +89,7 @@ class LengthResource { ) size: Int, ): Response = internal(accept, size) + @Uncompressed @DELETE fun delete( @RestHeader(HttpHeaders.ACCEPT) accept: String, @@ -100,7 +108,7 @@ class LengthResource { accept: String, size: Int, ): Response = - if (size < 1 || size > 2048) { + if (size !in 1..2048) { Response .status(500) .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) diff --git a/src/main/kotlin/com/testainers/RedirectResource.kt b/src/main/kotlin/com/testainers/RedirectResource.kt index 125b1ef..87e3a6c 100644 --- a/src/main/kotlin/com/testainers/RedirectResource.kt +++ b/src/main/kotlin/com/testainers/RedirectResource.kt @@ -1,7 +1,9 @@ package com.testainers import jakarta.ws.rs.* -import jakarta.ws.rs.core.* +import jakarta.ws.rs.core.HttpHeaders +import jakarta.ws.rs.core.MediaType +import jakarta.ws.rs.core.Response import org.eclipse.microprofile.openapi.annotations.media.Schema import org.eclipse.microprofile.openapi.annotations.parameters.Parameter import org.eclipse.microprofile.openapi.annotations.responses.APIResponse @@ -126,7 +128,7 @@ class RedirectResource { .build() } - if (code < 300 || code > 399) { + if (code !in 300..399) { return Response .status(500) .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) diff --git a/src/main/kotlin/com/testainers/StatusResource.kt b/src/main/kotlin/com/testainers/StatusResource.kt index 1986429..7369461 100644 --- a/src/main/kotlin/com/testainers/StatusResource.kt +++ b/src/main/kotlin/com/testainers/StatusResource.kt @@ -2,7 +2,9 @@ package com.testainers import io.vertx.core.http.HttpServerRequest import jakarta.ws.rs.* -import jakarta.ws.rs.core.* +import jakarta.ws.rs.core.MediaType +import jakarta.ws.rs.core.Response +import jakarta.ws.rs.core.UriInfo import org.eclipse.microprofile.openapi.annotations.media.Schema import org.eclipse.microprofile.openapi.annotations.parameters.Parameter @@ -86,7 +88,7 @@ class StatusResource( var status = code val responseBody = ResponseBody(request, uriInfo, body) - if (status < 200 || status > 599) { + if (status !in 200..599) { val message = if (status in 100..199) { "Informational responses are not supported: %d" diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a84bb57..bfeb1a8 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,21 +1,20 @@ # suppress inspection "UnusedProperty" for whole file # +%prod.smallrye.config.source.file.locations=/run/secrets +# +quarkus.default-locale=en-US +quarkus.locales=en-US quarkus.analytics.disabled=true quarkus.banner.enabled=false quarkus.application.name=httpbucket quarkus.ssl.native=true +%dev.quarkus.http.host=0.0.0.0 # quarkus.docker.dockerfile-native-path=src/main/docker/Dockerfile.native-micro # -%prod.smallrye.config.source.file.locations=/run/secrets -# quarkus.http.ssl.certificate.files=${httpbucket.ssl.cert:cert.pem} quarkus.http.ssl.certificate.key-files=${httpbucket.ssl.key:key.pem} # -quarkus.http.cors=true -quarkus.http.cors.origins=* -quarkus.http.cors.access-control-max-age=24H -# quarkus.info.path=/info # quarkus.smallrye-health.root-path=/health @@ -30,8 +29,15 @@ quarkus.swagger-ui.always-include=true quarkus.swagger-ui.path=/swagger-ui quarkus.swagger-ui.theme=flattop quarkus.swagger-ui.title=httpbucket -quarkus.swagger-ui.footer=© 2025 - Testainers +quarkus.swagger-ui.footer=© 2026 - Testainers # %test.quarkus.jacoco.footer=httpbucket %test.quarkus.jacoco.title=httpbucket %test.quarkus.jacoco.excludes=**/SimpleHealthCheck.class +# +quarkus.http.cors.enabled=true +quarkus.http.cors.origins=* +quarkus.http.cors.access-control-max-age=24H +# +quarkus.http.enable-compression=true +quarkus.http.enable-decompression=true diff --git a/src/test/kotlin/com/testainers/BaseResourceTest.kt b/src/test/kotlin/com/testainers/BaseResourceTest.kt index 637a019..e6f16f2 100644 --- a/src/test/kotlin/com/testainers/BaseResourceTest.kt +++ b/src/test/kotlin/com/testainers/BaseResourceTest.kt @@ -1,7 +1,9 @@ package com.testainers import io.restassured.RestAssured -import io.restassured.config.* +import io.restassured.config.LogConfig +import io.restassured.config.RedirectConfig +import io.restassured.config.RestAssuredConfig import io.restassured.http.ContentType import io.restassured.http.Method import io.restassured.specification.RequestSpecification @@ -40,7 +42,7 @@ abstract class BaseResourceTest { val result = mutableListOf() methods.forEach { method -> - if(method !in remove) { + if (method !in remove) { list.forEach { status -> result.add(Arguments.of(method, status)) } diff --git a/src/test/kotlin/com/testainers/BasicAuthResourceTest.kt b/src/test/kotlin/com/testainers/BasicAuthResourceTest.kt index c7fe1d4..2beaf8a 100644 --- a/src/test/kotlin/com/testainers/BasicAuthResourceTest.kt +++ b/src/test/kotlin/com/testainers/BasicAuthResourceTest.kt @@ -2,8 +2,8 @@ package com.testainers import io.quarkus.test.junit.QuarkusTest import io.restassured.http.Method -import jakarta.ws.rs.core.HttpHeaders -import org.hamcrest.Matchers.* +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.nullValue import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource @@ -48,40 +48,6 @@ class BasicAuthResourceTest : BaseResourceTest() { } } - @ParameterizedTest - @MethodSource("onlyMethods") - fun emptyHeader(method: Method) { - json(method) - .header(HttpHeaders.AUTHORIZATION, "") - .request(method, "/basic-auth/$user/$pass") - .then() - .statusCode(401) - .apply { - if (method != Method.HEAD) { - body( - "body.body", - if (method == Method.GET) { - nullValue() - } else { - equalTo(body) - }, - *bodyMatchers( - method, - mapOf( - "body.auth" to equalTo(false), - "body.user" to equalTo(user), - "body.pass" to equalTo(pass), - "body.message" to - equalTo( - "Authorization header not present.", - ), - ), - ), - ) - } - } - } - @ParameterizedTest @MethodSource("onlyMethods") fun success(method: Method) {