ci: use shared ci-workflows Maven workflow #61

Merged
james.maes merged 7 commits from ci/ci-workflows into develop 2026-11-06 00:48:16 +00:00
13 changed files with 68 additions and 109 deletions

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
name: build
on:
push:
jobs:
maven:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@main
secrets: inherit
-25
View File
@@ -1,25 +0,0 @@
# Standard QRun release caller (v2, thin contract): every push and manual dispatch delegates to the protected qrun-release-v1 channel. Do not edit per repository.
name: qrun-release
on:
push: {}
workflow_dispatch: {}
permissions:
contents: read
actions: read
jobs:
release:
uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/java-docker.yaml@qrun-release-v1
with:
runner: linux-small
docker-runner: container-build
browsers: true
browser-runner: browser-build
postpublish:
if: ${{ needs.release.outputs.capture == 'true' }}
needs: release
permissions:
contents: write
actions: write
uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/qrun-source-version.yaml@qrun-release-v1
with:
runner: linux-small
-72
View File
@@ -1,72 +0,0 @@
name: Release candidate
'on':
workflow_dispatch:
inputs:
dry_run:
description: Plan a cut without changing release state
type: boolean
default: false
finalize:
description: Retry finalization of an existing cut; do not cut another RC
type: boolean
default: false
recover:
description: Recover the same published RC after a deployment timeout; never
reupload
type: boolean
default: false
circle_build:
description: Original release run id (recovery only)
type: string
default: ''
merge_sha:
description: Existing cut merge_sha (finalize retry only)
type: string
default: ''
bump_sha:
description: Existing cut bump_sha (finalize retry only)
type: string
default: ''
branch:
description: Existing cut branch (finalize retry only)
type: string
default: ''
version:
description: Existing cut version (finalize retry only)
type: string
default: ''
pull_request:
types:
- closed
branches:
- develop
permissions:
contents: read
jobs:
release-rc:
# The old qbit-rc-published repository_dispatch is replaced by the already-proven workflow_dispatch
# completion: the publisher's own ActionsWRITE job dispatches this workflow with finalize=true plus
# merge_sha/bump_sha/branch/version (same inputs as the manual finalize retry).
permissions:
contents: write
actions: read
pull-requests: write
if: github.event_name == 'workflow_dispatch'
|| (github.event_name == 'pull_request' && github.event.pull_request.merged
== true && github.event.pull_request.base.ref == 'develop' && contains(github.event.pull_request.labels.*.name,
'release:rc'))
uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/qbit-release-rc.yaml@qrun-release-v1
with:
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false
}}
finalize: ${{ github.event_name == 'workflow_dispatch' && inputs.finalize }}
recover: ${{ github.event_name == 'workflow_dispatch' && inputs.recover || false
}}
circle_build: ${{ github.event_name == 'workflow_dispatch' && inputs.circle_build
|| '' }}
merge_sha: ${{ inputs.merge_sha || '' }}
bump_sha: ${{ inputs.bump_sha || '' }}
branch: ${{ inputs.branch || '' }}
version: ${{ inputs.version || '' }}
secrets:
QRUN_TAG_SIGNING_KEY: ${{ secrets.QRUN_TAG_SIGNING_KEY }}
@@ -161,7 +161,7 @@ public class SchemaDriftTest
{
return (surfacePath);
}
String regex = surfacePath.replace("{key}", "\\{[^/]+\\}");
String regex = java.util.regex.Pattern.quote(surfacePath).replace("{key}", "\\E\\{[^/]+\\}\\Q");
Iterator<Map.Entry<String, JsonNode>> fields = paths.fields();
while(fields.hasNext())
{
@@ -255,9 +255,36 @@ public final class AuthentikTestStack
TestApi admin = admin();
try
{
long[] nextRetry = { System.currentTimeMillis() + 60_000 };
waitFor(Duration.ofMinutes(8), "all three kof22 blueprints applied", () ->
isSuccessful(admin, INFRA_BLUEPRINT) && isSuccessful(admin, BOOTSTRAP_BLUEPRINT) && isSuccessful(admin, TOKEN_BLUEPRINT)
{
////////////////////////////////////////////////////////////////////
// built-in blueprints may race our custom dependencies on first //
// boot, leaving one of ours in "error" or never applied //
// ("unknown"); re-apply it (throttled). //
////////////////////////////////////////////////////////////////////
if(System.currentTimeMillis() >= nextRetry[0])
{
nextRetry[0] = System.currentTimeMillis() + 30_000;
for(String name : List.of(INFRA_BLUEPRINT, BOOTSTRAP_BLUEPRINT, TOKEN_BLUEPRINT))
{
try
{
JSONObject current = blueprint(name);
if(current != null && List.of("error", "unknown").contains(current.optString("status")))
{
admin.post("/managed/blueprints/" + current.getString("pk") + "/apply/", new JSONObject());
}
}
catch(Exception e)
{
// Bootstrap authentication and built-in dependencies settle after HTTP readiness.
}
}
}
return (isSuccessful(admin, INFRA_BLUEPRINT) && isSuccessful(admin, BOOTSTRAP_BLUEPRINT) && isSuccessful(admin, TOKEN_BLUEPRINT)
&& admin.first("/core/users/?username=" + SERVICE_ACCOUNT) != null);
});
}
catch(IllegalStateException e)
{
@@ -278,8 +305,16 @@ public final class AuthentikTestStack
}
try
{
worker.getLogs().lines().filter(line -> line.contains("blueprint") && (line.contains("error") || line.contains("warning") || line.contains("fail")))
worker.getLogs().lines().filter(line -> (line.contains("blueprint") && (line.contains("error") || line.contains("warning") || line.contains("fail")))
|| line.contains("\"level\": \"error\"") || line.contains("Traceback"))
.filter(line -> !line.contains("clear_failed_blueprints"))
.limit(60).forEach(line -> System.err.println("worker: " + line));
////////////////////////////////////////////////////////////////
// blueprints re-applied through the API run in the server //
////////////////////////////////////////////////////////////////
server.getLogs().lines().filter(line -> line.contains("\"level\": \"error\"") || line.contains("\"level\": \"warning\"") || line.contains("Traceback"))
.filter(line -> !line.contains("Base URL is not configured"))
.limit(60).forEach(line -> System.err.println("server: " + line));
}
catch(RuntimeException unavailable)
{
@@ -174,7 +174,7 @@ class InvitationEnrollmentContractIT
}
assertThat(used).isNotNull();
assertThat(used.getJSONObject("user").optInt("pk", -1)).isNotEqualTo(userPk);
var config = assignment == null ? new com.kingsrook.qbits.authentik.AuthentikQBitConfig().withBaseUrl(stack.baseUrl())
var config = assignment == null ? new com.kingsrook.qbits.authentik.AuthentikQBitConfig().withBaseUrl(stack.baseUrl()).withAllowInsecureHttp(true)
.withApiToken(stack.serviceAccountToken()).withAppKey("kof22-admin").withListCacheTtlSeconds(0) : assignment.config;
var instance = assignment == null ? com.kingsrook.qbits.authentik.AuthentikTestSupport.produce(config) : assignment.instance;
com.kingsrook.qqq.backend.core.context.QContext.init(instance,
@@ -265,7 +265,7 @@ class InvitationEnrollmentContractIT
String actor = create(admin, "/core/users/", new JSONObject().put("username", owner + "-operator").put("name", "Invitation operator")
.put("email", owner + "-operator@example.test").put("type", "internal").put("path", "users/invitation-contract"), cleanup);
assertThat(admin.post("/core/groups/" + admins + "/add_user/", new JSONObject().put("pk", Integer.parseInt(actor))).status()).isEqualTo(204);
config = new AuthentikQBitConfig().withBaseUrl(stack.baseUrl()).withApiToken(stack.serviceAccountToken()).withAppKey("kof22-admin")
config = new AuthentikQBitConfig().withBaseUrl(stack.baseUrl()).withAllowInsecureHttp(true).withApiToken(stack.serviceAccountToken()).withAppKey("kof22-admin")
.withListCacheTtlSeconds(0).withGroupCacheTtlSeconds(0).withInvitationFlow(owner).withLocalBackendName("memory")
.withRecoveryEmailStage("kof22-recovery-email")
.withFeatures(EnumSet.of(AuthentikFeature.READ, AuthentikFeature.USERS, AuthentikFeature.GROUPS, AuthentikFeature.MEMBERSHIP));
@@ -96,7 +96,7 @@ class Phase1ReadContractIT
void beforeEach() throws QException
{
config = new AuthentikQBitConfig()
.withBaseUrl(stack.baseUrl())
.withBaseUrl(stack.baseUrl()).withAllowInsecureHttp(true)
.withApiToken(stack.serviceAccountToken())
.withAppKey("kof22-admin")
.withApplicationsInScope(List.of(AuthentikTestStack.APP_SLUG))
@@ -126,7 +126,7 @@ class QbitReadContractIT
void beforeEach() throws QException
{
config = new AuthentikQBitConfig()
.withBaseUrl(stack.baseUrl())
.withBaseUrl(stack.baseUrl()).withAllowInsecureHttp(true)
.withApiToken(stack.serviceAccountToken())
.withAppKey("kof22-admin")
.withListCacheTtlSeconds(0);
@@ -155,7 +155,7 @@ class QbitWriteContractIT
{
MemoryRecordStore.getInstance().reset();
config = new AuthentikQBitConfig()
.withBaseUrl(stack.baseUrl())
.withBaseUrl(stack.baseUrl()).withAllowInsecureHttp(true)
.withApiToken(stack.serviceAccountToken())
.withAppKey("kof22-admin")
.withListCacheTtlSeconds(0)
@@ -89,7 +89,7 @@ class SchemaDriftIT
for(java.util.Iterator<String> it = paths.fieldNames(); it.hasNext(); )
{
String candidate = it.next();
if(candidate.equals(path) || candidate.matches(path.replace("{key}", "\\{[^/]+\\}")))
if(candidate.equals(path) || candidate.matches(java.util.regex.Pattern.quote(path).replace("{key}", "\\E\\{[^/]+\\}\\Q")))
{
return (SchemaDriftTest.parameterNames(schema, paths.get(candidate).get(operation.method())));
}
@@ -579,7 +579,7 @@ class TokenContractIT
private void configureQbit(int owner, String subject) throws Exception
{
MemoryRecordStore.getInstance().reset();
config = new AuthentikQBitConfig().withBaseUrl(stack.baseUrl()).withApiToken(stack.serviceAccountToken()).withAppKey("kof22-admin")
config = new AuthentikQBitConfig().withBaseUrl(stack.baseUrl()).withAllowInsecureHttp(true).withApiToken(stack.serviceAccountToken()).withAppKey("kof22-admin")
.withListCacheTtlSeconds(0).withGroupCacheTtlSeconds(0).withBulkPacingMillis(0).withLocalBackendName("memory")
.withRecoveryEmailStage("kof22-recovery-email")
.withFeatures(EnumSet.of(AuthentikFeature.READ, AuthentikFeature.USERS, AuthentikFeature.TOKENS))
@@ -16,6 +16,7 @@
package com.kingsrook.qbits.authentik.sync;
import java.util.Set;
import java.util.UUID;
import javax.sql.DataSource;
import com.kingsrook.qbits.authentik.RequiresDockerCondition;
@@ -27,6 +28,7 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.extension.ExtendWith;
import org.postgresql.ds.PGSimpleDataSource;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.PostgreSQLContainer;
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.PortBinding;
@@ -39,6 +41,7 @@ import com.github.dockerjava.api.model.Ports;
@ExtendWith(RequiresDockerCondition.class)
class AuthentikMirrorPostgresIT extends AuthentikMirrorSqlTest
{
private static final Set<String> LOCAL_DOCKER_HOSTS = Set.of("localhost", "127.0.0.1");
private static PostgreSQLContainer<?> postgres;
private String schema;
@@ -55,7 +58,11 @@ class AuthentikMirrorPostgresIT extends AuthentikMirrorSqlTest
.withCreateContainerCmdModifier(command ->
{
command.withName(owner);
command.getHostConfig().withPortBindings(new PortBinding(Ports.Binding.bindIp("127.0.0.1"), new ExposedPort(5432)));
// Loopback-only only when the daemon is local; a remote daemon (CI) is reached via its host address.
if(LOCAL_DOCKER_HOSTS.contains(DockerClientFactory.instance().dockerHostIpAddress()))
{
command.getHostConfig().withPortBindings(new PortBinding(Ports.Binding.bindIp("127.0.0.1"), new ExposedPort(5432)));
}
});
try
{
@@ -17,6 +17,7 @@
package com.kingsrook.qbits.authentik.sync;
import java.sql.SQLException;
import java.util.Set;
import java.util.UUID;
import javax.sql.DataSource;
import com.kingsrook.qbits.authentik.RequiresDockerCondition;
@@ -24,6 +25,7 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.extension.ExtendWith;
import org.postgresql.ds.PGSimpleDataSource;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.PostgreSQLContainer;
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.PortBinding;
@@ -36,6 +38,7 @@ import com.github.dockerjava.api.model.Ports;
@ExtendWith(RequiresDockerCondition.class)
class AuthentikWebhookInboxPostgresIT extends AuthentikWebhookInboxTest
{
private static final Set<String> LOCAL_DOCKER_HOSTS = Set.of("localhost", "127.0.0.1");
private static PostgreSQLContainer<?> postgres;
private String schema;
@@ -53,7 +56,11 @@ class AuthentikWebhookInboxPostgresIT extends AuthentikWebhookInboxTest
.withCreateContainerCmdModifier(command ->
{
command.withName(owner);
command.getHostConfig().withPortBindings(new PortBinding(Ports.Binding.bindIp("127.0.0.1"), new ExposedPort(5432)));
// Loopback-only only when the daemon is local; a remote daemon (CI) is reached via its host address.
if(LOCAL_DOCKER_HOSTS.contains(DockerClientFactory.instance().dockerHostIpAddress()))
{
command.getHostConfig().withPortBindings(new PortBinding(Ports.Binding.bindIp("127.0.0.1"), new ExposedPort(5432)));
}
});
try
{