Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c533dff
Kubernetes Gateway API support for routing #36
Bethibande Feb 11, 2026
3943ff4
Kubernetes Gateway API support for routing #36
Bethibande Feb 11, 2026
e59f230
Kubernetes Gateway API support for routing #36
Bethibande Feb 12, 2026
48009f6
Kubernetes Gateway API support for routing #36
Bethibande Feb 12, 2026
02d5d94
Kubernetes Gateway API support for routing #36
Bethibande Feb 12, 2026
7fe549a
Kubernetes Gateway API support for routing #36
Bethibande Feb 12, 2026
f46d80b
Kubernetes Gateway API support for routing #36 debug
Bethibande Feb 12, 2026
15cd8cf
Kubernetes Gateway API support for routing #36 debug
Bethibande Feb 12, 2026
b6cfe41
Kubernetes Gateway API support for routing #36
Bethibande Feb 12, 2026
9f57f0d
Kubernetes Gateway API support for routing #36
Bethibande Feb 12, 2026
429d218
Kubernetes Gateway API support for routing #36
Bethibande Feb 12, 2026
2427016
Kubernetes Gateway API support for routing #36 more debugging...
Bethibande Feb 12, 2026
b6f369a
Kubernetes Gateway API support for routing #36
Bethibande Feb 12, 2026
da59949
Kubernetes Gateway API support for routing #36 fix pipeline
Bethibande Feb 12, 2026
7236ec7
Kubernetes Gateway API support for routing #36
Bethibande Feb 12, 2026
3aeb665
Kubernetes Gateway API support for routing #36 more debugging..
Bethibande Feb 12, 2026
c4f9c57
Kubernetes Gateway API support for routing #36 more debugging...
Bethibande Feb 12, 2026
8e2a473
Kubernetes Gateway API support for routing #36
Bethibande Feb 12, 2026
5f04992
Kubernetes Gateway API support for routing #36 and debug info once again
Bethibande Feb 12, 2026
991f838
Kubernetes Gateway API support for routing #36 and debug info once again
Bethibande Feb 13, 2026
7d0e59c
Kubernetes Gateway API support for routing #36
Bethibande Feb 13, 2026
324a551
Kubernetes Gateway API support for routing #36
Bethibande Feb 13, 2026
3d264f2
Kubernetes Gateway API support for routing #36...
Bethibande Feb 13, 2026
809ea8a
Kubernetes Gateway API support for routing #36
Bethibande Feb 13, 2026
0324029
Kubernetes Gateway API support for routing #36
Bethibande Feb 13, 2026
9821c47
update build job
Bethibande Feb 13, 2026
fdf1289
updated setup-gradle actions
Bethibande Feb 13, 2026
ea98f5c
Kubernetes Gateway API support for routing #36
Bethibande Feb 13, 2026
bac4a37
fix job
Bethibande Feb 13, 2026
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
25 changes: 12 additions & 13 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
distribution: 'temurin'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/actions/setup-gradle@v5
with:
cache-read-only: false

Expand All @@ -56,15 +56,16 @@ jobs:
# The next step will generate these files.
# This order is necessary since this step will generate the openapi-spec.
- name: Quarkus App Parts Build
run: ./gradlew :server:quarkusAppPartsBuild
run: ./gradlew :server:quarkusAppPartsBuild "-Pversion=${{ github.event.inputs.version }}"
continue-on-error: true

- name: Generate OpenAPI
run: ./gradlew :server:openApiGenerate
run: ./gradlew :server:openApiGenerate "-Pversion=${{ github.event.inputs.version }}"

- name: Build and Push Arch-Specific Image
run: |
./gradlew :server:build \
"-Pversion=${{ github.event.inputs.version }}" \
"-Dquarkus.native.container-build=true" \
"-Dquarkus.container-image.builder=docker" \
"-Dquarkus.native.enabled=true" \
Expand All @@ -87,13 +88,11 @@ jobs:

- name: Create and Push Multi-Arch Manifest
run: |
docker manifest create bethibande/repository:${{ github.event.inputs.version }} \
--amend bethibande/repository:${{ github.event.inputs.version }}-amd64 \
--amend bethibande/repository:${{ github.event.inputs.version }}-arm64

docker manifest create bethibande/repository:latest \
--amend bethibande/repository:${{ github.event.inputs.version }}-amd64 \
--amend bethibande/repository:${{ github.event.inputs.version }}-arm64

docker manifest push bethibande/repository:${{ github.event.inputs.version }}
docker manifest push bethibande/repository:latest
docker buildx imagetools create -t bethibande/repository:${{ github.event.inputs.version }} \
bethibande/repository:${{ github.event.inputs.version }}-amd64 \
bethibande/repository:${{ github.event.inputs.version }}-arm64

# Create the latest tag
docker buildx imagetools create -t bethibande/repository:latest \
bethibande/repository:${{ github.event.inputs.version }}-amd64 \
bethibande/repository:${{ github.event.inputs.version }}-arm64
2 changes: 1 addition & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ services:
volumes:
- minio:/data
repository:
image: bethibande/repository:1.0
image: bethibande/repository:latest
restart: unless-stopped
ports:
- "8080:8080"
Expand Down
2 changes: 2 additions & 0 deletions server/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ dependencies {
implementation("com.cronutils:cron-utils:9.2.1")

implementation("software.amazon.awssdk:s3:2.41.24")
implementation("software.amazon.awssdk:apache-client:2.41.26")

implementation("io.hypersistence:hypersistence-utils-hibernate-71:3.14.1")

implementation("com.bethibande.process:annotations:1.5")
implementation("io.quarkus:quarkus-kubernetes-client")
annotationProcessor("com.bethibande.process:processor:1.5")

// Jackson & Hibernate Search ORM
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.bethibande.repository.jpa.repository;

import com.bethibande.repository.repository.ManagedRepository;
import com.bethibande.repository.repository.RepositoryApplicationContext;
import com.bethibande.repository.repository.maven.MavenRepository;
import com.bethibande.repository.repository.oci.OCIRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
Expand All @@ -17,9 +18,9 @@ public enum PackageManager {
this.factory = factory;
}

public ManagedRepository createRepository(final Repository info, final ObjectMapper mapper) {
public ManagedRepository createRepository(final Repository info, final RepositoryApplicationContext ctx) {
try {
return factory.createRepository(info, mapper);
return factory.createRepository(info, ctx);
} catch (final JsonProcessingException ex) {
throw new RuntimeException(ex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,17 @@ public <T> T getMetadata(final RepositoryMetadataKey key) {
return (T) metadata.get(key);
}

public <T> T getMetadataOrDefault(final RepositoryMetadataKey key, final T defaultValue) {
final T value = getMetadata(key);
return value != null ? value : defaultValue;
}

public void setMetadata(final RepositoryMetadataKey key, final Object value) {
metadata.put(key, value);
if (value == null) {
metadata.remove(key);
} else {
metadata.put(key, value);
}
}

public boolean canView(final User user) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.bethibande.repository.jpa.repository;

import com.bethibande.repository.repository.ManagedRepository;
import com.bethibande.repository.repository.RepositoryApplicationContext;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

@FunctionalInterface
public interface RepositoryFactory {

ManagedRepository createRepository(final Repository info, final ObjectMapper mapper) throws JsonProcessingException;
ManagedRepository createRepository(final Repository info,
final RepositoryApplicationContext ctx) throws JsonProcessingException;

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.bethibande.repository.jpa.repository;

import com.bethibande.repository.k8s.KubernetesSupport;
import com.bethibande.repository.repository.ManagedRepository;
import com.bethibande.repository.repository.RepositoryApplicationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

Expand All @@ -11,6 +14,16 @@ public class RepositoryManager {
@Inject
protected ObjectMapper mapper;

@Inject
protected KubernetesSupport kubernetesSupport;

private RepositoryApplicationContext ctx;

@PostConstruct
protected void init() {
ctx = new RepositoryApplicationContext(mapper, kubernetesSupport);
}

public <T extends ManagedRepository> T findRepository(final String name, final PackageManager packageManager) {
final Repository repo = Repository.find("name = ?1 and packageManager = ?2", name, packageManager).firstResult();
if (repo == null) return null;
Expand All @@ -20,7 +33,7 @@ public <T extends ManagedRepository> T findRepository(final String name, final P

@SuppressWarnings("unchecked")
public <T extends ManagedRepository> T manage(final Repository repo) {
return (T) repo.packageManager.createRepository(repo, mapper);
return (T) repo.packageManager.createRepository(repo, this.ctx);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public boolean canView(final User user) {
return switch (type) {
case ANONYMOUS -> true;
case AUTHENTICATED -> user != null;
case USER -> Objects.equals(this.user.id, user.id);
case USER -> user != null && Objects.equals(this.user.id, user.id);
};
}

Expand All @@ -47,7 +47,7 @@ public boolean canWrite(final User user) {
return switch (type) {
case ANONYMOUS -> true;
case AUTHENTICATED -> user != null;
case USER -> Objects.equals(this.user.id, user.id);
case USER -> user != null && Objects.equals(this.user.id, user.id);
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.bethibande.process.annotation.EntityDTO;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
Expand All @@ -14,6 +15,7 @@

@Entity
@Indexed
@RegisterForReflection
@Table(name = "Users")
@EntityDTO(excludeProperties = "id")
@EntityDTO(excludeProperties = "password")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package com.bethibande.repository.k8s;

import com.bethibande.repository.jpa.repository.PackageManager;
import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReview;
import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReviewBuilder;
import io.fabric8.kubernetes.api.model.gatewayapi.v1.HTTPRoute;
import io.fabric8.kubernetes.api.model.gatewayapi.v1.HTTPRouteBuilder;
import io.fabric8.kubernetes.api.model.gatewayapi.v1.HTTPRouteRuleBuilder;
import io.fabric8.kubernetes.api.model.gatewayapi.v1.ParentReferenceBuilder;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.quarkus.runtime.Startup;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Startup
@ApplicationScoped
public class KubernetesSupport {

private static final Logger LOGGER = LoggerFactory.getLogger(KubernetesSupport.class);

public static final String GATEWAY_API_GROUP = "gateway.networking.k8s.io";
public static final String HTTP_ROUTE_NAME = "httproutes";

@Inject
protected KubernetesClient client;

private String namespace;

protected boolean kubernetesSupport = true;
protected boolean canManageHttpRoutes = false;

protected boolean canAccessApi() {
try (final KubernetesClient timeoutClient = new KubernetesClientBuilder()
.withConfig(new ConfigBuilder()
.withConnectionTimeout(200)
.withRequestTimeout(200)
.withRequestRetryBackoffLimit(0)
.build())
.build()) {

timeoutClient.getApiGroups();
return true;
} catch (final Throwable _) {
return false;
}
}

@PostConstruct
protected void init() {
if (this.client == null) return;
if (!canAccessApi()) {
this.kubernetesSupport = false;
LOGGER.info("Disabled kubernetes support");
return;
}

this.namespace = client.getNamespace();

this.canManageHttpRoutes = hasPermission("create", HTTP_ROUTE_NAME, GATEWAY_API_GROUP)
&& hasPermission("delete", HTTP_ROUTE_NAME, GATEWAY_API_GROUP)
&& hasPermission("get", HTTP_ROUTE_NAME, GATEWAY_API_GROUP)
&& hasPermission("patch", HTTP_ROUTE_NAME, GATEWAY_API_GROUP)
&& hasPermission("list", HTTP_ROUTE_NAME, GATEWAY_API_GROUP);
}

public boolean isEnabled() {
return this.kubernetesSupport;
}

public boolean hasHttpRouteSupport() {
return this.canManageHttpRoutes;
}

protected String toHttpRouteName(final String repository, final PackageManager packageManager) {
return "%s-%s".formatted(packageManager.name().toLowerCase(), repository);
}

public void deleteRepositoryHttpRouteIfExists(final String repository, final PackageManager packageManager) {
this.client.resources(HTTPRoute.class)
.inNamespace(this.namespace)
.withName(toHttpRouteName(repository, packageManager))
.delete();
}

public void createOrUpdateRepositoryHttpRoute(final String repository,
final PackageManager packageManager,
final String host,
final String targetService,
final int targetPort,
final String gateway,
final String gatewayNamespace) {
final HTTPRoute route = new HTTPRouteBuilder()
.withNewMetadata()
.withName(toHttpRouteName(repository, packageManager))
.withNamespace(this.namespace)
.endMetadata()
.withNewSpec()
.withHostnames(host)
.withParentRefs(new ParentReferenceBuilder()
.withName(gateway)
.withNamespace(gatewayNamespace)
.withGroup(GATEWAY_API_GROUP)
.withKind("Gateway")
.build())
.withRules(new HTTPRouteRuleBuilder()
.addNewMatch()
.withNewPath()
.withType("PathPrefix")
.withValue("/v2")
.endPath()
.endMatch()
.addNewFilter()
.withType("URLRewrite")
.withNewUrlRewrite()
.withNewPath()
.withType("ReplacePrefixMatch")
.withReplacePrefixMatch("/repositories/%s/%s/v2".formatted(packageManager.name().toLowerCase(), repository))
.endPath()
.endUrlRewrite()
.endFilter()
.addNewBackendRef()
.withName(targetService)
.withPort(targetPort)
.endBackendRef()
.build())
.endSpec()
.build();

client.resource(route).serverSideApply();
}

protected boolean hasPermission(final String verb, final String resource, final String group) {
final SelfSubjectAccessReview review = new SelfSubjectAccessReviewBuilder()
.withNewSpec()
.withNewResourceAttributes()
.withNamespace(this.namespace)
.withVerb(verb)
.withGroup(group)
.withResource(resource)
.endResourceAttributes()
.endSpec()
.build();

final SelfSubjectAccessReview result = client.resource(review).create();

return result.getStatus().getAllowed();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.bethibande.repository.jpa.artifact.ArtifactVersion;
import com.bethibande.repository.jpa.repository.Repository;
import com.bethibande.repository.jpa.user.User;
import jakarta.persistence.EntityManager;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.session.SearchSession;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.bethibande.repository.repository;

import com.bethibande.repository.k8s.KubernetesSupport;
import com.fasterxml.jackson.databind.ObjectMapper;

public record RepositoryApplicationContext(
ObjectMapper objectMapper,
KubernetesSupport kubernetesSupport
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.bethibande.repository.repository;

/**
* An interface that extends {@link ManagedRepository} to provide additional functionality
* for handling repository update notifications.
*/
public interface RepositoryUpdatedNotifier extends ManagedRepository {

void processUpdate(final UpdateType type);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.bethibande.repository.repository;

public enum UpdateType {

CREATE,
UPDATE,
DELETE

}
Loading
Loading