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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
.kotlin

server/src/main/webui/src/generated
server/src/main/webui-2/src/generated

### IntelliJ IDEA ###
.idea
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public void run(final Object config) {
() -> Repository.<Repository>listAll()
.stream()
.filter(repo -> repo.cleanupPolicies != null)
.filter(repo -> repo.cleanupPolicies.maxVersionCountPolicy() != null)
.filter(repo -> repo.cleanupPolicies.maxVersionCountPolicy().enabled())
.filter(repo -> repo.cleanupPolicies.maxAgePolicy() != null)
.filter(repo -> repo.cleanupPolicies.maxAgePolicy().enabled())
.map(repo -> this.repositoryManager.<MavenRepository>manage(repo))
.toList()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.quarkus.panache.common.Sort;
import jakarta.persistence.*;
import org.hibernate.annotations.Type;
import org.hibernate.search.engine.backend.types.Sortable;
import org.hibernate.search.mapper.pojo.automaticindexing.ReindexOnUpdate;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.*;

Expand Down Expand Up @@ -35,11 +36,11 @@ public static Instant findMaxUpdated(final long repositoryId) {
@Column(nullable = false, columnDefinition = "varchar(128)")
public String version;

@GenericField
@GenericField(sortable = Sortable.YES)
@Column(nullable = false, columnDefinition = "timestamptz")
public Instant created;

@GenericField
@GenericField(sortable = Sortable.YES)
@Column(nullable = false, columnDefinition = "timestamptz")
public Instant updated;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.bethibande.repository.jpa.repository;

import com.bethibande.process.annotation.EntityDTO;
import com.bethibande.process.annotation.VirtualDTOField;
import com.bethibande.repository.jpa.repository.permissions.PermissionScope;
import com.bethibande.repository.jpa.user.UserRole;
import com.bethibande.repository.repository.ManagedRepository;
import com.bethibande.repository.repository.cleanup.CleanupPolicies;
import com.bethibande.repository.repository.security.AuthContext;
import com.bethibande.repository.repository.security.UserAuthContext;
import io.hypersistence.utils.hibernate.type.json.JsonBinaryType;
import io.quarkus.arc.Arc;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import org.hibernate.annotations.Type;
import org.hibernate.search.engine.backend.types.Sortable;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
Expand Down Expand Up @@ -38,32 +43,29 @@ public class Repository extends PanacheEntityBase {
@Column(columnDefinition = "jsonb")
public String settings;

/**
* This ia a generated field. Call {@link #updateMetadata()} to update its value.
* Never overwrite this value manually, any changes will be lost when persisting the entity.
*/
@Type(JsonBinaryType.class)
@Column(columnDefinition = "jsonb")
public Map<RepositoryMetadataKey, Object> metadata;
public Map<String, Object> metadata;

@Type(JsonBinaryType.class)
@Column(columnDefinition = "jsonb")
@Column(columnDefinition = "jsonb", nullable = false)
public CleanupPolicies cleanupPolicies;

@OneToMany(mappedBy = "repository", cascade = CascadeType.ALL, orphanRemoval = true)
public List<PermissionScope> permissions;

@SuppressWarnings("unchecked")
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;
}
@PreUpdate
@PrePersist
public void updateMetadata() {
try (final InstanceHandle<RepositoryManager> handle = Arc.container().instance(RepositoryManager.class)) {
final RepositoryManager manager = handle.get();
final ManagedRepository repository = manager.manage(this);

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,14 @@ public <T extends ManagedRepository> T findRepository(final String name, final P
return manage(repo);
}

@SuppressWarnings("unchecked")
public <T extends ManagedRepository> T manage(final Repository repo) {
return manage(repo, true);
}

@SuppressWarnings("unchecked")
public <T extends ManagedRepository> T manage(final Repository repo, final boolean useCache) {
if (!Hibernate.isInitialized(repo.permissions)) Hibernate.initialize(repo.permissions);
if (!useCache) return (T) repo.packageManager.createRepository(repo, this.ctx);

return (T) this.repositoryCache.get(repo.name, (_) -> repo.packageManager.createRepository(repo, this.ctx));
}
Expand Down

This file was deleted.

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

import java.sql.Timestamp;
import java.time.Instant;
import java.util.Map;

/**
* A ManagedRepository is an implementation of a repository for a specific package manager backed by a repository entity describing its features/settings.
Expand All @@ -21,6 +22,8 @@
*/
public interface ManagedRepository {

Map<String, Object> generateMetadata();

Repository getInfo();

default boolean canView(final AuthContext auth) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.bethibande.repository.repository;

import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.validation.constraints.NotNull;

@RegisterForReflection
public record S3Config(
String url,
String region,
String bucket,
String accessKey,
String secretKey
@NotNull String url,
@NotNull String region,
@NotNull String bucket,
@NotNull String accessKey,
@NotNull String secretKey
) {
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.bethibande.repository.repository.cleanup;

import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.validation.constraints.NotNull;

@RegisterForReflection
public record CleanupPolicies(
MaxAgeCleanupPolicy maxAgePolicy,
MaxVersionCountPolicy maxVersionCountPolicy
@NotNull
MaxAgeCleanupPolicy maxAgePolicy,
@NotNull
MaxVersionCountPolicy maxVersionCountPolicy
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.quarkus.narayana.jta.QuarkusTransaction;
import io.quarkus.narayana.jta.TransactionSemantics;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.validation.constraints.NotNull;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
Expand All @@ -17,6 +18,7 @@
public record MaxAgeCleanupPolicy(
boolean enabled,
long time,
@NotNull
ChronoUnit unit
) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;

public class MavenRepository implements ManagedRepository {
Expand All @@ -44,7 +46,7 @@ public class MavenRepository implements ManagedRepository {
private final MavenFileIndexer fileIndexer;
private final MavenMirrorSupport mirrorSupport;

private final S3Backend backend;
private final S3Backend backend; // TODO: Make this lazy
private final Executor executor;

public MavenRepository(final Repository info, final RepositoryApplicationContext ctx) throws JsonProcessingException {
Expand All @@ -67,6 +69,11 @@ public Repository getInfo() {
return info;
}

@Override
public Map<String, Object> generateMetadata() {
return Collections.emptyMap();
}

@Override
public void delete(final StoredFile file) {
this.backend.delete("%s/%s".formatted(info.name, file.key));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.bethibande.repository.jpa.files.OCISubject;
import com.bethibande.repository.jpa.files.StoredFile;
import com.bethibande.repository.jpa.repository.Repository;
import com.bethibande.repository.jpa.repository.RepositoryMetadataKey;
import com.bethibande.repository.k8s.KubernetesSupport;
import com.bethibande.repository.repository.*;
import com.bethibande.repository.repository.backend.MultipartUploadStatus;
Expand Down Expand Up @@ -52,7 +51,7 @@ public class OCIRepository implements RepositoryUpdatedNotifier, HasUploadSessio
private final OCIRepositoryConfig config;
private final KubernetesSupport kubernetesSupport;

private final S3Backend backend;
private final S3Backend backend; // TODO: Make this lazy
private final OCIMirrorSupport mirrorSupport;
private final OCIImageIndex index;

Expand Down Expand Up @@ -81,6 +80,13 @@ public OCIRepository(final Repository info,
this.index = new OCIImageIndex(this);
}

@Override
public Map<String, Object> generateMetadata() {
return Map.of(
"HOST_NAME", config.externalHostname()
);
}

@Override
public void delete(final StoredFile file) {
this.backend.delete(file.key);
Expand All @@ -97,7 +103,7 @@ public void processUpdate(final UpdateType type) {
this.kubernetesSupport.createOrUpdateRepositoryHttpRoute(
info.name,
info.packageManager,
info.getMetadata(RepositoryMetadataKey.HOST_NAME),
config.externalHostname(),
routing.targetService(),
routing.targetPort(),
routing.gatewayName(),
Expand All @@ -110,7 +116,6 @@ public void processUpdate(final UpdateType type) {
info.packageManager
);
}
info.persist();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public record OCIRepositoryConfig(
@NotNull
OCIRoutingConfig routingConfig,
Boolean allowRedeployments,
StandardMirrorConfig mirrorConfig
StandardMirrorConfig mirrorConfig,
@NotNull String externalHostname
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,16 @@ public record ArtifactQuery(
) {
}

public record VersionQuery(
@QueryParam("q") String text,
@QueryParam("p") @Min(0) int page,
@QueryParam("s") @Max(100) @Min(1) int pageSize,
@QueryParam("o") ArtifactSortOrder sortOrder
) {
}

@Inject
public SearchSession searchSession;
protected SearchSession searchSession;

@GET
@Transactional
Expand Down Expand Up @@ -93,6 +101,47 @@ public PagedResponse<ArtifactDTO> searchByGroupAndName(final @BeanParam Artifact
);
}

@GET
@Transactional
@Path("/{id}/versions/search")
public PagedResponse<ArtifactVersionDTO> searchVersions(final @PathParam("id") long id, final @BeanParam VersionQuery query) {
final Artifact artifact = Artifact.findById(id);
if (artifact == null) throw new NotFoundException();

final User self = authenticatedUser.getSelf();
final Repository repository = artifact.repository;
if (!repository.canView(AuthContext.ofUser(self))) throw new ForbiddenException("Unauthorized");

final SearchResult<ArtifactVersion> result = searchSession.search(ArtifactVersion.class)
.where(q -> {
final List<PredicateFinalStep> predicates = new ArrayList<>();
predicates.add(q.match().field("artifact.id").matching(id));
if (query.text() != null) {
predicates.add(q.match().field("version").matching(query.text()));
}

final BooleanPredicateClausesStep<?, ?> step = q.bool();
predicates.forEach(step::must);

return step;
})
.sort(query.sortOrder == ArtifactSortOrder.LAST_UPDATED ? (b -> b.field("updated").desc()) : TypedSearchSortFactory::score)
.fetch(query.page() * query.pageSize(), query.pageSize());

final long total = result.total().hitCount();
final int totalPages = (int) Math.ceil(total / (double) query.pageSize());

return new PagedResponse<>(
result.hits()
.stream()
.map(ArtifactVersionDTO::from)
.toList(),
query.page(),
totalPages,
(int) total
);
}

@GET
@Transactional
@Path("/{id}")
Expand All @@ -106,6 +155,20 @@ public ArtifactDTO getArtifact(final @PathParam("id") long id) {
return ArtifactDTO.from(artifact);
}

@GET
@Transactional
@Path("/version/{id}")
public ArtifactVersionDTO getArtifactVersion(final @PathParam("id") long versionId) {
final ArtifactVersion version = ArtifactVersion.findById(versionId);
if (version == null) throw new NotFoundException("Unknown version");

final User self = authenticatedUser.getSelf();
final Repository repository = version.artifact.repository;
if (!repository.canView(AuthContext.ofUser(self))) throw new ForbiddenException("Unauthorized");

return ArtifactVersionDTO.from(version);
}

@GET
@Transactional
@Path("/{id}/versions")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public RepositoryEndpoint(final AuthenticatedUser authenticatedUser, final Repos
}

protected void processUpdate(final Repository entity, final UpdateType type) {
final ManagedRepository managed = repositoryManager.manage(entity);
final ManagedRepository managed = this.repositoryManager.manage(entity, false);
if (managed instanceof RepositoryUpdatedNotifier notifier) {
notifier.processUpdate(type);
}
Expand All @@ -54,7 +54,6 @@ public RepositoryDTO create(final RepositoryDTOWithoutId dto) {
repository.name = dto.name();
repository.packageManager = dto.packageManager();
repository.settings = dto.settings();
repository.metadata = dto.metadata();
repository.cleanupPolicies = dto.cleanupPolicies();

repository.persist();
Expand All @@ -72,7 +71,6 @@ public RepositoryDTO update(final RepositoryDTO dto) {

repository.name = dto.name();
repository.settings = dto.settings();
repository.metadata = dto.metadata();
repository.cleanupPolicies = dto.cleanupPolicies();
repository.persist();

Expand Down
Loading
Loading