From 9f95426f01efc330f43be09a896273d153b97309 Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Fri, 4 Oct 2024 15:52:54 -0400 Subject: [PATCH 01/12] Implemented basic support for DRAFT (mutable) artifact versions --- .../rest/v7/impl/AbstractResource.java | 4 +- .../limits/RegistryStorageLimitsEnforcer.java | 9 +- .../registry/rest/v2/GroupsResourceImpl.java | 6 +- .../registry/rest/v3/GroupsResourceImpl.java | 138 +++++++++++---- .../registry/storage/RegistryStorage.java | 22 ++- .../ReadOnlyRegistryStorageDecorator.java | 16 +- .../RegistryStorageDecoratorBase.java | 15 +- .../AbstractReadOnlyRegistryStorage.java | 11 +- .../kafkasql/KafkaSqlRegistryStorage.java | 33 ++-- .../messages/CreateArtifact10Message.java | 55 ++++++ .../messages/CreateArtifact9Message.java | 3 +- .../CreateArtifactVersion8Message.java | 3 +- .../CreateArtifactVersion9Message.java | 53 ++++++ .../UpdateArtifactVersionContent5Message.java | 48 ++++++ .../kafkasql/serde/KafkaSqlMessageIndex.java | 5 +- .../impl/sql/AbstractSqlRegistryStorage.java | 84 ++++++++- .../storage/impl/sql/CommonSqlStatements.java | 5 + .../storage/impl/sql/SqlStatements.java | 6 + .../noprofile/rest/v3/DraftContentTest.java | 159 ++++++++++++++++++ .../RegistryStoragePerformanceTest.java | 2 +- .../storage/RegistryStorageSmokeTest.java | 14 +- .../readonly/ReadOnlyRegistryStorageTest.java | 12 +- 22 files changed, 618 insertions(+), 85 deletions(-) create mode 100644 app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifact10Message.java create mode 100644 app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifactVersion9Message.java create mode 100644 app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/UpdateArtifactVersionContent5Message.java create mode 100644 app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java index 25254bd8eb..c384002036 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java @@ -127,7 +127,7 @@ protected ArtifactVersionMetaDataDto createOrUpdateArtifact(String artifactId, S .contentType(contentType).references(parsedReferences).build(); res = storage.createArtifact(groupId, artifactId, artifactType, artifactMetaData, null, - firstVersionContent, firstVersionMetaData, null, false).getValue(); + firstVersionContent, firstVersionMetaData, null, false, false).getValue(); } else { TypedContent typedSchemaContent = TypedContent.create(schemaContent, contentType); rulesService.applyRules(groupId, artifactId, artifactType, typedSchemaContent, @@ -135,7 +135,7 @@ protected ArtifactVersionMetaDataDto createOrUpdateArtifact(String artifactId, S ContentWrapperDto versionContent = ContentWrapperDto.builder().content(schemaContent) .contentType(contentType).references(parsedReferences).build(); res = storage.createArtifactVersion(groupId, artifactId, null, artifactType, versionContent, - EditableVersionMetaDataDto.builder().build(), List.of(), false); + EditableVersionMetaDataDto.builder().build(), List.of(), false, false); } } catch (RuleViolationException ex) { if (ex.getRuleType() == RuleType.VALIDITY) { diff --git a/app/src/main/java/io/apicurio/registry/limits/RegistryStorageLimitsEnforcer.java b/app/src/main/java/io/apicurio/registry/limits/RegistryStorageLimitsEnforcer.java index 363a9a95a1..033eb1ec4b 100644 --- a/app/src/main/java/io/apicurio/registry/limits/RegistryStorageLimitsEnforcer.java +++ b/app/src/main/java/io/apicurio/registry/limits/RegistryStorageLimitsEnforcer.java @@ -60,11 +60,12 @@ public int order() { public Pair createArtifact(String groupId, String artifactId, String artifactType, EditableArtifactMetaDataDto artifactMetaData, String version, ContentWrapperDto versionContent, EditableVersionMetaDataDto versionMetaData, - List versionBranches, boolean dryRun) throws RegistryStorageException { + List versionBranches, boolean versionIsDraft, boolean dryRun) + throws RegistryStorageException { Pair rval = withLimitsCheck( () -> limitsService.canCreateArtifact(artifactMetaData, versionContent, versionMetaData)) .execute(() -> super.createArtifact(groupId, artifactId, artifactType, artifactMetaData, - version, versionContent, versionMetaData, versionBranches, dryRun)); + version, versionContent, versionMetaData, versionBranches, versionIsDraft, dryRun)); limitsService.artifactCreated(); return rval; } @@ -72,11 +73,11 @@ public Pair createArtifact(Stri @Override public ArtifactVersionMetaDataDto createArtifactVersion(String groupId, String artifactId, String version, String artifactType, ContentWrapperDto content, EditableVersionMetaDataDto metaData, - List branches, boolean dryRun) throws RegistryStorageException { + List branches, boolean isDraft, boolean dryRun) throws RegistryStorageException { ArtifactVersionMetaDataDto dto = withLimitsCheck( () -> limitsService.canCreateArtifactVersion(groupId, artifactId, null, content.getContent())) .execute(() -> super.createArtifactVersion(groupId, artifactId, version, artifactType, - content, metaData, branches, dryRun)); + content, metaData, branches, isDraft, dryRun)); limitsService.artifactVersionCreated(groupId, artifactId); return dto; } diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java index 4751ae1032..fd68e141f3 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java @@ -1131,7 +1131,7 @@ private ArtifactMetaData createArtifactWithRefs(String groupId, String xRegistry Pair createResult = storage.createArtifact( defaultGroupIdToNull(groupId), artifactId, artifactType, metaData, xRegistryVersion, - contentDto, versionMetaData, List.of(), false); + contentDto, versionMetaData, List.of(), false, false); return V2ApiUtil.dtoToMetaData(groupId, artifactId, artifactType, createResult.getRight()); } catch (ArtifactAlreadyExistsException ex) { @@ -1257,7 +1257,7 @@ private VersionMetaData createArtifactVersionWithRefs(String groupId, String art ContentWrapperDto contentDto = ContentWrapperDto.builder().content(content).contentType(ct) .references(referencesAsDtos).build(); ArtifactVersionMetaDataDto vmdDto = storage.createArtifactVersion(defaultGroupIdToNull(groupId), - artifactId, xRegistryVersion, artifactType, contentDto, metaData, List.of(), false); + artifactId, xRegistryVersion, artifactType, contentDto, metaData, List.of(), false, false); return V2ApiUtil.dtoToVersionMetaData(defaultGroupIdToNull(groupId), artifactId, artifactType, vmdDto); } @@ -1393,7 +1393,7 @@ private ArtifactMetaData updateArtifactInternal(String groupId, String artifactI ContentWrapperDto contentDto = ContentWrapperDto.builder().content(content).contentType(contentType) .references(referencesAsDtos).build(); ArtifactVersionMetaDataDto dto = storage.createArtifactVersion(defaultGroupIdToNull(groupId), - artifactId, version, artifactType, contentDto, metaData, List.of(), false); + artifactId, version, artifactType, contentDto, metaData, List.of(), false, false); // Note: if the version was created, we need to update the artifact metadata as well, because // those are the semantics of the v2 API. :( diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java index 203728a9cf..9a3ac2d3f8 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java @@ -14,6 +14,7 @@ import io.apicurio.registry.model.GroupId; import io.apicurio.registry.model.VersionExpressionParser; import io.apicurio.registry.model.VersionId; +import io.apicurio.registry.rest.ConflictException; import io.apicurio.registry.rest.HeadersHack; import io.apicurio.registry.rest.MissingRequiredParameterException; import io.apicurio.registry.rest.RestConfig; @@ -44,13 +45,33 @@ import io.apicurio.registry.rest.v3.beans.ReplaceBranchVersions; import io.apicurio.registry.rest.v3.beans.Rule; import io.apicurio.registry.rest.v3.beans.SortOrder; +import io.apicurio.registry.rest.v3.beans.VersionContent; import io.apicurio.registry.rest.v3.beans.VersionMetaData; import io.apicurio.registry.rest.v3.beans.VersionSearchResults; import io.apicurio.registry.rest.v3.beans.VersionSortBy; import io.apicurio.registry.rules.RuleApplicationType; import io.apicurio.registry.rules.RulesService; import io.apicurio.registry.storage.RegistryStorage.RetrievalBehavior; -import io.apicurio.registry.storage.dto.*; +import io.apicurio.registry.storage.dto.ArtifactMetaDataDto; +import io.apicurio.registry.storage.dto.ArtifactReferenceDto; +import io.apicurio.registry.storage.dto.ArtifactSearchResultsDto; +import io.apicurio.registry.storage.dto.ArtifactVersionMetaDataDto; +import io.apicurio.registry.storage.dto.BranchMetaDataDto; +import io.apicurio.registry.storage.dto.BranchSearchResultsDto; +import io.apicurio.registry.storage.dto.CommentDto; +import io.apicurio.registry.storage.dto.ContentWrapperDto; +import io.apicurio.registry.storage.dto.EditableArtifactMetaDataDto; +import io.apicurio.registry.storage.dto.EditableBranchMetaDataDto; +import io.apicurio.registry.storage.dto.EditableGroupMetaDataDto; +import io.apicurio.registry.storage.dto.EditableVersionMetaDataDto; +import io.apicurio.registry.storage.dto.GroupMetaDataDto; +import io.apicurio.registry.storage.dto.GroupSearchResultsDto; +import io.apicurio.registry.storage.dto.OrderBy; +import io.apicurio.registry.storage.dto.OrderDirection; +import io.apicurio.registry.storage.dto.RuleConfigurationDto; +import io.apicurio.registry.storage.dto.SearchFilter; +import io.apicurio.registry.storage.dto.StoredArtifactVersionDto; +import io.apicurio.registry.storage.dto.VersionSearchResultsDto; import io.apicurio.registry.storage.error.ArtifactAlreadyExistsException; import io.apicurio.registry.storage.error.ArtifactNotFoundException; import io.apicurio.registry.storage.error.GroupNotFoundException; @@ -70,6 +91,7 @@ import jakarta.inject.Inject; import jakarta.interceptor.Interceptors; import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.HEAD; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.NotAllowedException; import jakarta.ws.rs.client.Client; @@ -505,6 +527,45 @@ public Response getArtifactVersionContent(String groupId, String artifactId, Str return builder.build(); } + @Override + @Authorized(style = AuthorizedStyle.GroupAndArtifact, level = AuthorizedLevel.Write) + public void updateArtifactVersionContent(String groupId, String artifactId, String versionExpression, + VersionContent data) { + requireParameter("groupId", groupId); + requireParameter("artifactId", artifactId); + requireParameter("versionExpression", versionExpression); + + // TODO check if DRAFT content is allowed (global config property) + + // Resolve the GAV info + var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), versionExpression, + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.SKIP_DISABLED_LATEST)); + + // Ensure the artifact version is in DRAFT status + ArtifactVersionMetaDataDto vmd = storage.getArtifactVersionMetaData(gav.getRawGroupIdWithNull(), + gav.getRawArtifactId(), gav.getRawVersionId()); + if (vmd.getState() != VersionState.DRAFT) { + throw new ConflictException( + "Requested artifact version is not in DRAFT state. Update disallowed."); + } + + ContentHandle content = ContentHandle.create(data.getContent()); + if (content.bytes().length == 0) { + throw new BadRequestException(EMPTY_CONTENT_ERROR_MESSAGE); + } + + // Transform the given references into dtos + final List referencesAsDtos = toReferenceDtos(data.getReferences()); + + // Create the content wrapper dto + ContentWrapperDto contentDto = ContentWrapperDto.builder().contentType(data.getContentType()) + .content(content).references(referencesAsDtos).build(); + + // Now ask the storage to update the content + storage.updateArtifactVersionContent(gav.getRawGroupIdWithNull(), gav.getRawArtifactId(), + gav.getRawVersionId(), vmd.getArtifactType(), contentDto); + } + /** * @see io.apicurio.registry.rest.v3.GroupsResource#deleteArtifactVersion(java.lang.String, * java.lang.String, java.lang.String) @@ -770,20 +831,6 @@ public CreateArtifactResponse createArtifact(String groupId, IfArtifactExists if String artifactType = ArtifactTypeUtil.determineArtifactType(typedContent, data.getArtifactType(), factory); - // Convert references to DTOs - final List referencesAsDtos = toReferenceDtos(references); - - // Try to resolve the references - final Map resolvedReferences = RegistryContentUtils - .recursivelyResolveReferences(referencesAsDtos, storage::getContentByReference); - - // Apply any configured rules - if (content != null) { - rulesService.applyRules(new GroupId(groupId).getRawGroupIdWithNull(), artifactId, - artifactType, typedContent, RuleApplicationType.CREATE, references, - resolvedReferences); - } - // Create the artifact (with optional first version) EditableArtifactMetaDataDto artifactMetaData = EditableArtifactMetaDataDto.builder() .description(data.getDescription()).name(data.getName()).labels(data.getLabels()).build(); @@ -791,7 +838,11 @@ public CreateArtifactResponse createArtifact(String groupId, IfArtifactExists if ContentWrapperDto firstVersionContent = null; EditableVersionMetaDataDto firstVersionMetaData = null; List firstVersionBranches = null; + boolean firstVersionIsDraft = false; if (data.getFirstVersion() != null) { + // Convert references to DTOs + final List referencesAsDtos = toReferenceDtos(references); + firstVersion = data.getFirstVersion().getVersion(); firstVersionContent = ContentWrapperDto.builder().content(content).contentType(contentType) .references(referencesAsDtos).build(); @@ -800,12 +851,25 @@ public CreateArtifactResponse createArtifact(String groupId, IfArtifactExists if .name(data.getFirstVersion().getName()).labels(data.getFirstVersion().getLabels()) .build(); firstVersionBranches = data.getFirstVersion().getBranches(); + firstVersionIsDraft = data.getFirstVersion().getIsDraft() != null + && data.getFirstVersion().getIsDraft(); + + // Try to resolve the references + final Map resolvedReferences = RegistryContentUtils + .recursivelyResolveReferences(referencesAsDtos, storage::getContentByReference); + + // Apply any configured rules unless it is a DRAFT version + if (!firstVersionIsDraft) { + rulesService.applyRules(new GroupId(groupId).getRawGroupIdWithNull(), artifactId, + artifactType, typedContent, RuleApplicationType.CREATE, references, + resolvedReferences); + } } Pair storageResult = storage.createArtifact( new GroupId(groupId).getRawGroupIdWithNull(), artifactId, artifactType, artifactMetaData, firstVersion, firstVersionContent, firstVersionMetaData, firstVersionBranches, - dryRun != null && dryRun); + firstVersionIsDraft, dryRun != null && dryRun); // Now return both the artifact metadata and (if available) the version metadata CreateArtifactResponse rval = CreateArtifactResponse.builder() @@ -870,20 +934,27 @@ public VersionMetaData createArtifactVersion(String groupId, String artifactId, throw new BadRequestException(EMPTY_CONTENT_ERROR_MESSAGE); } String ct = data.getContent().getContentType(); + boolean isDraft = data.getIsDraft() != null && data.getIsDraft(); + + // TODO Only allow DRAFT content if the global opt-in config property is set/enabled // Transform the given references into dtos final List referencesAsDtos = toReferenceDtos( data.getContent().getReferences()); - // Try to resolve the new artifact references and the nested ones (if any) - final Map resolvedReferences = RegistryContentUtils - .recursivelyResolveReferences(referencesAsDtos, storage::getContentByReference); - String artifactType = lookupArtifactType(groupId, artifactId); - TypedContent typedContent = TypedContent.create(content, ct); - rulesService.applyRules(new GroupId(groupId).getRawGroupIdWithNull(), artifactId, artifactType, - typedContent, RuleApplicationType.UPDATE, data.getContent().getReferences(), - resolvedReferences); + + // Apply rules unless the version is DRAFT + if (!isDraft) { + // Try to resolve the new artifact references and the nested ones (if any) + final Map resolvedReferences = RegistryContentUtils + .recursivelyResolveReferences(referencesAsDtos, storage::getContentByReference); + + TypedContent typedContent = TypedContent.create(content, ct); + rulesService.applyRules(new GroupId(groupId).getRawGroupIdWithNull(), artifactId, artifactType, + typedContent, RuleApplicationType.UPDATE, data.getContent().getReferences(), + resolvedReferences); + } EditableVersionMetaDataDto metaDataDto = EditableVersionMetaDataDto.builder() .description(data.getDescription()).name(data.getName()).labels(data.getLabels()).build(); ContentWrapperDto contentDto = ContentWrapperDto.builder().contentType(ct).content(content) @@ -891,7 +962,7 @@ public VersionMetaData createArtifactVersion(String groupId, String artifactId, ArtifactVersionMetaDataDto vmd = storage.createArtifactVersion( new GroupId(groupId).getRawGroupIdWithNull(), artifactId, data.getVersion(), artifactType, - contentDto, metaDataDto, data.getBranches(), dryRun != null && dryRun); + contentDto, metaDataDto, data.getBranches(), isDraft, dryRun != null && dryRun); return V3ApiUtil.dtoToVersionMetaData(vmd); } @@ -1141,6 +1212,7 @@ private CreateArtifactResponse updateArtifactInternal(String groupId, String art List references = theVersion.getContent().getReferences(); String contentType = theVersion.getContent().getContentType(); ContentHandle content = ContentHandle.create(theVersion.getContent().getContent()); + boolean isDraftVersion = theVersion.getIsDraft() != null && theVersion.getIsDraft(); String artifactType = lookupArtifactType(groupId, artifactId); @@ -1148,17 +1220,21 @@ private CreateArtifactResponse updateArtifactInternal(String groupId, String art // passed references does not exist. final List referencesAsDtos = toReferenceDtos(references); - final Map resolvedReferences = RegistryContentUtils - .recursivelyResolveReferences(referencesAsDtos, storage::getContentByReference); - final TypedContent typedContent = TypedContent.create(content, contentType); - rulesService.applyRules(new GroupId(groupId).getRawGroupIdWithNull(), artifactId, artifactType, - typedContent, RuleApplicationType.UPDATE, references, resolvedReferences); + // Apply rules only if not a draft version + if (!isDraftVersion) { + final Map resolvedReferences = RegistryContentUtils + .recursivelyResolveReferences(referencesAsDtos, storage::getContentByReference); + final TypedContent typedContent = TypedContent.create(content, contentType); + rulesService.applyRules(new GroupId(groupId).getRawGroupIdWithNull(), artifactId, artifactType, + typedContent, RuleApplicationType.UPDATE, references, resolvedReferences); + } + EditableVersionMetaDataDto metaData = EditableVersionMetaDataDto.builder().name(name) .description(description).labels(labels).build(); ContentWrapperDto contentDto = ContentWrapperDto.builder().contentType(contentType).content(content) .references(referencesAsDtos).build(); ArtifactVersionMetaDataDto vmdDto = storage.createArtifactVersion(groupId, artifactId, version, - artifactType, contentDto, metaData, branches, false); + artifactType, contentDto, metaData, branches, isDraftVersion, false); VersionMetaData vmd = V3ApiUtil.dtoToVersionMetaData(vmdDto); // Need to also return the artifact metadata diff --git a/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java index 80b6b72499..29970a73a3 100644 --- a/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java @@ -88,7 +88,7 @@ public interface RegistryStorage extends DynamicConfigStorage { Pair createArtifact(String groupId, String artifactId, String artifactType, EditableArtifactMetaDataDto artifactMetaData, String version, ContentWrapperDto versionContent, EditableVersionMetaDataDto versionMetaData, - List versionBranches, boolean dryRun) + List versionBranches, boolean versionIsDraft, boolean dryRun) throws ArtifactAlreadyExistsException, RegistryStorageException; /** @@ -161,11 +161,12 @@ ContentWrapperDto getContentByHash(String contentHash) * @param content * @param metaData * @param branches + * @param isDraft * @param dryRun */ ArtifactVersionMetaDataDto createArtifactVersion(String groupId, String artifactId, String version, String artifactType, ContentWrapperDto content, EditableVersionMetaDataDto metaData, - List branches, boolean dryRun) + List branches, boolean isDraft, boolean dryRun) throws ArtifactNotFoundException, VersionAlreadyExistsException, RegistryStorageException; /** @@ -433,6 +434,23 @@ StoredArtifactVersionDto getArtifactVersionContent(long globalId) StoredArtifactVersionDto getArtifactVersionContent(String groupId, String artifactId, String version) throws ArtifactNotFoundException, VersionNotFoundException, RegistryStorageException; + /** + * Updates the content of a specific artifact version. This is only applicable for versions in the DRAFT + * status. + * + * @param groupId + * @param artifactId + * @param version + * @param artifactType + * @param content + * @throws ArtifactNotFoundException + * @throws VersionNotFoundException + * @throws RegistryStorageException + */ + void updateArtifactVersionContent(String groupId, String artifactId, String version, String artifactType, + ContentWrapperDto content) + throws ArtifactNotFoundException, VersionNotFoundException, RegistryStorageException; + /** * Deletes a single version of a given artifact. * diff --git a/app/src/main/java/io/apicurio/registry/storage/decorator/ReadOnlyRegistryStorageDecorator.java b/app/src/main/java/io/apicurio/registry/storage/decorator/ReadOnlyRegistryStorageDecorator.java index e944ffa8e2..93a3b07816 100644 --- a/app/src/main/java/io/apicurio/registry/storage/decorator/ReadOnlyRegistryStorageDecorator.java +++ b/app/src/main/java/io/apicurio/registry/storage/decorator/ReadOnlyRegistryStorageDecorator.java @@ -75,10 +75,11 @@ public boolean isReadOnly() { public Pair createArtifact(String groupId, String artifactId, String artifactType, EditableArtifactMetaDataDto artifactMetaData, String version, ContentWrapperDto versionContent, EditableVersionMetaDataDto versionMetaData, - List versionBranches, boolean dryRun) throws RegistryStorageException { + List versionBranches, boolean isVersionDraft, boolean dryRun) + throws RegistryStorageException { checkReadOnly(); return delegate.createArtifact(groupId, artifactId, artifactType, artifactMetaData, version, - versionContent, versionMetaData, versionBranches, dryRun); + versionContent, versionMetaData, versionBranches, isVersionDraft, dryRun); } @Override @@ -97,10 +98,10 @@ public void deleteArtifacts(String groupId) throws RegistryStorageException { @Override public ArtifactVersionMetaDataDto createArtifactVersion(String groupId, String artifactId, String version, String artifactType, ContentWrapperDto content, EditableVersionMetaDataDto metaData, - List branches, boolean dryRun) throws RegistryStorageException { + List branches, boolean isDraft, boolean dryRun) throws RegistryStorageException { checkReadOnly(); return delegate.createArtifactVersion(groupId, artifactId, version, artifactType, content, metaData, - branches, dryRun); + branches, isDraft, dryRun); } @Override @@ -302,6 +303,13 @@ public void updateArtifactVersionComment(String groupId, String artifactId, Stri delegate.updateArtifactVersionComment(groupId, artifactId, version, commentId, value); } + @Override + public void updateArtifactVersionContent(String groupId, String artifactId, String version, + String artifactType, ContentWrapperDto content) throws RegistryStorageException { + checkReadOnly(); + delegate.updateArtifactVersionContent(groupId, artifactId, version, artifactType, content); + } + @Override public String createDownload(DownloadContextDto context) throws RegistryStorageException { checkReadOnly(); diff --git a/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorBase.java b/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorBase.java index dad4f4bf3b..033cd8a571 100644 --- a/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorBase.java +++ b/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorBase.java @@ -41,9 +41,10 @@ protected RegistryStorageDecoratorBase() { public Pair createArtifact(String groupId, String artifactId, String artifactType, EditableArtifactMetaDataDto artifactMetaData, String version, ContentWrapperDto versionContent, EditableVersionMetaDataDto versionMetaData, - List versionBranches, boolean dryRun) throws RegistryStorageException { + List versionBranches, boolean versionIsDraft, boolean dryRun) + throws RegistryStorageException { return delegate.createArtifact(groupId, artifactId, artifactType, artifactMetaData, version, - versionContent, versionMetaData, versionBranches, dryRun); + versionContent, versionMetaData, versionBranches, versionIsDraft, dryRun); } @Override @@ -60,9 +61,15 @@ public void deleteArtifacts(String groupId) throws RegistryStorageException { @Override public ArtifactVersionMetaDataDto createArtifactVersion(String groupId, String artifactId, String version, String artifactType, ContentWrapperDto content, EditableVersionMetaDataDto metaData, - List branches, boolean dryRun) throws RegistryStorageException { + List branches, boolean isDraft, boolean dryRun) throws RegistryStorageException { return delegate.createArtifactVersion(groupId, artifactId, version, artifactType, content, metaData, - branches, dryRun); + branches, isDraft, dryRun); + } + + @Override + public void updateArtifactVersionContent(String groupId, String artifactId, String version, + String artifactType, ContentWrapperDto content) throws RegistryStorageException { + delegate.updateArtifactVersionContent(groupId, artifactId, version, artifactType, content); } @Override diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/AbstractReadOnlyRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/AbstractReadOnlyRegistryStorage.java index bdb756de1e..51b148268e 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/AbstractReadOnlyRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/AbstractReadOnlyRegistryStorage.java @@ -51,7 +51,8 @@ public boolean isReadOnly() { public Pair createArtifact(String groupId, String artifactId, String artifactType, EditableArtifactMetaDataDto artifactMetaData, String version, ContentWrapperDto versionContent, EditableVersionMetaDataDto versionMetaData, - List versionBranches, boolean dryRun) throws RegistryStorageException { + List versionBranches, boolean versionIsDraft, boolean dryRun) + throws RegistryStorageException { readOnlyViolation(); return null; } @@ -59,11 +60,17 @@ public Pair createArtifact(Stri @Override public ArtifactVersionMetaDataDto createArtifactVersion(String groupId, String artifactId, String version, String artifactType, ContentWrapperDto content, EditableVersionMetaDataDto metaData, - List branches, boolean dryRun) throws RegistryStorageException { + List branches, boolean isDraft, boolean dryRun) throws RegistryStorageException { readOnlyViolation(); return null; } + @Override + public void updateArtifactVersionContent(String groupId, String artifactId, String version, String artifactType, + ContentWrapperDto contentDto) throws RegistryStorageException { + readOnlyViolation(); + } + @Override public List deleteArtifact(String groupId, String artifactId) throws RegistryStorageException { readOnlyViolation(); diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java index 1758b37092..add5eb2f7a 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java @@ -385,13 +385,15 @@ public void deleteConfigProperty(String propertyName) { public Pair createArtifact(String groupId, String artifactId, String artifactType, EditableArtifactMetaDataDto artifactMetaData, String version, ContentWrapperDto versionContent, EditableVersionMetaDataDto versionMetaData, - List versionBranches, boolean dryRun) throws RegistryStorageException { + List versionBranches, boolean versionIsDraft, boolean dryRun) + throws RegistryStorageException { String content = versionContent != null ? versionContent.getContent().content() : null; String contentType = versionContent != null ? versionContent.getContentType() : null; List references = versionContent != null ? versionContent.getReferences() : null; - var message = new CreateArtifact9Message(groupId, artifactId, artifactType, artifactMetaData, version, - contentType, content, references, versionMetaData, versionBranches, dryRun); + var message = new CreateArtifact10Message(groupId, artifactId, artifactType, artifactMetaData, + version, contentType, content, references, versionMetaData, versionBranches, versionIsDraft, + dryRun); var uuid = ConcurrentUtil.get(submitter.submitMessage(message)); Pair createdArtifact = (Pair) coordinator @@ -433,12 +435,12 @@ public void deleteArtifacts(String groupId) throws RegistryStorageException { @Override public ArtifactVersionMetaDataDto createArtifactVersion(String groupId, String artifactId, String version, String artifactType, ContentWrapperDto contentDto, EditableVersionMetaDataDto metaData, - List branches, boolean dryRun) throws RegistryStorageException { + List branches, boolean isDraft, boolean dryRun) throws RegistryStorageException { String content = contentDto != null ? contentDto.getContent().content() : null; String contentType = contentDto != null ? contentDto.getContentType() : null; List references = contentDto != null ? contentDto.getReferences() : null; - var message = new CreateArtifactVersion8Message(groupId, artifactId, version, artifactType, - contentType, content, references, metaData, branches, dryRun); + var message = new CreateArtifactVersion9Message(groupId, artifactId, version, artifactType, + contentType, content, references, metaData, branches, isDraft, dryRun); var uuid = ConcurrentUtil.get(submitter.submitMessage(message)); ArtifactVersionMetaDataDto versionMetaDataDto = (ArtifactVersionMetaDataDto) coordinator .waitForResponse(uuid); @@ -446,6 +448,19 @@ public ArtifactVersionMetaDataDto createArtifactVersion(String groupId, String a return versionMetaDataDto; } + @Override + public void updateArtifactVersionContent(String groupId, String artifactId, String version, + String artifactType, + ContentWrapperDto contentDto) throws RegistryStorageException { + String content = contentDto != null ? contentDto.getContent().content() : null; + String contentType = contentDto != null ? contentDto.getContentType() : null; + List references = contentDto != null ? contentDto.getReferences() : null; + var message = new UpdateArtifactVersionContent5Message(groupId, artifactId, version, artifactType, contentType, + content, references); + var uuid = ConcurrentUtil.get(submitter.submitMessage(message)); + coordinator.waitForResponse(uuid); + } + /** * @see io.apicurio.registry.storage.RegistryStorage#updateArtifactMetaData(java.lang.String, * java.lang.String, io.apicurio.registry.storage.dto.EditableArtifactMetaDataDto) @@ -683,8 +698,7 @@ public void updateGroupMetaData(String groupId, EditableGroupMetaDataDto dto) { } /** - * @see io.apicurio.registry.storage.RegistryStorage#importData(io.apicurio.registry.storage.impexp.EntityInputStream, - * boolean, boolean) + * @see io.apicurio.registry.storage.RegistryStorage#importData(EntityInputStream, boolean, boolean) */ @Override public void importData(EntityInputStream entities, boolean preserveGlobalId, boolean preserveContentId) @@ -709,8 +723,7 @@ public void importData(EntityInputStream entities, boolean preserveGlobalId, boo } /** - * @see io.apicurio.registry.storage.RegistryStorage#upgradeData(io.apicurio.registry.storage.impexp.EntityInputStream, - * boolean, boolean) + * @see io.apicurio.registry.storage.RegistryStorage#upgradeData(EntityInputStream, boolean, boolean) */ @Override public void upgradeData(EntityInputStream entities, boolean preserveGlobalId, boolean preserveContentId) diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifact10Message.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifact10Message.java new file mode 100644 index 0000000000..be63137be8 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifact10Message.java @@ -0,0 +1,55 @@ +package io.apicurio.registry.storage.impl.kafkasql.messages; + +import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.storage.RegistryStorage; +import io.apicurio.registry.storage.dto.ArtifactReferenceDto; +import io.apicurio.registry.storage.dto.ContentWrapperDto; +import io.apicurio.registry.storage.dto.EditableArtifactMetaDataDto; +import io.apicurio.registry.storage.dto.EditableVersionMetaDataDto; +import io.apicurio.registry.storage.impl.kafkasql.AbstractMessage; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +@ToString +public class CreateArtifact10Message extends AbstractMessage { + + private String groupId; + private String artifactId; + private String artifactType; + private EditableArtifactMetaDataDto artifactMetaDataDto; + private String version; + private String contentType; + private String content; + private List references; + private EditableVersionMetaDataDto versionMetaData; + private List versionBranches; + private boolean versionIsDraft; + private boolean dryRun; + + /** + * @see io.apicurio.registry.storage.impl.kafkasql.KafkaSqlMessage#dispatchTo(RegistryStorage) + */ + @Override + public Object dispatchTo(RegistryStorage storage) { + ContentHandle handle = content != null ? ContentHandle.create(content) : null; + ContentWrapperDto versionContent = content != null ? ContentWrapperDto.builder() + .contentType(contentType).content(handle).references(references).build() + : null; + return storage.createArtifact(groupId, artifactId, artifactType, artifactMetaDataDto, version, + versionContent, versionMetaData, versionBranches, versionIsDraft, dryRun); + } + +} diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifact9Message.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifact9Message.java index 8f7ace8259..e385bf2e61 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifact9Message.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifact9Message.java @@ -24,6 +24,7 @@ @Setter @EqualsAndHashCode(callSuper = false) @ToString +@Deprecated public class CreateArtifact9Message extends AbstractMessage { private String groupId; @@ -48,7 +49,7 @@ public Object dispatchTo(RegistryStorage storage) { .contentType(contentType).content(handle).references(references).build() : null; return storage.createArtifact(groupId, artifactId, artifactType, artifactMetaDataDto, version, - versionContent, versionMetaData, versionBranches, dryRun); + versionContent, versionMetaData, versionBranches, false, dryRun); } } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifactVersion8Message.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifactVersion8Message.java index 8c75b07802..6b3e9ca73f 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifactVersion8Message.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifactVersion8Message.java @@ -23,6 +23,7 @@ @Setter @EqualsAndHashCode(callSuper = false) @ToString +@Deprecated public class CreateArtifactVersion8Message extends AbstractMessage { private String groupId; @@ -46,7 +47,7 @@ public Object dispatchTo(RegistryStorage storage) { .content(handle).references(references).build() : null; return storage.createArtifactVersion(groupId, artifactId, version, artifactType, contentDto, metaData, - branches, dryRun); + branches, false, dryRun); } } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifactVersion9Message.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifactVersion9Message.java new file mode 100644 index 0000000000..46bc5b48e1 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/CreateArtifactVersion9Message.java @@ -0,0 +1,53 @@ +package io.apicurio.registry.storage.impl.kafkasql.messages; + +import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.storage.RegistryStorage; +import io.apicurio.registry.storage.dto.ArtifactReferenceDto; +import io.apicurio.registry.storage.dto.ContentWrapperDto; +import io.apicurio.registry.storage.dto.EditableVersionMetaDataDto; +import io.apicurio.registry.storage.impl.kafkasql.AbstractMessage; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +@ToString +public class CreateArtifactVersion9Message extends AbstractMessage { + + private String groupId; + private String artifactId; + private String version; + private String artifactType; + private String contentType; + private String content; + private List references; + private EditableVersionMetaDataDto metaData; + private List branches; + private boolean isDraft; + private boolean dryRun; + + /** + * @see io.apicurio.registry.storage.impl.kafkasql.KafkaSqlMessage#dispatchTo(RegistryStorage) + */ + @Override + public Object dispatchTo(RegistryStorage storage) { + ContentHandle handle = content != null ? ContentHandle.create(content) : null; + ContentWrapperDto contentDto = content != null ? ContentWrapperDto.builder().contentType(contentType) + .content(handle).references(references).build() + : null; + return storage.createArtifactVersion(groupId, artifactId, version, artifactType, contentDto, metaData, + branches, isDraft, dryRun); + } + +} diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/UpdateArtifactVersionContent5Message.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/UpdateArtifactVersionContent5Message.java new file mode 100644 index 0000000000..cb9b139502 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/UpdateArtifactVersionContent5Message.java @@ -0,0 +1,48 @@ +package io.apicurio.registry.storage.impl.kafkasql.messages; + +import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.storage.RegistryStorage; +import io.apicurio.registry.storage.dto.ArtifactReferenceDto; +import io.apicurio.registry.storage.dto.ContentWrapperDto; +import io.apicurio.registry.storage.impl.kafkasql.AbstractMessage; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +@ToString +public class UpdateArtifactVersionContent5Message extends AbstractMessage { + + private String groupId; + private String artifactId; + private String version; + private String artifactType; + private String contentType; + private String content; + private List references; + + /** + * @see io.apicurio.registry.storage.impl.kafkasql.KafkaSqlMessage#dispatchTo(RegistryStorage) + */ + @Override + public Object dispatchTo(RegistryStorage storage) { + ContentHandle handle = content != null ? ContentHandle.create(content) : null; + ContentWrapperDto contentDto = content != null ? ContentWrapperDto.builder().contentType(contentType) + .content(handle).references(references).build() + : null; + storage.updateArtifactVersionContent(groupId, artifactId, version, artifactType, contentDto); + return null; + } + +} diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/serde/KafkaSqlMessageIndex.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/serde/KafkaSqlMessageIndex.java index 039800ef09..d04dc009ea 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/serde/KafkaSqlMessageIndex.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/serde/KafkaSqlMessageIndex.java @@ -3,9 +3,11 @@ import io.apicurio.registry.storage.impl.kafkasql.KafkaSqlMessage; import io.apicurio.registry.storage.impl.kafkasql.messages.AppendVersionToBranch3Message; import io.apicurio.registry.storage.impl.kafkasql.messages.ConsumeDownload1Message; +import io.apicurio.registry.storage.impl.kafkasql.messages.CreateArtifact10Message; import io.apicurio.registry.storage.impl.kafkasql.messages.CreateArtifact9Message; import io.apicurio.registry.storage.impl.kafkasql.messages.CreateArtifactRule4Message; import io.apicurio.registry.storage.impl.kafkasql.messages.CreateArtifactVersion8Message; +import io.apicurio.registry.storage.impl.kafkasql.messages.CreateArtifactVersion9Message; import io.apicurio.registry.storage.impl.kafkasql.messages.CreateArtifactVersionComment4Message; import io.apicurio.registry.storage.impl.kafkasql.messages.CreateBranch4Message; import io.apicurio.registry.storage.impl.kafkasql.messages.CreateDownload1Message; @@ -81,7 +83,8 @@ private static void indexMessageClasses(Class... mcla static { indexMessageClasses(AppendVersionToBranch3Message.class, ConsumeDownload1Message.class, - CreateArtifact9Message.class, CreateArtifactVersion8Message.class, + CreateArtifact9Message.class, CreateArtifact10Message.class, + CreateArtifactVersion8Message.class, CreateArtifactVersion9Message.class, CreateArtifactRule4Message.class, CreateGroupRule3Message.class, CreateArtifactVersionComment4Message.class, CreateBranch4Message.class, CreateDownload1Message.class, CreateGlobalRule2Message.class, CreateGroup1Message.class, diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java index cfba4fae70..60be3e19c5 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java @@ -6,7 +6,18 @@ import io.apicurio.common.apps.config.Info; import io.apicurio.common.apps.core.System; import io.apicurio.registry.content.TypedContent; -import io.apicurio.registry.events.*; +import io.apicurio.registry.events.ArtifactCreated; +import io.apicurio.registry.events.ArtifactDeleted; +import io.apicurio.registry.events.ArtifactMetadataUpdated; +import io.apicurio.registry.events.ArtifactRuleConfigured; +import io.apicurio.registry.events.ArtifactVersionCreated; +import io.apicurio.registry.events.ArtifactVersionDeleted; +import io.apicurio.registry.events.ArtifactVersionMetadataUpdated; +import io.apicurio.registry.events.GlobalRuleConfigured; +import io.apicurio.registry.events.GroupCreated; +import io.apicurio.registry.events.GroupDeleted; +import io.apicurio.registry.events.GroupMetadataUpdated; +import io.apicurio.registry.events.GroupRuleConfigured; import io.apicurio.registry.exception.UnreachableCodeException; import io.apicurio.registry.model.BranchId; import io.apicurio.registry.model.GA; @@ -20,7 +31,34 @@ import io.apicurio.registry.storage.StorageBehaviorProperties; import io.apicurio.registry.storage.StorageEvent; import io.apicurio.registry.storage.StorageEventType; -import io.apicurio.registry.storage.dto.*; +import io.apicurio.registry.storage.dto.ArtifactMetaDataDto; +import io.apicurio.registry.storage.dto.ArtifactReferenceDto; +import io.apicurio.registry.storage.dto.ArtifactSearchResultsDto; +import io.apicurio.registry.storage.dto.ArtifactVersionMetaDataDto; +import io.apicurio.registry.storage.dto.BranchMetaDataDto; +import io.apicurio.registry.storage.dto.BranchSearchResultsDto; +import io.apicurio.registry.storage.dto.CommentDto; +import io.apicurio.registry.storage.dto.ContentWrapperDto; +import io.apicurio.registry.storage.dto.DownloadContextDto; +import io.apicurio.registry.storage.dto.EditableArtifactMetaDataDto; +import io.apicurio.registry.storage.dto.EditableBranchMetaDataDto; +import io.apicurio.registry.storage.dto.EditableGroupMetaDataDto; +import io.apicurio.registry.storage.dto.EditableVersionMetaDataDto; +import io.apicurio.registry.storage.dto.GroupMetaDataDto; +import io.apicurio.registry.storage.dto.GroupSearchResultsDto; +import io.apicurio.registry.storage.dto.OrderBy; +import io.apicurio.registry.storage.dto.OrderDirection; +import io.apicurio.registry.storage.dto.OutboxEvent; +import io.apicurio.registry.storage.dto.RoleMappingDto; +import io.apicurio.registry.storage.dto.RoleMappingSearchResultsDto; +import io.apicurio.registry.storage.dto.RuleConfigurationDto; +import io.apicurio.registry.storage.dto.SearchFilter; +import io.apicurio.registry.storage.dto.SearchedArtifactDto; +import io.apicurio.registry.storage.dto.SearchedBranchDto; +import io.apicurio.registry.storage.dto.SearchedGroupDto; +import io.apicurio.registry.storage.dto.SearchedVersionDto; +import io.apicurio.registry.storage.dto.StoredArtifactVersionDto; +import io.apicurio.registry.storage.dto.VersionSearchResultsDto; import io.apicurio.registry.storage.error.ArtifactAlreadyExistsException; import io.apicurio.registry.storage.error.ArtifactNotFoundException; import io.apicurio.registry.storage.error.BranchAlreadyExistsException; @@ -74,6 +112,7 @@ import io.apicurio.registry.storage.importing.v3.SqlDataImporter; import io.apicurio.registry.types.ArtifactState; import io.apicurio.registry.types.RuleType; +import io.apicurio.registry.types.VersionState; import io.apicurio.registry.utils.DtoUtil; import io.apicurio.registry.utils.IoUtil; import io.apicurio.registry.utils.StringUtil; @@ -94,6 +133,7 @@ import jakarta.inject.Inject; import jakarta.transaction.Transactional; import jakarta.validation.ValidationException; +import jakarta.ws.rs.HEAD; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -468,7 +508,8 @@ public List getEnabledArtifactContentIds(String groupId, String artifactId public Pair createArtifact(String groupId, String artifactId, String artifactType, EditableArtifactMetaDataDto artifactMetaData, String version, ContentWrapperDto versionContent, EditableVersionMetaDataDto versionMetaData, - List versionBranches, boolean dryRun) throws RegistryStorageException { + List versionBranches, boolean versionIsDraft, boolean dryRun) + throws RegistryStorageException { log.debug("Inserting an artifact row for: {} {}", groupId, artifactId); String owner = securityIdentity.getPrincipal().getName(); @@ -533,7 +574,7 @@ public Pair createArtifact(Stri if (versionContent != null) { ArtifactVersionMetaDataDto vmdDto = createArtifactVersionRaw(handle, true, groupId, artifactId, version, versionMetaData, owner, createdOn, contentId, - versionBranches); + versionBranches, versionIsDraft); pair = ImmutablePair.of(amdDto, vmdDto); } else { @@ -554,12 +595,12 @@ public Pair createArtifact(Stri private ArtifactVersionMetaDataDto createArtifactVersionRaw(Handle handle, boolean firstVersion, String groupId, String artifactId, String version, EditableVersionMetaDataDto metaData, - String owner, Date createdOn, Long contentId, List branches) { + String owner, Date createdOn, Long contentId, List branches, boolean isDraft) { if (metaData == null) { metaData = EditableVersionMetaDataDto.builder().build(); } - ArtifactState state = ArtifactState.ENABLED; + VersionState state = isDraft ? VersionState.DRAFT : VersionState.ENABLED; String labelsStr = RegistryContentUtils.serializeLabels(metaData.getLabels()); Long globalId = nextGlobalIdRaw(handle); @@ -815,7 +856,7 @@ public void deleteArtifacts(String groupId) throws RegistryStorageException { @Override public ArtifactVersionMetaDataDto createArtifactVersion(String groupId, String artifactId, String version, String artifactType, ContentWrapperDto content, EditableVersionMetaDataDto metaData, - List branches, boolean dryRun) + List branches, boolean isDraft, boolean dryRun) throws VersionAlreadyExistsException, RegistryStorageException { log.debug("Creating new artifact version for {} {} (version {}).", groupId, artifactId, version); @@ -839,7 +880,7 @@ public ArtifactVersionMetaDataDto createArtifactVersion(String groupId, String a ArtifactVersionMetaDataDto versionDto = createArtifactVersionRaw(handle, isFirstVersion, groupId, artifactId, version, metaData == null ? EditableVersionMetaDataDto.builder().build() : metaData, owner, - createdOn, contentId, branches); + createdOn, contentId, branches, isDraft); return versionDto; }); } catch (Exception ex) { @@ -1713,6 +1754,33 @@ public StoredArtifactVersionDto getArtifactVersionContent(String groupId, String }); } + @Override + public void updateArtifactVersionContent(String groupId, String artifactId, String version, String artifactType, + ContentWrapperDto content) throws RegistryStorageException { + log.debug("Updating content for artifact version: {} {} @ {}", groupId, artifactId, version); + + // Put the new content in the DB and get the unique content ID back. + long contentId = ensureContentAndGetId(artifactType, content); + + String modifiedBy = securityIdentity.getPrincipal().getName(); + Date modifiedOn = new Date(); + + handles.withHandle(handle -> { + int rowCount = handle.createUpdate(sqlStatements.updateArtifactVersionContent()) + .bind(0, contentId) + .bind(1, modifiedBy) + .bind(2, modifiedOn) + .bind(3, normalizeGroupId(groupId)) + .bind(4, artifactId) + .bind(5, version) + .execute(); + if (rowCount == 0) { + throw new VersionNotFoundException(groupId, artifactId, version); + } + return null; + }); + } + @Override public void deleteArtifactVersion(String groupId, String artifactId, String version) throws ArtifactNotFoundException, VersionNotFoundException, RegistryStorageException { diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java index 9e37948822..e1e73a6630 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java @@ -1051,6 +1051,11 @@ public String updateVersionComment() { return "UPDATE version_comments SET cvalue = ? WHERE globalId = ? AND commentId = ? AND owner = ?"; } + @Override + public String updateArtifactVersionContent() { + return "UPDATE versions SET contentId = ?, modifiedBy = ?, modifiedOn = ? WHERE groupId = ? AND artifactId = ? AND version = ?"; + } + @Override public String selectGAVByGlobalId() { return "SELECT groupId, artifactId, version FROM versions " + "WHERE globalId = ?"; diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java index f09534174a..8de4b7e6c9 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java @@ -89,6 +89,12 @@ public interface SqlStatements { */ public String insertVersion(boolean firstVersion); + /** + * A statement used when updating artifact version content. Updates the versions + * table with a new contentId, modifiedBy, and modifiedOn. + */ + public String updateArtifactVersionContent(); + /** * A statement used to select a single row in the versions table by globalId. */ diff --git a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java new file mode 100644 index 0000000000..401ba83fe0 --- /dev/null +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java @@ -0,0 +1,159 @@ +package io.apicurio.registry.noprofile.rest.v3; + +import io.apicurio.registry.AbstractResourceTestBase; +import io.apicurio.registry.rest.client.models.CreateArtifact; +import io.apicurio.registry.rest.client.models.CreateVersion; +import io.apicurio.registry.rest.client.models.ProblemDetails; +import io.apicurio.registry.rest.client.models.VersionContent; +import io.apicurio.registry.rest.client.models.VersionMetaData; +import io.apicurio.registry.rest.client.models.VersionState; +import io.apicurio.registry.types.ArtifactType; +import io.apicurio.registry.types.ContentTypes; +import io.apicurio.registry.utils.tests.TestUtils; +import io.quarkus.test.junit.QuarkusTest; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +@QuarkusTest +public class DraftContentTest extends AbstractResourceTestBase { + + private static final String AVRO_CONTENT_V1 = """ +{ + "type" : "record", + "namespace" : "Apicurio", + "name" : "FullName", + "fields" : [ + { "name" : "FirstName" , "type" : "string" }, + { "name" : "LastName" , "type" : "string" } + ] +} +"""; + + private static final String AVRO_CONTENT_V2 = """ +{ + "type" : "record", + "namespace" : "Apicurio", + "name" : "FullName", + "fields" : [ + { "name" : "FirstName" , "type" : "string" }, + { "name" : "MiddleName" , "type" : "string" }, + { "name" : "LastName" , "type" : "string" } + ] +} +"""; + + @Test + public void testCreateDraftArtifact() throws Exception { + String content = resourceToString("openapi-empty.json"); + String groupId = TestUtils.generateGroupId(); + String artifactId = TestUtils.generateArtifactId(); + + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, content, ContentTypes.APPLICATION_JSON); + createArtifact.getFirstVersion().setIsDraft(true); + createArtifact.getFirstVersion().setVersion("1.0.0"); + + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + + VersionMetaData vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0.0").get(); + Assertions.assertNotNull(vmd); + Assertions.assertEquals(VersionState.DRAFT, vmd.getState()); + } + + @Test + public void testCreateDraftArtifactVersion() throws Exception { + String content = resourceToString("openapi-empty.json"); + String groupId = TestUtils.generateGroupId(); + String artifactId = TestUtils.generateArtifactId(); + + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, content, ContentTypes.APPLICATION_JSON); + createArtifact.getFirstVersion().setIsDraft(false); + createArtifact.getFirstVersion().setVersion("1.0.0"); + + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + + CreateVersion createVersion = TestUtils.clientCreateVersion(content, ContentTypes.APPLICATION_JSON); + createVersion.setVersion("1.0.1"); + createVersion.setIsDraft(true); + VersionMetaData vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().post(createVersion); + + Assertions.assertNotNull(vmd); + Assertions.assertEquals(VersionState.DRAFT, vmd.getState()); + + vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0.0").get(); + Assertions.assertNotNull(vmd); + Assertions.assertEquals(VersionState.ENABLED, vmd.getState()); + vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0.1").get(); + Assertions.assertNotNull(vmd); + Assertions.assertEquals(VersionState.DRAFT, vmd.getState()); + } + + @Test + public void testUpdateDraftContent() throws Exception { + String groupId = TestUtils.generateGroupId(); + String artifactId = TestUtils.generateArtifactId(); + + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.ASYNCAPI, AVRO_CONTENT_V1, ContentTypes.APPLICATION_JSON); + createArtifact.getFirstVersion().setIsDraft(true); + createArtifact.getFirstVersion().setVersion("1.0"); + + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + + VersionMetaData vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").get(); + Assertions.assertNotNull(vmd); + Assertions.assertEquals(VersionState.DRAFT, vmd.getState()); + + try (InputStream inputStream = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").content().get()) { + String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + Assertions.assertEquals(TestUtils.normalizeMultiLineString(AVRO_CONTENT_V1), TestUtils.normalizeMultiLineString(content)); + } + + VersionContent versionContent = new VersionContent(); + versionContent.setContentType(ContentTypes.APPLICATION_JSON); + versionContent.setContent(AVRO_CONTENT_V2); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").content().put(versionContent); + + try (InputStream inputStream = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").content().get()) { + String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + Assertions.assertEquals(TestUtils.normalizeMultiLineString(AVRO_CONTENT_V2), TestUtils.normalizeMultiLineString(content)); + } + } + + @Test + public void testCannotUpdateNonDraftContent() throws Exception { + String groupId = TestUtils.generateGroupId(); + String artifactId = TestUtils.generateArtifactId(); + + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.ASYNCAPI, AVRO_CONTENT_V1, ContentTypes.APPLICATION_JSON); + createArtifact.getFirstVersion().setVersion("1.0"); + + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + + VersionMetaData vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").get(); + Assertions.assertNotNull(vmd); + Assertions.assertEquals(VersionState.ENABLED, vmd.getState()); + + try (InputStream inputStream = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").content().get()) { + String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + Assertions.assertEquals(TestUtils.normalizeMultiLineString(AVRO_CONTENT_V1), TestUtils.normalizeMultiLineString(content)); + } + + VersionContent versionContent = new VersionContent(); + versionContent.setContentType(ContentTypes.APPLICATION_JSON); + versionContent.setContent(AVRO_CONTENT_V2); + ProblemDetails error = Assertions.assertThrows(ProblemDetails.class, () -> { + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").content().put(versionContent); + }); + Assertions.assertEquals("ConflictException", error.getName()); + Assertions.assertEquals("Requested artifact version is not in DRAFT state. Update disallowed.", error.getTitle()); + + try (InputStream inputStream = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").content().get()) { + String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + Assertions.assertEquals(TestUtils.normalizeMultiLineString(AVRO_CONTENT_V1), TestUtils.normalizeMultiLineString(content)); + } + } + +} diff --git a/app/src/test/java/io/apicurio/registry/noprofile/storage/RegistryStoragePerformanceTest.java b/app/src/test/java/io/apicurio/registry/noprofile/storage/RegistryStoragePerformanceTest.java index e4349ea24d..f95fef6f85 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/storage/RegistryStoragePerformanceTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/storage/RegistryStoragePerformanceTest.java @@ -74,7 +74,7 @@ public void testStoragePerformance() throws Exception { EditableVersionMetaDataDto versionMetaData = EditableVersionMetaDataDto.builder().name(title) .description(description).build(); storage.createArtifact(GROUP_ID, artifactId, ArtifactType.OPENAPI, metaData, null, versionContent, - versionMetaData, List.of(), false); + versionMetaData, List.of(), false, false); System.out.print("."); if (idx % 100 == 0) { diff --git a/app/src/test/java/io/apicurio/registry/noprofile/storage/RegistryStorageSmokeTest.java b/app/src/test/java/io/apicurio/registry/noprofile/storage/RegistryStorageSmokeTest.java index a915d3671d..591f2d008c 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/storage/RegistryStorageSmokeTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/storage/RegistryStorageSmokeTest.java @@ -92,11 +92,11 @@ public void testArtifactsAndMeta() throws Exception { EditableVersionMetaDataDto versionMetaData1 = EditableVersionMetaDataDto.builder().build(); ArtifactVersionMetaDataDto vmdDto1_1 = getStorage() .createArtifact(GROUP_ID, artifactId1, ArtifactType.JSON, artifactMetaData1, null, - versionContent1, versionMetaData1, List.of(), false) + versionContent1, versionMetaData1, List.of(), false, false) .getRight(); // Create version 2 (for artifact 1) ArtifactVersionMetaDataDto vmdDto1_2 = getStorage().createArtifactVersion(GROUP_ID, artifactId1, null, - ArtifactType.JSON, versionContent1, versionMetaData1, List.of(), false); + ArtifactType.JSON, versionContent1, versionMetaData1, List.of(), false, false); // Create artifact 2 EditableArtifactMetaDataDto artifactMetaData2 = EditableArtifactMetaDataDto.builder().build(); @@ -104,7 +104,7 @@ public void testArtifactsAndMeta() throws Exception { .content(ContentHandle.create("content2")).contentType(ContentTypes.APPLICATION_JSON).build(); EditableVersionMetaDataDto versionMetaData2 = EditableVersionMetaDataDto.builder().build(); getStorage().createArtifact(GROUP_ID, artifactId2, ArtifactType.AVRO, artifactMetaData2, null, - versionContent2, versionMetaData2, List.of(), false).getRight(); + versionContent2, versionMetaData2, List.of(), false, false).getRight(); assertEquals(size + 2, getStorage().getArtifactIds(null).size()); assertTrue(getStorage().getArtifactIds(null).contains(artifactId1)); @@ -166,7 +166,7 @@ public void testRules() throws Exception { .content(ContentHandle.create("content1")).contentType(ContentTypes.APPLICATION_JSON).build(); EditableVersionMetaDataDto versionMetaData = EditableVersionMetaDataDto.builder().build(); getStorage().createArtifact(GROUP_ID, artifactId, ArtifactType.JSON, artifactMetaData, null, - versionContent1, versionMetaData, List.of(), false).getRight(); + versionContent1, versionMetaData, List.of(), false, false).getRight(); assertEquals(0, getStorage().getArtifactRules(GROUP_ID, artifactId).size()); assertEquals(0, getStorage().getGlobalRules().size()); @@ -200,15 +200,15 @@ public void testLimitGetArtifactIds() throws Exception { .contentType(ContentTypes.APPLICATION_JSON).build(); getStorage().createArtifact(GROUP_ID, testId0, ArtifactType.JSON, null, null, content, null, - List.of(), false); + List.of(), false, false); int size = getStorage().getArtifactIds(null).size(); // Create 2 artifacts getStorage().createArtifact(GROUP_ID, testId1, ArtifactType.JSON, null, null, content, null, - List.of(), false); + List.of(), false, false); getStorage().createArtifact(GROUP_ID, testId2, ArtifactType.JSON, null, null, content, null, - List.of(), false); + List.of(), false, false); int newSize = getStorage().getArtifactIds(null).size(); int limitedSize = getStorage().getArtifactIds(1).size(); diff --git a/app/src/test/java/io/apicurio/registry/storage/impl/readonly/ReadOnlyRegistryStorageTest.java b/app/src/test/java/io/apicurio/registry/storage/impl/readonly/ReadOnlyRegistryStorageTest.java index ac40a41635..535210a6d7 100644 --- a/app/src/test/java/io/apicurio/registry/storage/impl/readonly/ReadOnlyRegistryStorageTest.java +++ b/app/src/test/java/io/apicurio/registry/storage/impl/readonly/ReadOnlyRegistryStorageTest.java @@ -45,14 +45,16 @@ public class ReadOnlyRegistryStorageTest { new State(false, s -> s.countActiveArtifactVersions(null, null))), entry("countTotalArtifactVersions0", new State(false, RegistryStorage::countTotalArtifactVersions)), - entry("createArtifact9", new State(true, - s -> s.createArtifact(null, null, null, null, null, null, null, null, false))), + entry("createArtifact10", new State(true, + s -> s.createArtifact(null, null, null, null, null, null, null, null, false, false))), entry("createArtifactRule4", new State(true, s -> s.createArtifactRule(null, null, null, null))), entry("createArtifactVersionComment4", new State(true, s -> s.createArtifactVersionComment(null, null, null, null))), - entry("createArtifactVersion8", new State(true, - s -> s.createArtifactVersion(null, null, null, null, null, null, null, false))), + entry("createArtifactVersion9", + new State(true, + s -> s.createArtifactVersion(null, null, null, null, null, null, null, false, + false))), entry("createBranch4", new State(true, s -> s.createBranch(null, null, null, null))), entry("createDownload1", new State(true, s -> s.createDownload(null))), entry("createGlobalRule2", new State(true, s -> s.createGlobalRule(null, null))), @@ -168,6 +170,8 @@ public class ReadOnlyRegistryStorageTest { new State(true, s -> s.updateArtifactRule(null, null, null, null))), entry("updateArtifactVersionComment5", new State(true, s -> s.updateArtifactVersionComment(null, null, null, null, null))), + entry("updateArtifactVersionContent5", + new State(true, s -> s.updateArtifactVersionContent(null, null, null, null, null))), entry("updateArtifactVersionMetaData4", new State(true, s -> s.updateArtifactVersionMetaData(null, null, null, null))), entry("updateBranchMetaData3", From 5a5b27b1278cea4907bdd3f48234a1a9bf5a94bd Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Mon, 7 Oct 2024 08:29:57 -0400 Subject: [PATCH 02/12] Updated REST API to support searching for versions by state --- .../registry/rest/v3/SearchResourceImpl.java | 10 +- .../RegistryStorageDecoratorBase.java | 2 +- .../AbstractReadOnlyRegistryStorage.java | 4 +- .../kafkasql/KafkaSqlRegistryStorage.java | 7 +- .../impl/sql/AbstractSqlRegistryStorage.java | 20 +-- .../storage/impl/sql/SqlStatements.java | 4 +- .../noprofile/rest/v3/DraftContentTest.java | 147 +++++++++++++----- .../src/main/resources/META-INF/openapi.json | 48 +++++- 8 files changed, 179 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/SearchResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/SearchResourceImpl.java index 4a112c787e..6e64dbe93c 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/SearchResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/SearchResourceImpl.java @@ -25,6 +25,7 @@ import io.apicurio.registry.storage.dto.VersionSearchResultsDto; import io.apicurio.registry.storage.impl.sql.RegistryStorageContentUtils; import io.apicurio.registry.types.Current; +import io.apicurio.registry.types.VersionState; import io.apicurio.registry.utils.StringUtil; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -172,6 +173,7 @@ public ArtifactSearchResults searchArtifactsByContent(Boolean canonical, String } @Override + @Authorized(style = AuthorizedStyle.None, level = AuthorizedLevel.Read) public GroupSearchResults searchGroups(BigInteger offset, BigInteger limit, SortOrder order, GroupSortBy orderby, List labels, String description, String groupId) { if (orderby == null) { @@ -226,9 +228,10 @@ public GroupSearchResults searchGroups(BigInteger offset, BigInteger limit, Sort } @Override + @Authorized(style = AuthorizedStyle.None, level = AuthorizedLevel.Read) public VersionSearchResults searchVersions(String version, BigInteger offset, BigInteger limit, SortOrder order, VersionSortBy orderby, List labels, String description, String groupId, - Long globalId, Long contentId, String artifactId, String name) { + Long globalId, Long contentId, String artifactId, String name, VersionState state) { if (orderby == null) { orderby = VersionSortBy.globalId; } @@ -253,7 +256,6 @@ public VersionSearchResults searchVersions(String version, BigInteger offset, Bi if (!StringUtil.isEmpty(version)) { filters.add(SearchFilter.ofVersion(version)); } - if (!StringUtil.isEmpty(name)) { filters.add(SearchFilter.ofName(name)); } @@ -289,6 +291,9 @@ public VersionSearchResults searchVersions(String version, BigInteger offset, Bi if (contentId != null && contentId > 0) { filters.add(SearchFilter.ofContentId(contentId)); } + if (state != null) { + filters.add(SearchFilter.ofState(state)); + } VersionSearchResultsDto results = storage.searchVersions(filters, oBy, oDir, offset.intValue(), limit.intValue()); @@ -296,6 +301,7 @@ public VersionSearchResults searchVersions(String version, BigInteger offset, Bi } @Override + @Authorized(style = AuthorizedStyle.None, level = AuthorizedLevel.Read) public VersionSearchResults searchVersionsByContent(Boolean canonical, String artifactType, BigInteger offset, BigInteger limit, SortOrder order, VersionSortBy orderby, String groupId, String artifactId, InputStream data) { diff --git a/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorBase.java b/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorBase.java index 033cd8a571..aa08d3499d 100644 --- a/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorBase.java +++ b/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorBase.java @@ -68,7 +68,7 @@ public ArtifactVersionMetaDataDto createArtifactVersion(String groupId, String a @Override public void updateArtifactVersionContent(String groupId, String artifactId, String version, - String artifactType, ContentWrapperDto content) throws RegistryStorageException { + String artifactType, ContentWrapperDto content) throws RegistryStorageException { delegate.updateArtifactVersionContent(groupId, artifactId, version, artifactType, content); } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/AbstractReadOnlyRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/AbstractReadOnlyRegistryStorage.java index 51b148268e..df60cd2fad 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/AbstractReadOnlyRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/AbstractReadOnlyRegistryStorage.java @@ -66,8 +66,8 @@ public ArtifactVersionMetaDataDto createArtifactVersion(String groupId, String a } @Override - public void updateArtifactVersionContent(String groupId, String artifactId, String version, String artifactType, - ContentWrapperDto contentDto) throws RegistryStorageException { + public void updateArtifactVersionContent(String groupId, String artifactId, String version, + String artifactType, ContentWrapperDto contentDto) throws RegistryStorageException { readOnlyViolation(); } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java index add5eb2f7a..89dcc6ad50 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java @@ -450,13 +450,12 @@ public ArtifactVersionMetaDataDto createArtifactVersion(String groupId, String a @Override public void updateArtifactVersionContent(String groupId, String artifactId, String version, - String artifactType, - ContentWrapperDto contentDto) throws RegistryStorageException { + String artifactType, ContentWrapperDto contentDto) throws RegistryStorageException { String content = contentDto != null ? contentDto.getContent().content() : null; String contentType = contentDto != null ? contentDto.getContentType() : null; List references = contentDto != null ? contentDto.getReferences() : null; - var message = new UpdateArtifactVersionContent5Message(groupId, artifactId, version, artifactType, contentType, - content, references); + var message = new UpdateArtifactVersionContent5Message(groupId, artifactId, version, artifactType, + contentType, content, references); var uuid = ConcurrentUtil.get(submitter.submitMessage(message)); coordinator.waitForResponse(uuid); } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java index 60be3e19c5..275d9f678e 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java @@ -1654,6 +1654,13 @@ public VersionSearchResultsDto searchVersions(Set filters, OrderBy }); where.append(")"); break; + case state: + op = filter.isNot() ? "!=" : "="; + where.append("v.state " + op + " ?"); + binders.add((query, idx) -> { + query.bind(idx, normalizeGroupId(filter.getStringValue())); + }); + break; default: break; } @@ -1755,8 +1762,8 @@ public StoredArtifactVersionDto getArtifactVersionContent(String groupId, String } @Override - public void updateArtifactVersionContent(String groupId, String artifactId, String version, String artifactType, - ContentWrapperDto content) throws RegistryStorageException { + public void updateArtifactVersionContent(String groupId, String artifactId, String version, + String artifactType, ContentWrapperDto content) throws RegistryStorageException { log.debug("Updating content for artifact version: {} {} @ {}", groupId, artifactId, version); // Put the new content in the DB and get the unique content ID back. @@ -1767,13 +1774,8 @@ public void updateArtifactVersionContent(String groupId, String artifactId, Stri handles.withHandle(handle -> { int rowCount = handle.createUpdate(sqlStatements.updateArtifactVersionContent()) - .bind(0, contentId) - .bind(1, modifiedBy) - .bind(2, modifiedOn) - .bind(3, normalizeGroupId(groupId)) - .bind(4, artifactId) - .bind(5, version) - .execute(); + .bind(0, contentId).bind(1, modifiedBy).bind(2, modifiedOn) + .bind(3, normalizeGroupId(groupId)).bind(4, artifactId).bind(5, version).execute(); if (rowCount == 0) { throw new VersionNotFoundException(groupId, artifactId, version); } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java index 8de4b7e6c9..bb01b4e264 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java @@ -90,8 +90,8 @@ public interface SqlStatements { public String insertVersion(boolean firstVersion); /** - * A statement used when updating artifact version content. Updates the versions - * table with a new contentId, modifiedBy, and modifiedOn. + * A statement used when updating artifact version content. Updates the versions table with a new + * contentId, modifiedBy, and modifiedOn. */ public String updateArtifactVersionContent(); diff --git a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java index 401ba83fe0..a4a945d0a5 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java @@ -6,6 +6,7 @@ import io.apicurio.registry.rest.client.models.ProblemDetails; import io.apicurio.registry.rest.client.models.VersionContent; import io.apicurio.registry.rest.client.models.VersionMetaData; +import io.apicurio.registry.rest.client.models.VersionSearchResults; import io.apicurio.registry.rest.client.models.VersionState; import io.apicurio.registry.types.ArtifactType; import io.apicurio.registry.types.ContentTypes; @@ -22,29 +23,29 @@ public class DraftContentTest extends AbstractResourceTestBase { private static final String AVRO_CONTENT_V1 = """ -{ - "type" : "record", - "namespace" : "Apicurio", - "name" : "FullName", - "fields" : [ - { "name" : "FirstName" , "type" : "string" }, - { "name" : "LastName" , "type" : "string" } - ] -} -"""; + { + "type" : "record", + "namespace" : "Apicurio", + "name" : "FullName", + "fields" : [ + { "name" : "FirstName" , "type" : "string" }, + { "name" : "LastName" , "type" : "string" } + ] + } + """; private static final String AVRO_CONTENT_V2 = """ -{ - "type" : "record", - "namespace" : "Apicurio", - "name" : "FullName", - "fields" : [ - { "name" : "FirstName" , "type" : "string" }, - { "name" : "MiddleName" , "type" : "string" }, - { "name" : "LastName" , "type" : "string" } - ] -} -"""; + { + "type" : "record", + "namespace" : "Apicurio", + "name" : "FullName", + "fields" : [ + { "name" : "FirstName" , "type" : "string" }, + { "name" : "MiddleName" , "type" : "string" }, + { "name" : "LastName" , "type" : "string" } + ] + } + """; @Test public void testCreateDraftArtifact() throws Exception { @@ -52,13 +53,15 @@ public void testCreateDraftArtifact() throws Exception { String groupId = TestUtils.generateGroupId(); String artifactId = TestUtils.generateArtifactId(); - CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, content, ContentTypes.APPLICATION_JSON); + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, + content, ContentTypes.APPLICATION_JSON); createArtifact.getFirstVersion().setIsDraft(true); createArtifact.getFirstVersion().setVersion("1.0.0"); clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); - VersionMetaData vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0.0").get(); + VersionMetaData vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId) + .versions().byVersionExpression("1.0.0").get(); Assertions.assertNotNull(vmd); Assertions.assertEquals(VersionState.DRAFT, vmd.getState()); } @@ -69,7 +72,8 @@ public void testCreateDraftArtifactVersion() throws Exception { String groupId = TestUtils.generateGroupId(); String artifactId = TestUtils.generateArtifactId(); - CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, content, ContentTypes.APPLICATION_JSON); + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, + content, ContentTypes.APPLICATION_JSON); createArtifact.getFirstVersion().setIsDraft(false); createArtifact.getFirstVersion().setVersion("1.0.0"); @@ -78,15 +82,18 @@ public void testCreateDraftArtifactVersion() throws Exception { CreateVersion createVersion = TestUtils.clientCreateVersion(content, ContentTypes.APPLICATION_JSON); createVersion.setVersion("1.0.1"); createVersion.setIsDraft(true); - VersionMetaData vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().post(createVersion); + VersionMetaData vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId) + .versions().post(createVersion); Assertions.assertNotNull(vmd); Assertions.assertEquals(VersionState.DRAFT, vmd.getState()); - vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0.0").get(); + vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() + .byVersionExpression("1.0.0").get(); Assertions.assertNotNull(vmd); Assertions.assertEquals(VersionState.ENABLED, vmd.getState()); - vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0.1").get(); + vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() + .byVersionExpression("1.0.1").get(); Assertions.assertNotNull(vmd); Assertions.assertEquals(VersionState.DRAFT, vmd.getState()); } @@ -96,29 +103,36 @@ public void testUpdateDraftContent() throws Exception { String groupId = TestUtils.generateGroupId(); String artifactId = TestUtils.generateArtifactId(); - CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.ASYNCAPI, AVRO_CONTENT_V1, ContentTypes.APPLICATION_JSON); + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.ASYNCAPI, + AVRO_CONTENT_V1, ContentTypes.APPLICATION_JSON); createArtifact.getFirstVersion().setIsDraft(true); createArtifact.getFirstVersion().setVersion("1.0"); clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); - VersionMetaData vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").get(); + VersionMetaData vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId) + .versions().byVersionExpression("1.0").get(); Assertions.assertNotNull(vmd); Assertions.assertEquals(VersionState.DRAFT, vmd.getState()); - try (InputStream inputStream = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").content().get()) { + try (InputStream inputStream = clientV3.groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).versions().byVersionExpression("1.0").content().get()) { String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8); - Assertions.assertEquals(TestUtils.normalizeMultiLineString(AVRO_CONTENT_V1), TestUtils.normalizeMultiLineString(content)); + Assertions.assertEquals(TestUtils.normalizeMultiLineString(AVRO_CONTENT_V1), + TestUtils.normalizeMultiLineString(content)); } VersionContent versionContent = new VersionContent(); versionContent.setContentType(ContentTypes.APPLICATION_JSON); versionContent.setContent(AVRO_CONTENT_V2); - clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").content().put(versionContent); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() + .byVersionExpression("1.0").content().put(versionContent); - try (InputStream inputStream = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").content().get()) { + try (InputStream inputStream = clientV3.groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).versions().byVersionExpression("1.0").content().get()) { String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8); - Assertions.assertEquals(TestUtils.normalizeMultiLineString(AVRO_CONTENT_V2), TestUtils.normalizeMultiLineString(content)); + Assertions.assertEquals(TestUtils.normalizeMultiLineString(AVRO_CONTENT_V2), + TestUtils.normalizeMultiLineString(content)); } } @@ -127,33 +141,82 @@ public void testCannotUpdateNonDraftContent() throws Exception { String groupId = TestUtils.generateGroupId(); String artifactId = TestUtils.generateArtifactId(); - CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.ASYNCAPI, AVRO_CONTENT_V1, ContentTypes.APPLICATION_JSON); + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.ASYNCAPI, + AVRO_CONTENT_V1, ContentTypes.APPLICATION_JSON); createArtifact.getFirstVersion().setVersion("1.0"); clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); - VersionMetaData vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").get(); + VersionMetaData vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId) + .versions().byVersionExpression("1.0").get(); Assertions.assertNotNull(vmd); Assertions.assertEquals(VersionState.ENABLED, vmd.getState()); - try (InputStream inputStream = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").content().get()) { + try (InputStream inputStream = clientV3.groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).versions().byVersionExpression("1.0").content().get()) { String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8); - Assertions.assertEquals(TestUtils.normalizeMultiLineString(AVRO_CONTENT_V1), TestUtils.normalizeMultiLineString(content)); + Assertions.assertEquals(TestUtils.normalizeMultiLineString(AVRO_CONTENT_V1), + TestUtils.normalizeMultiLineString(content)); } VersionContent versionContent = new VersionContent(); versionContent.setContentType(ContentTypes.APPLICATION_JSON); versionContent.setContent(AVRO_CONTENT_V2); ProblemDetails error = Assertions.assertThrows(ProblemDetails.class, () -> { - clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").content().put(versionContent); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() + .byVersionExpression("1.0").content().put(versionContent); }); Assertions.assertEquals("ConflictException", error.getName()); - Assertions.assertEquals("Requested artifact version is not in DRAFT state. Update disallowed.", error.getTitle()); + Assertions.assertEquals("Requested artifact version is not in DRAFT state. Update disallowed.", + error.getTitle()); - try (InputStream inputStream = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression("1.0").content().get()) { + try (InputStream inputStream = clientV3.groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).versions().byVersionExpression("1.0").content().get()) { String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8); - Assertions.assertEquals(TestUtils.normalizeMultiLineString(AVRO_CONTENT_V1), TestUtils.normalizeMultiLineString(content)); + Assertions.assertEquals(TestUtils.normalizeMultiLineString(AVRO_CONTENT_V1), + TestUtils.normalizeMultiLineString(content)); + } + } + + @Test + public void testSearchForDraftContent() throws Exception { + String groupId = TestUtils.generateGroupId(); + String artifactId1 = TestUtils.generateArtifactId(); + String artifactId2 = TestUtils.generateArtifactId(); + String artifactId3 = TestUtils.generateArtifactId(); + + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId1, ArtifactType.ASYNCAPI, + AVRO_CONTENT_V1, ContentTypes.APPLICATION_JSON); + createArtifact.getFirstVersion().setVersion("1.0"); + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + CreateVersion createVersion = TestUtils.clientCreateVersion(AVRO_CONTENT_V2, ContentTypes.APPLICATION_JSON); + createVersion.setVersion("1.1"); + createVersion.setIsDraft(true); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId1).versions().post(createVersion); + + createArtifact = TestUtils.clientCreateArtifact(artifactId2, ArtifactType.ASYNCAPI, + AVRO_CONTENT_V1, ContentTypes.APPLICATION_JSON); + createArtifact.getFirstVersion().setVersion("1.0"); + createArtifact.getFirstVersion().setIsDraft(true); + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + + createArtifact = TestUtils.clientCreateArtifact(artifactId3, ArtifactType.ASYNCAPI, + AVRO_CONTENT_V1, ContentTypes.APPLICATION_JSON); + createArtifact.getFirstVersion().setVersion("1.0"); + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + for (int i = 1; i <= 5; i++) { + createVersion = TestUtils.clientCreateVersion(AVRO_CONTENT_V2, ContentTypes.APPLICATION_JSON); + createVersion.setVersion("1." + i); + createVersion.setIsDraft(true); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId3).versions().post(createVersion); } + + VersionSearchResults results = clientV3.search().versions().get(config -> { + config.queryParameters.groupId = groupId; + config.queryParameters.state = VersionState.DRAFT; + }); + Assertions.assertNotNull(results); + Assertions.assertEquals(7, results.getVersions().size()); } } diff --git a/common/src/main/resources/META-INF/openapi.json b/common/src/main/resources/META-INF/openapi.json index 40c4fd1e48..68af47f456 100644 --- a/common/src/main/resources/META-INF/openapi.json +++ b/common/src/main/resources/META-INF/openapi.json @@ -2637,6 +2637,39 @@ "summary": "Get artifact version", "description": "Retrieves a single version of the artifact content. Both the `artifactId` and the\nunique `version` number must be provided. The `Content-Type` of the response depends \non the artifact type. In most cases, this is `application/json`, but for some types \nit may be different (for example, `PROTOBUF`).\n\nThis operation can fail for the following reasons:\n\n* No artifact with this `artifactId` exists (HTTP error `404`)\n* No version with this `version` exists (HTTP error `404`)\n* A server error occurred (HTTP error `500`)\n" }, + "put": { + "requestBody": { + "description": "The new artifact version content.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VersionContent" + } + } + }, + "required": true + }, + "tags": [ + "Versions" + ], + "responses": { + "204": { + "description": "The artifact version content was successfully updated." + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "operationId": "updateArtifactVersionContent", + "summary": "Update artifact version content", + "description": "Updates the content of a single version of an artifact.\n\nNOTE: the artifact must be in `DRAFT` status.\n\nBoth the `artifactId` and the unique `version` number must be provided to identify\nthe version to update.\n\nThis operation can fail for the following reasons:\n\n* No artifact with this `artifactId` exists (HTTP error `404`)\n* No version with this `version` exists (HTTP error `404`)\n* Artifact version not in `DRAFT` status (HTTP error `409`)\n* A server error occurred (HTTP error `500`)\n" + }, "parameters": [ { "name": "groupId", @@ -2872,6 +2905,14 @@ "type": "string" }, "in": "query" + }, + { + "name": "state", + "description": "Filter by version state.", + "schema": { + "$ref": "#/components/schemas/VersionState" + }, + "in": "query" } ], "responses": { @@ -4794,7 +4835,8 @@ "enum": [ "ENABLED", "DISABLED", - "DEPRECATED" + "DEPRECATED", + "DRAFT" ], "type": "string", "x-codegen-package": "io.apicurio.registry.types" @@ -4885,6 +4927,10 @@ "items": { "type": "string" } + }, + "isDraft": { + "description": "", + "type": "boolean" } } }, From 0e7a4c0db15187625cd044f3f85cd4a8103f2aa6 Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Mon, 7 Oct 2024 08:41:12 -0400 Subject: [PATCH 03/12] Formatting --- .../noprofile/rest/v3/DraftContentTest.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java index a4a945d0a5..dd154f69e9 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java @@ -189,26 +189,29 @@ public void testSearchForDraftContent() throws Exception { AVRO_CONTENT_V1, ContentTypes.APPLICATION_JSON); createArtifact.getFirstVersion().setVersion("1.0"); clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); - CreateVersion createVersion = TestUtils.clientCreateVersion(AVRO_CONTENT_V2, ContentTypes.APPLICATION_JSON); + CreateVersion createVersion = TestUtils.clientCreateVersion(AVRO_CONTENT_V2, + ContentTypes.APPLICATION_JSON); createVersion.setVersion("1.1"); createVersion.setIsDraft(true); - clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId1).versions().post(createVersion); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId1).versions() + .post(createVersion); - createArtifact = TestUtils.clientCreateArtifact(artifactId2, ArtifactType.ASYNCAPI, - AVRO_CONTENT_V1, ContentTypes.APPLICATION_JSON); + createArtifact = TestUtils.clientCreateArtifact(artifactId2, ArtifactType.ASYNCAPI, AVRO_CONTENT_V1, + ContentTypes.APPLICATION_JSON); createArtifact.getFirstVersion().setVersion("1.0"); createArtifact.getFirstVersion().setIsDraft(true); clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); - createArtifact = TestUtils.clientCreateArtifact(artifactId3, ArtifactType.ASYNCAPI, - AVRO_CONTENT_V1, ContentTypes.APPLICATION_JSON); + createArtifact = TestUtils.clientCreateArtifact(artifactId3, ArtifactType.ASYNCAPI, AVRO_CONTENT_V1, + ContentTypes.APPLICATION_JSON); createArtifact.getFirstVersion().setVersion("1.0"); clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); for (int i = 1; i <= 5; i++) { createVersion = TestUtils.clientCreateVersion(AVRO_CONTENT_V2, ContentTypes.APPLICATION_JSON); createVersion.setVersion("1." + i); createVersion.setIsDraft(true); - clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId3).versions().post(createVersion); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId3).versions() + .post(createVersion); } VersionSearchResults results = clientV3.search().versions().get(config -> { From e31d4f5df9a5d53f90e200413861acab7549ceec Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Mon, 7 Oct 2024 08:54:36 -0400 Subject: [PATCH 04/12] Regenerate go-sdk and fix generate.sh to put kiota_tmp into ./target --- go-sdk/generate.sh | 14 +++--- ...m_versions_item_content_request_builder.go | 45 +++++++++++++++++++ go-sdk/pkg/registryclient-v3/kiota-lock.json | 2 +- .../models/create_version.go | 31 +++++++++++++ .../registryclient-v3/models/version_state.go | 5 ++- .../search/versions_request_builder.go | 7 ++- 6 files changed, 94 insertions(+), 10 deletions(-) diff --git a/go-sdk/generate.sh b/go-sdk/generate.sh index 9154ae945e..7101e008d2 100755 --- a/go-sdk/generate.sh +++ b/go-sdk/generate.sh @@ -17,17 +17,17 @@ URL="https://github.com/microsoft/kiota/releases/download/v${VERSION}/${PACKAGE_ # if ! command -v $COMMAND &> /dev/null # then # echo "System wide kiota could not be found, using local version" - if [[ ! -f $SCRIPT_DIR/kiota_tmp/kiota ]] + if [[ ! -f $SCRIPT_DIR/target/kiota_tmp/kiota ]] then echo "Local kiota could not be found, downloading" - rm -rf $SCRIPT_DIR/kiota_tmp - mkdir -p $SCRIPT_DIR/kiota_tmp - curl -sL $URL > $SCRIPT_DIR/kiota_tmp/kiota.zip - unzip $SCRIPT_DIR/kiota_tmp/kiota.zip -d $SCRIPT_DIR/kiota_tmp + rm -rf $SCRIPT_DIR/target/kiota_tmp + mkdir -p $SCRIPT_DIR/target/kiota_tmp + curl -sL $URL > $SCRIPT_DIR/target/kiota_tmp/kiota.zip + unzip $SCRIPT_DIR/target/kiota_tmp/kiota.zip -d $SCRIPT_DIR/target/kiota_tmp - chmod a+x $SCRIPT_DIR/kiota_tmp/kiota + chmod a+x $SCRIPT_DIR/target/kiota_tmp/kiota fi - COMMAND="$SCRIPT_DIR/kiota_tmp/kiota" + COMMAND="$SCRIPT_DIR/target/kiota_tmp/kiota" # fi rm -rf $SCRIPT_DIR/pkg/registryclient-v2 diff --git a/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_item_content_request_builder.go b/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_item_content_request_builder.go index 2ff6133815..2d0c8ff0d0 100644 --- a/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_item_content_request_builder.go +++ b/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_item_content_request_builder.go @@ -30,6 +30,14 @@ type ItemArtifactsItemVersionsItemContentRequestBuilderGetRequestConfiguration s QueryParameters *ItemArtifactsItemVersionsItemContentRequestBuilderGetQueryParameters } +// ItemArtifactsItemVersionsItemContentRequestBuilderPutRequestConfiguration configuration for the request such as headers, query parameters, and middleware options. +type ItemArtifactsItemVersionsItemContentRequestBuilderPutRequestConfiguration struct { + // Request headers + Headers *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestHeaders + // Request options + Options []i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestOption +} + // NewItemArtifactsItemVersionsItemContentRequestBuilderInternal instantiates a new ItemArtifactsItemVersionsItemContentRequestBuilder and sets the default values. func NewItemArtifactsItemVersionsItemContentRequestBuilderInternal(pathParameters map[string]string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *ItemArtifactsItemVersionsItemContentRequestBuilder { m := &ItemArtifactsItemVersionsItemContentRequestBuilder{ @@ -70,6 +78,27 @@ func (m *ItemArtifactsItemVersionsItemContentRequestBuilder) Get(ctx context.Con return res.([]byte), nil } +// Put updates the content of a single version of an artifact.NOTE: the artifact must be in `DRAFT` status.Both the `artifactId` and the unique `version` number must be provided to identifythe version to update.This operation can fail for the following reasons:* No artifact with this `artifactId` exists (HTTP error `404`)* No version with this `version` exists (HTTP error `404`)* Artifact version not in `DRAFT` status (HTTP error `409`)* A server error occurred (HTTP error `500`) +// returns a ProblemDetails error when the service returns a 404 status code +// returns a ProblemDetails error when the service returns a 409 status code +// returns a ProblemDetails error when the service returns a 500 status code +func (m *ItemArtifactsItemVersionsItemContentRequestBuilder) Put(ctx context.Context, body i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.VersionContentable, requestConfiguration *ItemArtifactsItemVersionsItemContentRequestBuilderPutRequestConfiguration) error { + requestInfo, err := m.ToPutRequestInformation(ctx, body, requestConfiguration) + if err != nil { + return err + } + errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "404": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, + "409": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, + "500": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, + } + err = m.BaseRequestBuilder.RequestAdapter.SendNoContent(ctx, requestInfo, errorMapping) + if err != nil { + return err + } + return nil +} + // ToGetRequestInformation retrieves a single version of the artifact content. Both the `artifactId` and theunique `version` number must be provided. The `Content-Type` of the response depends on the artifact type. In most cases, this is `application/json`, but for some types it may be different (for example, `PROTOBUF`).This operation can fail for the following reasons:* No artifact with this `artifactId` exists (HTTP error `404`)* No version with this `version` exists (HTTP error `404`)* A server error occurred (HTTP error `500`) // returns a *RequestInformation when successful func (m *ItemArtifactsItemVersionsItemContentRequestBuilder) ToGetRequestInformation(ctx context.Context, requestConfiguration *ItemArtifactsItemVersionsItemContentRequestBuilderGetRequestConfiguration) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { @@ -85,6 +114,22 @@ func (m *ItemArtifactsItemVersionsItemContentRequestBuilder) ToGetRequestInforma return requestInfo, nil } +// ToPutRequestInformation updates the content of a single version of an artifact.NOTE: the artifact must be in `DRAFT` status.Both the `artifactId` and the unique `version` number must be provided to identifythe version to update.This operation can fail for the following reasons:* No artifact with this `artifactId` exists (HTTP error `404`)* No version with this `version` exists (HTTP error `404`)* Artifact version not in `DRAFT` status (HTTP error `409`)* A server error occurred (HTTP error `500`) +// returns a *RequestInformation when successful +func (m *ItemArtifactsItemVersionsItemContentRequestBuilder) ToPutRequestInformation(ctx context.Context, body i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.VersionContentable, requestConfiguration *ItemArtifactsItemVersionsItemContentRequestBuilderPutRequestConfiguration) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { + requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.PUT, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) + if requestConfiguration != nil { + requestInfo.Headers.AddAll(requestConfiguration.Headers) + requestInfo.AddRequestOptions(requestConfiguration.Options) + } + requestInfo.Headers.TryAdd("Accept", "application/json") + err := requestInfo.SetContentFromParsable(ctx, m.BaseRequestBuilder.RequestAdapter, "application/json", body) + if err != nil { + return nil, err + } + return requestInfo, nil +} + // WithUrl returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. // returns a *ItemArtifactsItemVersionsItemContentRequestBuilder when successful func (m *ItemArtifactsItemVersionsItemContentRequestBuilder) WithUrl(rawUrl string) *ItemArtifactsItemVersionsItemContentRequestBuilder { diff --git a/go-sdk/pkg/registryclient-v3/kiota-lock.json b/go-sdk/pkg/registryclient-v3/kiota-lock.json index cc2b7bc5e4..f532f020a1 100644 --- a/go-sdk/pkg/registryclient-v3/kiota-lock.json +++ b/go-sdk/pkg/registryclient-v3/kiota-lock.json @@ -1,5 +1,5 @@ { - "descriptionHash": "63458FA9F74D289AF7723F27696C3DBA642832DEC01628597EEF51657803AE44946245B0BD2B46A22E96261A48CF659102629051B9F248F60040E7914A2A029C", + "descriptionHash": "E5ED0C5C8F0A47793ABA0A9A0C89B7EC1A9389B126163E27488DD6DD6AA0A808B4EBEA968749621300166FFCD05769D335D8088A2A4FBF0FA24B3F59CCEABF4F", "descriptionLocation": "../../v3.json", "lockFileVersion": "1.0.0", "kiotaVersion": "1.18.0", diff --git a/go-sdk/pkg/registryclient-v3/models/create_version.go b/go-sdk/pkg/registryclient-v3/models/create_version.go index a49e1f6945..d5031d4b35 100644 --- a/go-sdk/pkg/registryclient-v3/models/create_version.go +++ b/go-sdk/pkg/registryclient-v3/models/create_version.go @@ -13,6 +13,8 @@ type CreateVersion struct { content VersionContentable // The description property description *string + // The isDraft property + isDraft *bool // User-defined name-value pairs. Name and value must be strings. labels Labelsable // The name property @@ -98,6 +100,16 @@ func (m *CreateVersion) GetFieldDeserializers() map[string]func(i878a80d2330e89d } return nil } + res["isDraft"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetBoolValue() + if err != nil { + return err + } + if val != nil { + m.SetIsDraft(val) + } + return nil + } res["labels"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { val, err := n.GetObjectValue(CreateLabelsFromDiscriminatorValue) if err != nil { @@ -131,6 +143,12 @@ func (m *CreateVersion) GetFieldDeserializers() map[string]func(i878a80d2330e89d return res } +// GetIsDraft gets the isDraft property value. The isDraft property +// returns a *bool when successful +func (m *CreateVersion) GetIsDraft() *bool { + return m.isDraft +} + // GetLabels gets the labels property value. User-defined name-value pairs. Name and value must be strings. // returns a Labelsable when successful func (m *CreateVersion) GetLabels() Labelsable { @@ -169,6 +187,12 @@ func (m *CreateVersion) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0 return err } } + { + err := writer.WriteBoolValue("isDraft", m.GetIsDraft()) + if err != nil { + return err + } + } { err := writer.WriteObjectValue("labels", m.GetLabels()) if err != nil { @@ -216,6 +240,11 @@ func (m *CreateVersion) SetDescription(value *string) { m.description = value } +// SetIsDraft sets the isDraft property value. The isDraft property +func (m *CreateVersion) SetIsDraft(value *bool) { + m.isDraft = value +} + // SetLabels sets the labels property value. User-defined name-value pairs. Name and value must be strings. func (m *CreateVersion) SetLabels(value Labelsable) { m.labels = value @@ -237,12 +266,14 @@ type CreateVersionable interface { GetBranches() []string GetContent() VersionContentable GetDescription() *string + GetIsDraft() *bool GetLabels() Labelsable GetName() *string GetVersion() *string SetBranches(value []string) SetContent(value VersionContentable) SetDescription(value *string) + SetIsDraft(value *bool) SetLabels(value Labelsable) SetName(value *string) SetVersion(value *string) diff --git a/go-sdk/pkg/registryclient-v3/models/version_state.go b/go-sdk/pkg/registryclient-v3/models/version_state.go index 095b024152..55825b6996 100644 --- a/go-sdk/pkg/registryclient-v3/models/version_state.go +++ b/go-sdk/pkg/registryclient-v3/models/version_state.go @@ -7,10 +7,11 @@ const ( ENABLED_VERSIONSTATE VersionState = iota DISABLED_VERSIONSTATE DEPRECATED_VERSIONSTATE + DRAFT_VERSIONSTATE ) func (i VersionState) String() string { - return []string{"ENABLED", "DISABLED", "DEPRECATED"}[i] + return []string{"ENABLED", "DISABLED", "DEPRECATED", "DRAFT"}[i] } func ParseVersionState(v string) (any, error) { result := ENABLED_VERSIONSTATE @@ -21,6 +22,8 @@ func ParseVersionState(v string) (any, error) { result = DISABLED_VERSIONSTATE case "DEPRECATED": result = DEPRECATED_VERSIONSTATE + case "DRAFT": + result = DRAFT_VERSIONSTATE default: return nil, nil } diff --git a/go-sdk/pkg/registryclient-v3/search/versions_request_builder.go b/go-sdk/pkg/registryclient-v3/search/versions_request_builder.go index 322a6aa1ae..68386fc4db 100644 --- a/go-sdk/pkg/registryclient-v3/search/versions_request_builder.go +++ b/go-sdk/pkg/registryclient-v3/search/versions_request_builder.go @@ -41,6 +41,11 @@ type VersionsRequestBuilderGetQueryParameters struct { Orderby *string `uriparametername:"orderby"` // The field to sort by. Can be one of:* `name`* `createdOn` OrderbyAsVersionSortBy *i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.VersionSortBy `uriparametername:"orderby"` + // Filter by version state. + // Deprecated: This property is deprecated, use StateAsVersionState instead + State *string `uriparametername:"state"` + // Filter by version state. + StateAsVersionState *i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.VersionState `uriparametername:"state"` // Filter by version number. Version *string `uriparametername:"version"` } @@ -94,7 +99,7 @@ type VersionsRequestBuilderPostRequestConfiguration struct { // NewVersionsRequestBuilderInternal instantiates a new VersionsRequestBuilder and sets the default values. func NewVersionsRequestBuilderInternal(pathParameters map[string]string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *VersionsRequestBuilder { m := &VersionsRequestBuilder{ - BaseRequestBuilder: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewBaseRequestBuilder(requestAdapter, "{+baseurl}/search/versions{?artifactId*,artifactType*,canonical*,contentId*,description*,globalId*,groupId*,labels*,limit*,name*,offset*,order*,orderby*,version*}", pathParameters), + BaseRequestBuilder: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewBaseRequestBuilder(requestAdapter, "{+baseurl}/search/versions{?artifactId*,artifactType*,canonical*,contentId*,description*,globalId*,groupId*,labels*,limit*,name*,offset*,order*,orderby*,state*,version*}", pathParameters), } return m } From 7bca013d1102c294ed6916435a1a91867184cfab Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Thu, 10 Oct 2024 09:24:54 -0400 Subject: [PATCH 05/12] Added support for state changes from DRAFT to ENABLED --- .../v7/impl/SubjectVersionsResourceImpl.java | 6 +- .../rest/v7/impl/SubjectsResourceImpl.java | 7 +- .../registry/rest/v2/GroupsResourceImpl.java | 5 +- .../registry/rest/v3/GroupsResourceImpl.java | 67 ++++++++- .../apicurio/registry/rest/v3/V3ApiUtil.java | 7 + .../registry/storage/RegistryStorage.java | 23 +++ .../ReadOnlyRegistryStorageDecorator.java | 8 ++ .../RegistryStorageDecoratorBase.java | 7 + .../RegistryStorageDecoratorReadOnlyBase.java | 6 + .../dto/EditableVersionMetaDataDto.java | 2 - .../storage/dto/SearchedVersionDto.java | 2 + .../AbstractReadOnlyRegistryStorage.java | 7 + .../impl/gitops/GitOpsRegistryStorage.java | 6 + .../kafkasql/KafkaSqlRegistryStorage.java | 9 ++ .../UpdateArtifactVersionState5Message.java | 38 +++++ .../impl/sql/AbstractSqlRegistryStorage.java | 96 ++++++++++--- .../storage/impl/sql/CommonSqlStatements.java | 11 ++ .../storage/impl/sql/SqlStatements.java | 10 ++ .../sql/mappers/SearchedVersionMapper.java | 2 + .../impl/sql/mappers/VersionStateMapper.java | 27 ++++ .../registry/noprofile/VersionStateTest.java | 28 ++-- .../noprofile/rest/v3/DraftContentTest.java | 82 +++++++++++ .../noprofile/rest/v3/GroupsResourceTest.java | 48 ++----- .../registry/rbac/RegistryClientTest.java | 18 +-- .../readonly/ReadOnlyRegistryStorageTest.java | 4 + .../src/main/resources/META-INF/openapi.json | 134 +++++++++++++++++- ...tem_versions_item_state_request_builder.go | 134 ++++++++++++++++++ ...version_expression_item_request_builder.go | 6 + go-sdk/pkg/registryclient-v3/kiota-lock.json | 2 +- .../models/editable_version_meta_data.go | 32 ----- .../models/searched_version.go | 62 ++++++++ .../models/wrapped_version_state.go | 89 ++++++++++++ .../smokeTests/apicurio/ArtifactsIT.java | 22 +-- 33 files changed, 860 insertions(+), 147 deletions(-) create mode 100644 app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/UpdateArtifactVersionState5Message.java create mode 100644 app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/VersionStateMapper.java create mode 100644 go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_item_state_request_builder.go create mode 100644 go-sdk/pkg/registryclient-v3/models/wrapped_version_state.go diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java index 8fc067b11a..a106773c5d 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java @@ -20,7 +20,6 @@ import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.model.GA; import io.apicurio.registry.storage.dto.ArtifactVersionMetaDataDto; -import io.apicurio.registry.storage.dto.EditableVersionMetaDataDto; import io.apicurio.registry.storage.dto.StoredArtifactVersionDto; import io.apicurio.registry.storage.error.ArtifactNotFoundException; import io.apicurio.registry.storage.error.InvalidArtifactTypeException; @@ -190,9 +189,8 @@ private String processDeleteVersion(String artifactId, String versionString, Str if (avmd.getState().equals(VersionState.DISABLED)) { throw new SchemaSoftDeletedException("Schema is already soft deleted"); } else { - EditableVersionMetaDataDto emd = EditableVersionMetaDataDto.builder() - .state(VersionState.DISABLED).build(); - storage.updateArtifactVersionMetaData(groupId, artifactId, version, emd); + storage.updateArtifactVersionState(groupId, artifactId, version, VersionState.DISABLED, + false); } } return version; diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectsResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectsResourceImpl.java index 13c339f5c2..b42d633a23 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectsResourceImpl.java @@ -16,7 +16,6 @@ import io.apicurio.registry.model.GA; import io.apicurio.registry.storage.dto.ArtifactSearchResultsDto; import io.apicurio.registry.storage.dto.ArtifactVersionMetaDataDto; -import io.apicurio.registry.storage.dto.EditableVersionMetaDataDto; import io.apicurio.registry.storage.dto.OrderBy; import io.apicurio.registry.storage.dto.OrderDirection; import io.apicurio.registry.storage.dto.SearchFilter; @@ -135,10 +134,8 @@ private List deleteSubjectPermanent(String groupId, String artifactId) private List deleteSubjectVersions(String groupId, String artifactId) { List deletedVersions = storage.getArtifactVersions(groupId, artifactId); try { - EditableVersionMetaDataDto dto = EditableVersionMetaDataDto.builder().state(VersionState.DISABLED) - .build(); - deletedVersions.forEach( - version -> storage.updateArtifactVersionMetaData(groupId, artifactId, version, dto)); + deletedVersions.forEach(version -> storage.updateArtifactVersionState(groupId, artifactId, + version, VersionState.DISABLED, false)); } catch (InvalidArtifactStateException | InvalidVersionStateException ignored) { log.warn("Invalid artifact state transition", ignored); } diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java index fd68e141f3..99e856baae 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java @@ -852,9 +852,8 @@ public void updateArtifactVersionState(String groupId, String artifactId, String requireParameter("artifactId", artifactId); requireParameter("version", version); - EditableVersionMetaDataDto emd = EditableVersionMetaDataDto.builder() - .state(VersionState.fromValue(data.getState().name())).build(); - storage.updateArtifactVersionMetaData(defaultGroupIdToNull(groupId), artifactId, version, emd); + VersionState newState = VersionState.fromValue(data.getState().name()); + storage.updateArtifactVersionState(groupId, artifactId, version, newState, false); } /** diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java index 9a3ac2d3f8..41e49eb791 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java @@ -49,6 +49,7 @@ import io.apicurio.registry.rest.v3.beans.VersionMetaData; import io.apicurio.registry.rest.v3.beans.VersionSearchResults; import io.apicurio.registry.rest.v3.beans.VersionSortBy; +import io.apicurio.registry.rest.v3.beans.WrappedVersionState; import io.apicurio.registry.rules.RuleApplicationType; import io.apicurio.registry.rules.RulesService; import io.apicurio.registry.storage.RegistryStorage.RetrievalBehavior; @@ -91,7 +92,6 @@ import jakarta.inject.Inject; import jakarta.interceptor.Interceptors; import jakarta.ws.rs.BadRequestException; -import jakarta.ws.rs.HEAD; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.NotAllowedException; import jakarta.ws.rs.client.Client; @@ -629,11 +629,74 @@ public void updateArtifactVersionMetaData(String groupId, String artifactId, Str dto.setName(data.getName()); dto.setDescription(data.getDescription()); dto.setLabels(data.getLabels()); - dto.setState(data.getState()); storage.updateArtifactVersionMetaData(gav.getRawGroupIdWithNull(), gav.getRawArtifactId(), gav.getRawVersionId(), dto); } + @Override + @Authorized(style = AuthorizedStyle.GroupAndArtifact, level = AuthorizedLevel.Read) + public WrappedVersionState getArtifactVersionState(String groupId, String artifactId, + String versionExpression) { + requireParameter("groupId", groupId); + requireParameter("artifactId", artifactId); + requireParameter("version", versionExpression); + + var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), versionExpression, + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.DEFAULT)); + + VersionState state = storage.getArtifactVersionState(gav.getRawGroupIdWithNull(), + gav.getRawArtifactId(), gav.getRawVersionId()); + return WrappedVersionState.builder().state(state).build(); + } + + @Override + @Audited(extractParameters = { "0", KEY_GROUP_ID, "1", KEY_ARTIFACT_ID, "2", KEY_VERSION, "3", "dryRun" }) + @Authorized(style = AuthorizedStyle.GroupAndArtifact, level = AuthorizedLevel.Write) + public void updateArtifactVersionState(String groupId, String artifactId, String versionExpression, + Boolean dryRun, WrappedVersionState data) { + requireParameter("groupId", groupId); + requireParameter("artifactId", artifactId); + requireParameter("versionExpression", versionExpression); + requireParameter("body.state", data.getState()); + + if (data.getState() == VersionState.DRAFT) { + throw new BadRequestException("Illegal state transition: cannot transition to DRAFT state."); + } + + var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), versionExpression, + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.DEFAULT)); + + // Get current state. + VersionState currentState = storage.getArtifactVersionState(gav.getRawGroupIdWithNull(), + gav.getRawArtifactId(), gav.getRawVersionId()); + + // If the current state is the same as the new state, do nothing. + if (currentState == data.getState()) { + return; + } + + // If the current state is DRAFT, apply rules. + if (currentState == VersionState.DRAFT) { + VersionMetaData vmd = getArtifactVersionMetaData(gav.getRawGroupIdWithNull(), + gav.getRawArtifactId(), gav.getRawVersionId()); + StoredArtifactVersionDto artifact = storage.getArtifactVersionContent(gav.getRawGroupIdWithNull(), + gav.getRawArtifactId(), gav.getRawVersionId()); + final Map resolvedReferences = RegistryContentUtils + .recursivelyResolveReferences(artifact.getReferences(), storage::getContentByReference); + final List references = V3ApiUtil + .referenceDtosToReferences(artifact.getReferences()); + + TypedContent typedContent = TypedContent.create(artifact.getContent(), artifact.getContentType()); + rulesService.applyRules(gav.getRawGroupIdWithNull(), gav.getRawArtifactId(), + vmd.getArtifactType(), typedContent, RuleApplicationType.UPDATE, references, + resolvedReferences); + } + + // Now update the state. + storage.updateArtifactVersionState(gav.getRawGroupIdWithNull(), gav.getRawArtifactId(), + gav.getRawVersionId(), data.getState(), dryRun != null && dryRun); + } + /** * @see io.apicurio.registry.rest.v3.GroupsResource#addArtifactVersionComment(java.lang.String, * java.lang.String, java.lang.String, io.apicurio.registry.rest.v3.beans.NewComment) diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/V3ApiUtil.java b/app/src/main/java/io/apicurio/registry/rest/v3/V3ApiUtil.java index 24077374cf..34fd65ec8d 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/V3ApiUtil.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/V3ApiUtil.java @@ -38,6 +38,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.Date; +import java.util.List; import java.util.stream.Collectors; public final class V3ApiUtil { @@ -192,6 +193,8 @@ public static VersionSearchResults dtoToSearchResults(VersionSearchResultsDto dt sv.setVersion(version.getVersion()); sv.setOwner(version.getOwner()); sv.setCreatedOn(version.getCreatedOn()); + sv.setModifiedBy(version.getModifiedBy()); + sv.setModifiedOn(version.getModifiedOn()); sv.setDescription(version.getDescription()); sv.setGlobalId(version.getGlobalId()); sv.setContentId(version.getContentId()); @@ -212,6 +215,10 @@ public static ArtifactReferenceDto referenceToDto(ArtifactReference reference) { return artifactReference; } + public static List referenceDtosToReferences(List dtos) { + return dtos.stream().map(dto -> referenceDtoToReference(dto)).collect(Collectors.toList()); + } + public static ArtifactReference referenceDtoToReference(ArtifactReferenceDto reference) { final ArtifactReference artifactReference = new ArtifactReference(); artifactReference.setGroupId(reference.getGroupId()); diff --git a/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java index 29970a73a3..e3e359c25b 100644 --- a/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java @@ -19,6 +19,7 @@ import io.apicurio.registry.storage.error.VersionAlreadyExistsException; import io.apicurio.registry.storage.error.VersionNotFoundException; import io.apicurio.registry.types.RuleType; +import io.apicurio.registry.types.VersionState; import io.apicurio.registry.utils.impexp.Entity; import io.apicurio.registry.utils.impexp.EntityInputStream; import io.apicurio.registry.utils.impexp.v3.ArtifactEntity; @@ -870,6 +871,28 @@ GroupSearchResultsDto searchGroups(Set filters, OrderBy orderBy, */ List getArtifactVersionComments(String groupId, String artifactId, String version); + /** + * Returns the current state of the artifact version. + * + * @param groupId + * @param artifactId + * @param version + * @return + */ + VersionState getArtifactVersionState(String groupId, String artifactId, String version); + + /** + * Updates the state of the given artifact version. + * + * @param groupId + * @param artifactId + * @param version + * @param newState + * @param dryRun + */ + void updateArtifactVersionState(String groupId, String artifactId, String version, VersionState newState, + boolean dryRun); + /** * Updates a single comment. * diff --git a/app/src/main/java/io/apicurio/registry/storage/decorator/ReadOnlyRegistryStorageDecorator.java b/app/src/main/java/io/apicurio/registry/storage/decorator/ReadOnlyRegistryStorageDecorator.java index 93a3b07816..63f7a3146f 100644 --- a/app/src/main/java/io/apicurio/registry/storage/decorator/ReadOnlyRegistryStorageDecorator.java +++ b/app/src/main/java/io/apicurio/registry/storage/decorator/ReadOnlyRegistryStorageDecorator.java @@ -16,6 +16,7 @@ import io.apicurio.registry.storage.error.RuleAlreadyExistsException; import io.apicurio.registry.storage.error.RuleNotFoundException; import io.apicurio.registry.types.RuleType; +import io.apicurio.registry.types.VersionState; import io.apicurio.registry.utils.impexp.EntityInputStream; import io.apicurio.registry.utils.impexp.v3.ArtifactEntity; import io.apicurio.registry.utils.impexp.v3.ArtifactRuleEntity; @@ -182,6 +183,13 @@ public void updateArtifactVersionMetaData(String groupId, String artifactId, Str delegate.updateArtifactVersionMetaData(groupId, artifactId, version, metaData); } + @Override + public void updateArtifactVersionState(String groupId, String artifactId, String version, + VersionState newState, boolean dryRun) { + checkReadOnly(); + delegate.updateArtifactVersionState(groupId, artifactId, version, newState, dryRun); + } + @Override public void createGlobalRule(RuleType rule, RuleConfigurationDto config) throws RuleAlreadyExistsException, RegistryStorageException { diff --git a/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorBase.java b/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorBase.java index aa08d3499d..c7acfca589 100644 --- a/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorBase.java +++ b/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorBase.java @@ -13,6 +13,7 @@ import io.apicurio.registry.storage.error.RuleNotFoundException; import io.apicurio.registry.storage.error.VersionNotFoundException; import io.apicurio.registry.types.RuleType; +import io.apicurio.registry.types.VersionState; import io.apicurio.registry.utils.impexp.EntityInputStream; import io.apicurio.registry.utils.impexp.v3.ArtifactEntity; import io.apicurio.registry.utils.impexp.v3.ArtifactRuleEntity; @@ -126,6 +127,12 @@ public void updateArtifactVersionMetaData(String groupId, String artifactId, Str delegate.updateArtifactVersionMetaData(groupId, artifactId, version, metaData); } + @Override + public void updateArtifactVersionState(String groupId, String artifactId, String version, + VersionState newState, boolean dryRun) { + delegate.updateArtifactVersionState(groupId, artifactId, version, newState, dryRun); + } + @Override public void createGlobalRule(RuleType rule, RuleConfigurationDto config) throws RuleAlreadyExistsException, RegistryStorageException { diff --git a/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorReadOnlyBase.java b/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorReadOnlyBase.java index c28bf6d58c..7e7f074724 100644 --- a/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorReadOnlyBase.java +++ b/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorReadOnlyBase.java @@ -31,6 +31,7 @@ import io.apicurio.registry.storage.error.RuleNotFoundException; import io.apicurio.registry.storage.error.VersionNotFoundException; import io.apicurio.registry.types.RuleType; +import io.apicurio.registry.types.VersionState; import io.apicurio.registry.utils.impexp.Entity; import java.time.Instant; @@ -321,6 +322,11 @@ public List getArtifactVersionComments(String groupId, String artifa return delegate.getArtifactVersionComments(groupId, artifactId, version); } + @Override + public VersionState getArtifactVersionState(String groupId, String artifactId, String version) { + return delegate.getArtifactVersionState(groupId, artifactId, version); + } + @Override public boolean isContentExists(String contentHash) throws RegistryStorageException { return delegate.isContentExists(contentHash); diff --git a/app/src/main/java/io/apicurio/registry/storage/dto/EditableVersionMetaDataDto.java b/app/src/main/java/io/apicurio/registry/storage/dto/EditableVersionMetaDataDto.java index 290abeff41..52402453da 100644 --- a/app/src/main/java/io/apicurio/registry/storage/dto/EditableVersionMetaDataDto.java +++ b/app/src/main/java/io/apicurio/registry/storage/dto/EditableVersionMetaDataDto.java @@ -1,6 +1,5 @@ package io.apicurio.registry.storage.dto; -import io.apicurio.registry.types.VersionState; import io.quarkus.runtime.annotations.RegisterForReflection; import lombok.AllArgsConstructor; import lombok.Builder; @@ -30,6 +29,5 @@ public static EditableVersionMetaDataDto fromEditableArtifactMetaDataDto( private String name; private String description; - private VersionState state; private Map labels; } diff --git a/app/src/main/java/io/apicurio/registry/storage/dto/SearchedVersionDto.java b/app/src/main/java/io/apicurio/registry/storage/dto/SearchedVersionDto.java index 54e3c07ed7..be50a026a4 100644 --- a/app/src/main/java/io/apicurio/registry/storage/dto/SearchedVersionDto.java +++ b/app/src/main/java/io/apicurio/registry/storage/dto/SearchedVersionDto.java @@ -27,6 +27,8 @@ public class SearchedVersionDto { private String description; private Date createdOn; private String owner; + private String modifiedBy; + private Date modifiedOn; private String artifactType; private VersionState state; private long globalId; diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/AbstractReadOnlyRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/AbstractReadOnlyRegistryStorage.java index df60cd2fad..7247b144ac 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/AbstractReadOnlyRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/AbstractReadOnlyRegistryStorage.java @@ -20,6 +20,7 @@ import io.apicurio.registry.storage.dto.RuleConfigurationDto; import io.apicurio.registry.storage.error.RegistryStorageException; import io.apicurio.registry.types.RuleType; +import io.apicurio.registry.types.VersionState; import io.apicurio.registry.utils.impexp.EntityInputStream; import io.apicurio.registry.utils.impexp.v3.ArtifactEntity; import io.apicurio.registry.utils.impexp.v3.ArtifactRuleEntity; @@ -145,6 +146,12 @@ public void updateArtifactVersionMetaData(String groupId, String artifactId, Str readOnlyViolation(); } + @Override + public void updateArtifactVersionState(String groupId, String artifactId, String version, + VersionState newState, boolean dryRun) { + readOnlyViolation(); + } + @Override public void createGlobalRule(RuleType rule, RuleConfigurationDto config) throws RegistryStorageException { readOnlyViolation(); diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java index 97408b01de..0579c7dcee 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java @@ -16,6 +16,7 @@ import io.apicurio.registry.storage.impl.gitops.sql.BlueSqlStorage; import io.apicurio.registry.storage.impl.gitops.sql.GreenSqlStorage; import io.apicurio.registry.types.RuleType; +import io.apicurio.registry.types.VersionState; import io.apicurio.registry.utils.impexp.Entity; import io.quarkus.scheduler.Scheduled; import jakarta.annotation.PreDestroy; @@ -453,6 +454,11 @@ public List getArtifactVersionComments(String groupId, String artifa return proxy(storage -> storage.getArtifactVersionComments(groupId, artifactId, version)); } + @Override + public VersionState getArtifactVersionState(String groupId, String artifactId, String version) { + return proxy(storage -> storage.getArtifactVersionState(groupId, artifactId, version)); + } + @Override public DynamicConfigPropertyDto getConfigProperty(String propertyName) { return proxy(storage -> storage.getConfigProperty(propertyName)); diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java index 89dcc6ad50..6ec84c1248 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java @@ -33,6 +33,7 @@ import io.apicurio.registry.storage.importing.v2.SqlDataUpgrader; import io.apicurio.registry.storage.importing.v3.SqlDataImporter; import io.apicurio.registry.types.RuleType; +import io.apicurio.registry.types.VersionState; import io.apicurio.registry.utils.ConcurrentUtil; import io.apicurio.registry.utils.impexp.EntityInputStream; import io.apicurio.registry.utils.impexp.v3.ArtifactEntity; @@ -604,6 +605,14 @@ public void updateArtifactVersionMetaData(String groupId, String artifactId, Str .of(ArtifactVersionMetadataUpdated.of(groupId, artifactId, version, metaData))); } + @Override + public void updateArtifactVersionState(String groupId, String artifactId, String version, + VersionState newState, boolean dryRun) { + var message = new UpdateArtifactVersionState5Message(groupId, artifactId, version, newState, dryRun); + var uuid = ConcurrentUtil.get(submitter.submitMessage(message)); + coordinator.waitForResponse(uuid); + } + /** * @see io.apicurio.registry.storage.RegistryStorage#createGlobalRule(io.apicurio.registry.types.RuleType, * io.apicurio.registry.storage.dto.RuleConfigurationDto) diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/UpdateArtifactVersionState5Message.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/UpdateArtifactVersionState5Message.java new file mode 100644 index 0000000000..5aa3c632e8 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/messages/UpdateArtifactVersionState5Message.java @@ -0,0 +1,38 @@ +package io.apicurio.registry.storage.impl.kafkasql.messages; + +import io.apicurio.registry.storage.RegistryStorage; +import io.apicurio.registry.storage.impl.kafkasql.AbstractMessage; +import io.apicurio.registry.types.VersionState; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +@ToString +public class UpdateArtifactVersionState5Message extends AbstractMessage { + + private String groupId; + private String artifactId; + private String version; + private VersionState newState; + private boolean dryRun; + + /** + * @see io.apicurio.registry.storage.impl.kafkasql.KafkaSqlMessage#dispatchTo(RegistryStorage) + */ + @Override + public Object dispatchTo(RegistryStorage storage) { + storage.updateArtifactVersionState(groupId, artifactId, version, newState, dryRun); + return null; + } + +} diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java index 275d9f678e..4d107e9c5f 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java @@ -107,10 +107,10 @@ import io.apicurio.registry.storage.impl.sql.mappers.SearchedVersionMapper; import io.apicurio.registry.storage.impl.sql.mappers.StoredArtifactMapper; import io.apicurio.registry.storage.impl.sql.mappers.StringMapper; +import io.apicurio.registry.storage.impl.sql.mappers.VersionStateMapper; import io.apicurio.registry.storage.importing.DataImporter; import io.apicurio.registry.storage.importing.v2.SqlDataUpgrader; import io.apicurio.registry.storage.importing.v3.SqlDataImporter; -import io.apicurio.registry.types.ArtifactState; import io.apicurio.registry.types.RuleType; import io.apicurio.registry.types.VersionState; import io.apicurio.registry.utils.DtoUtil; @@ -133,7 +133,6 @@ import jakarta.inject.Inject; import jakarta.transaction.Transactional; import jakarta.validation.ValidationException; -import jakarta.ws.rs.HEAD; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -1183,10 +1182,11 @@ public void updateArtifactMetaData(String groupId, String artifactId, // Update description if (metaData.getDescription() != null) { + modified = true; + int rowCount = handle.createUpdate(sqlStatements.updateArtifactDescription()) .bind(0, limitStr(metaData.getDescription(), 1024)).bind(1, normalizeGroupId(groupId)) .bind(2, artifactId).execute(); - modified = true; if (rowCount == 0) { throw new ArtifactNotFoundException(groupId, artifactId); } @@ -1194,10 +1194,11 @@ public void updateArtifactMetaData(String groupId, String artifactId, // TODO versions shouldn't have owners, only groups and artifacts? if (metaData.getOwner() != null && !metaData.getOwner().trim().isEmpty()) { + modified = true; + int rowCount = handle.createUpdate(sqlStatements.updateArtifactOwner()) .bind(0, metaData.getOwner()).bind(1, normalizeGroupId(groupId)).bind(2, artifactId) .execute(); - modified = true; if (rowCount == 0) { throw new ArtifactNotFoundException(groupId, artifactId); } @@ -1205,10 +1206,11 @@ public void updateArtifactMetaData(String groupId, String artifactId, // Update labels if (metaData.getLabels() != null) { + modified = true; + int rowCount = handle.createUpdate(sqlStatements.updateArtifactLabels()) .bind(0, RegistryContentUtils.serializeLabels(metaData.getLabels())) .bind(1, normalizeGroupId(groupId)).bind(2, artifactId).execute(); - modified = true; if (rowCount == 0) { throw new ArtifactNotFoundException(groupId, artifactId); } @@ -1232,10 +1234,10 @@ public void updateArtifactMetaData(String groupId, String artifactId, if (modified) { String modifiedBy = securityIdentity.getPrincipal().getName(); Date modifiedOn = new Date(); + int rowCount = handle.createUpdate(sqlStatements.updateArtifactModifiedByOn()) .bind(0, modifiedBy).bind(1, modifiedOn).bind(2, normalizeGroupId(groupId)) .bind(3, artifactId).execute(); - modified = true; if (rowCount == 0) { throw new ArtifactNotFoundException(groupId, artifactId); } else { @@ -1847,7 +1849,11 @@ public void updateArtifactVersionMetaData(String groupId, String artifactId, Str var metadata = getArtifactVersionMetaData(groupId, artifactId, version); long globalId = metadata.getGlobalId(); handles.withHandle(handle -> { + boolean modified = false; + if (editableMetadata.getName() != null) { + modified = true; + int rowCount = handle.createUpdate(sqlStatements.updateArtifactVersionNameByGAV()) .bind(0, limitStr(editableMetadata.getName(), 512)).bind(1, normalizeGroupId(groupId)) .bind(2, artifactId).bind(3, version).execute(); @@ -1857,6 +1863,8 @@ public void updateArtifactVersionMetaData(String groupId, String artifactId, Str } if (editableMetadata.getDescription() != null) { + modified = true; + int rowCount = handle.createUpdate(sqlStatements.updateArtifactVersionDescriptionByGAV()) .bind(0, limitStr(editableMetadata.getDescription(), 1024)) .bind(1, normalizeGroupId(groupId)).bind(2, artifactId).bind(3, version).execute(); @@ -1865,19 +1873,14 @@ public void updateArtifactVersionMetaData(String groupId, String artifactId, Str } } - if (editableMetadata.getState() != null) { - int rowCount = handle.createUpdate(sqlStatements.updateArtifactVersionStateByGAV()) - .bind(0, editableMetadata.getState().name()).bind(1, normalizeGroupId(groupId)) - .bind(2, artifactId).bind(3, version).execute(); - if (rowCount == 0) { - throw new VersionNotFoundException(groupId, artifactId, version); - } - } + Map labels = editableMetadata.getLabels(); + if (labels != null) { + modified = true; - if (editableMetadata.getLabels() != null) { int rowCount = handle.createUpdate(sqlStatements.updateArtifactVersionLabelsByGAV()) .bind(0, RegistryContentUtils.serializeLabels(editableMetadata.getLabels())) - .bind(1, normalizeGroupId(groupId)).bind(2, artifactId).bind(3, version).execute(); + .bind(1, normalizeGroupId(groupId)) + .bind(2, artifactId).bind(3, version).execute(); if (rowCount == 0) { throw new VersionNotFoundException(groupId, artifactId, version); } @@ -1887,13 +1890,22 @@ public void updateArtifactVersionMetaData(String groupId, String artifactId, Str .execute(); // Insert new labels into the "version_labels" table - Map labels = editableMetadata.getLabels(); - if (labels != null && !labels.isEmpty()) { - labels.forEach((k, v) -> { - String sqli = sqlStatements.insertVersionLabel(); - handle.createUpdate(sqli).bind(0, globalId).bind(1, limitStr(k.toLowerCase(), 256)) - .bind(2, limitStr(asLowerCase(v), 512)).execute(); - }); + labels.forEach((k, v) -> { + String sqli = sqlStatements.insertVersionLabel(); + handle.createUpdate(sqli).bind(0, globalId).bind(1, limitStr(k.toLowerCase(), 256)) + .bind(2, limitStr(asLowerCase(v), 512)).execute(); + }); + + if (modified) { + String modifiedBy = securityIdentity.getPrincipal().getName(); + Date modifiedOn = new Date(); + + rowCount = handle.createUpdate(sqlStatements.updateArtifactVersionModifiedByOn()) + .bind(0, modifiedBy).bind(1, modifiedOn).bind(2, normalizeGroupId(groupId)) + .bind(3, artifactId).bind(4, version).execute(); + if (rowCount == 0) { + throw new VersionNotFoundException(groupId, artifactId, version); + } } } @@ -2000,6 +2012,44 @@ public void updateArtifactVersionComment(String groupId, String artifactId, Stri }); } + @Override + public VersionState getArtifactVersionState(String groupId, String artifactId, String version) { + return handles.withHandle(handle -> { + Optional res = handle.createQuery(sqlStatements.selectArtifactVersionState()) + .bind(0, normalizeGroupId(groupId)).bind(1, artifactId).bind(2, version) + .map(VersionStateMapper.instance).findOne(); + return res.orElseThrow(() -> new VersionNotFoundException(groupId, artifactId, version)); + }); + } + + @Override + public void updateArtifactVersionState(String groupId, String artifactId, String version, + VersionState newState, boolean dryRun) { + handles.withHandle(handle -> { + if (dryRun) { + handle.setRollback(true); + } + + int rowCount = handle.createUpdate(sqlStatements.updateArtifactVersionStateByGAV()) + .bind(0, newState.name()).bind(1, normalizeGroupId(groupId)).bind(2, artifactId) + .bind(3, version).execute(); + if (rowCount == 0) { + throw new VersionNotFoundException(groupId, artifactId, version); + } + String modifiedBy = securityIdentity.getPrincipal().getName(); + Date modifiedOn = new Date(); + + rowCount = handle.createUpdate(sqlStatements.updateArtifactVersionModifiedByOn()) + .bind(0, modifiedBy).bind(1, modifiedOn).bind(2, normalizeGroupId(groupId)) + .bind(3, artifactId).bind(4, version).execute(); + if (rowCount == 0) { + throw new VersionNotFoundException(groupId, artifactId, version); + } + + return null; + }); + } + @Override public List getGlobalRules() throws RegistryStorageException { return handles.withHandleNoException(handle -> { diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java index e1e73a6630..3cea7a5c31 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java @@ -176,6 +176,12 @@ public String selectArtifactVersionMetaData() { + "WHERE v.groupId = ? AND v.artifactId = ? AND v.version = ?"; } + @Override + public String selectArtifactVersionState() { + return "SELECT v.state FROM versions v " + + "WHERE v.groupId = ? AND v.artifactId = ? AND v.version = ?"; + } + /** * @see io.apicurio.registry.storage.impl.sql.SqlStatements#selectArtifactVersionMetaDataByContentHash() */ @@ -284,6 +290,11 @@ public String updateArtifactModifiedByOn() { return "UPDATE artifacts SET modifiedBy = ?, modifiedOn = ? WHERE groupId = ? AND artifactId = ?"; } + @Override + public String updateArtifactVersionModifiedByOn() { + return "UPDATE versions SET modifiedBy = ?, modifiedOn = ? WHERE groupId = ? AND artifactId = ? AND version = ?"; + } + /** * @see io.apicurio.registry.storage.impl.sql.SqlStatements#updateArtifactOwner() */ diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java index bb01b4e264..9b34374b32 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java @@ -226,6 +226,11 @@ public interface SqlStatements { */ public String updateArtifactModifiedByOn(); + /** + * A statement to update the modified by and modified on for an artifact version. + */ + public String updateArtifactVersionModifiedByOn(); + /** * A statement to update a single artifact owner. */ @@ -445,6 +450,11 @@ public interface SqlStatements { */ public String selectGroupByGroupId(); + /** + * A statement used to select the state of a version. + */ + public String selectArtifactVersionState(); + /* * The next few statements support globalId and contentId management. */ diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/SearchedVersionMapper.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/SearchedVersionMapper.java index f021747bce..ef97a07bed 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/SearchedVersionMapper.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/SearchedVersionMapper.java @@ -33,6 +33,8 @@ public SearchedVersionDto map(ResultSet rs) throws SQLException { dto.setState(VersionState.valueOf(rs.getString("state"))); dto.setOwner(rs.getString("owner")); dto.setCreatedOn(rs.getTimestamp("createdOn")); + dto.setModifiedBy(rs.getString("modifiedBy")); + dto.setModifiedOn(rs.getTimestamp("modifiedOn")); dto.setName(rs.getString("name")); dto.setDescription(rs.getString("description")); dto.setArtifactType(rs.getString("type")); diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/VersionStateMapper.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/VersionStateMapper.java new file mode 100644 index 0000000000..68e0d751be --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/VersionStateMapper.java @@ -0,0 +1,27 @@ +package io.apicurio.registry.storage.impl.sql.mappers; + +import io.apicurio.registry.storage.impl.sql.jdb.RowMapper; +import io.apicurio.registry.types.VersionState; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class VersionStateMapper implements RowMapper { + + public static final VersionStateMapper instance = new VersionStateMapper(); + + /** + * Constructor. + */ + private VersionStateMapper() { + } + + /** + * @see RowMapper#map(ResultSet) + */ + @Override + public VersionState map(ResultSet rs) throws SQLException { + return VersionState.fromValue(rs.getString("state")); + } + +} \ No newline at end of file diff --git a/app/src/test/java/io/apicurio/registry/noprofile/VersionStateTest.java b/app/src/test/java/io/apicurio/registry/noprofile/VersionStateTest.java index 03d13a2c5c..d89d9669c2 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/VersionStateTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/VersionStateTest.java @@ -4,6 +4,7 @@ import io.apicurio.registry.rest.client.models.EditableVersionMetaData; import io.apicurio.registry.rest.client.models.VersionMetaData; import io.apicurio.registry.rest.client.models.VersionState; +import io.apicurio.registry.rest.client.models.WrappedVersionState; import io.apicurio.registry.types.ArtifactType; import io.apicurio.registry.types.ContentTypes; import io.quarkus.test.junit.QuarkusTest; @@ -17,12 +18,6 @@ @QuarkusTest public class VersionStateTest extends AbstractResourceTestBase { - private static final EditableVersionMetaData toEditableVersionMetaData(VersionState state) { - EditableVersionMetaData evmd = new EditableVersionMetaData(); - evmd.setState(state); - return evmd; - } - @Test public void testSmoke() throws Exception { String groupId = "VersionStateTest_testSmoke"; @@ -39,9 +34,10 @@ public void testSmoke() throws Exception { // disable latest - EditableVersionMetaData evmd = toEditableVersionMetaData(VersionState.DISABLED); + WrappedVersionState vs = new WrappedVersionState(); + vs.setState(VersionState.DISABLED); clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() - .byVersionExpression(amd.getVersion()).put(evmd); + .byVersionExpression(amd.getVersion()).state().put(vs); VersionMetaData tvmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId) .versions().byVersionExpression("3").get(); @@ -77,8 +73,10 @@ public void testSmoke() throws Exception { Assertions.assertEquals(description, innerAvmd.getDescription()); } + vs = new WrappedVersionState(); + vs.setState(VersionState.DEPRECATED); clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() - .byVersionExpression("3").put(toEditableVersionMetaData(VersionState.DEPRECATED)); + .byVersionExpression("3").state().put(vs); tamd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() .byVersionExpression("branch=latest").get(); @@ -102,8 +100,10 @@ public void testSmoke() throws Exception { } // can revert back to enabled from deprecated + vs = new WrappedVersionState(); + vs.setState(VersionState.ENABLED); clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() - .byVersionExpression("3").put(toEditableVersionMetaData(VersionState.ENABLED)); + .byVersionExpression("3").state().put(vs); { VersionMetaData innerAmd = clientV3.groups().byGroupId(groupId).artifacts() @@ -115,6 +115,14 @@ public void testSmoke() throws Exception { .byArtifactId(artifactId).versions().byVersionExpression("1").get(); Assertions.assertNull(innerVmd.getDescription()); } + + // cannot change to DRAFT (not allowed) + Assertions.assertThrows(io.apicurio.registry.rest.client.models.ProblemDetails.class, () -> { + WrappedVersionState vstate = new WrappedVersionState(); + vstate.setState(VersionState.DRAFT); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() + .byVersionExpression("3").state().put(vstate); + }); } } \ No newline at end of file diff --git a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java index dd154f69e9..09f20adb27 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java @@ -2,12 +2,17 @@ import io.apicurio.registry.AbstractResourceTestBase; import io.apicurio.registry.rest.client.models.CreateArtifact; +import io.apicurio.registry.rest.client.models.CreateGroup; +import io.apicurio.registry.rest.client.models.CreateRule; import io.apicurio.registry.rest.client.models.CreateVersion; import io.apicurio.registry.rest.client.models.ProblemDetails; +import io.apicurio.registry.rest.client.models.RuleType; import io.apicurio.registry.rest.client.models.VersionContent; import io.apicurio.registry.rest.client.models.VersionMetaData; import io.apicurio.registry.rest.client.models.VersionSearchResults; import io.apicurio.registry.rest.client.models.VersionState; +import io.apicurio.registry.rest.client.models.WrappedVersionState; +import io.apicurio.registry.rules.validity.ValidityLevel; import io.apicurio.registry.types.ArtifactType; import io.apicurio.registry.types.ContentTypes; import io.apicurio.registry.utils.tests.TestUtils; @@ -47,6 +52,13 @@ public class DraftContentTest extends AbstractResourceTestBase { } """; + private static final String INVALID_AVRO_CONTENT = """ + { + "type" : "record", + "namespace" : "Apicurio", + "name" : "FullName" + """; + @Test public void testCreateDraftArtifact() throws Exception { String content = resourceToString("openapi-empty.json"); @@ -222,4 +234,74 @@ public void testSearchForDraftContent() throws Exception { Assertions.assertEquals(7, results.getVersions().size()); } + @Test + public void testCreateInvalidDraftArtifact() throws Exception { + String groupId = TestUtils.generateGroupId(); + String artifactId = TestUtils.generateArtifactId(); + + // Create group + CreateGroup createGroup = new CreateGroup(); + createGroup.setGroupId(groupId); + clientV3.groups().post(createGroup); + + // Enable validity group rule + CreateRule createRule = new CreateRule(); + createRule.setRuleType(RuleType.VALIDITY); + createRule.setConfig(ValidityLevel.FULL.name()); + clientV3.groups().byGroupId(groupId).rules().post(createRule); + + // Create artifact with first version that has invalid content + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, + INVALID_AVRO_CONTENT, ContentTypes.APPLICATION_JSON); + createArtifact.getFirstVersion().setIsDraft(true); + createArtifact.getFirstVersion().setVersion("1.0.0"); + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + + // Now try to transition from DRAFT to ENABLED - should fail + WrappedVersionState enabled = new WrappedVersionState(); + enabled.setState(VersionState.ENABLED); + ProblemDetails error = Assertions.assertThrows(ProblemDetails.class, () -> { + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() + .byVersionExpression("1.0.0").state().put(enabled); + }); + Assertions.assertEquals("RuleViolationException", error.getName()); + Assertions.assertEquals("Syntax violation for OpenAPI artifact.", error.getTitle()); + } + + @Test + public void testCreateInvalidDraftVersion() throws Exception { + String groupId = TestUtils.generateGroupId(); + String artifactId = TestUtils.generateArtifactId(); + + // Create empty artifact + CreateArtifact createArtifact = new CreateArtifact(); + createArtifact.setArtifactId(artifactId); + createArtifact.setArtifactType(ArtifactType.AVRO); + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + + // Enable the validity rule for the new artifact. + CreateRule createRule = new CreateRule(); + createRule.setRuleType(RuleType.VALIDITY); + createRule.setConfig(ValidityLevel.FULL.name()); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).rules().post(createRule); + + // Try to create a new version with invalid content (should work if state is DRAFT). + CreateVersion createVersion = TestUtils.clientCreateVersion(INVALID_AVRO_CONTENT, + ContentTypes.APPLICATION_JSON); + createVersion.setVersion("1.0.0"); + createVersion.setIsDraft(true); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() + .post(createVersion); + + // Now try to transition from DRAFT to ENABLED - should fail + WrappedVersionState enabled = new WrappedVersionState(); + enabled.setState(VersionState.ENABLED); + ProblemDetails error = Assertions.assertThrows(ProblemDetails.class, () -> { + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() + .byVersionExpression("1.0.0").state().put(enabled); + }); + Assertions.assertEquals("RuleViolationException", error.getName()); + Assertions.assertEquals("Syntax violation for Avro artifact.", error.getTitle()); + } + } diff --git a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/GroupsResourceTest.java b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/GroupsResourceTest.java index a29e7cfbde..060aa1502d 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/GroupsResourceTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/GroupsResourceTest.java @@ -8,11 +8,11 @@ import io.apicurio.registry.rest.v3.beans.Comment; import io.apicurio.registry.rest.v3.beans.CreateRule; import io.apicurio.registry.rest.v3.beans.EditableArtifactMetaData; -import io.apicurio.registry.rest.v3.beans.EditableVersionMetaData; import io.apicurio.registry.rest.v3.beans.IfArtifactExists; import io.apicurio.registry.rest.v3.beans.NewComment; import io.apicurio.registry.rest.v3.beans.Rule; import io.apicurio.registry.rest.v3.beans.VersionMetaData; +import io.apicurio.registry.rest.v3.beans.WrappedVersionState; import io.apicurio.registry.rules.compatibility.jsonschema.diff.DiffType; import io.apicurio.registry.rules.integrity.IntegrityLevel; import io.apicurio.registry.storage.impl.sql.RegistryContentUtils; @@ -446,20 +446,19 @@ public void testUpdateVersionState() throws Exception { createArtifact("testUpdateVersionState", "testUpdateVersionState/EmptyAPI/1", ArtifactType.OPENAPI, oaiArtifactContent, ContentTypes.APPLICATION_JSON); - EditableVersionMetaData body = new EditableVersionMetaData(); - body.setState(VersionState.DEPRECATED); + WrappedVersionState newState = WrappedVersionState.builder().state(VersionState.DEPRECATED).build(); // Update the artifact state to DEPRECATED. given().when().contentType(CT_JSON).pathParam("groupId", "testUpdateVersionState") - .pathParam("artifactId", "testUpdateVersionState/EmptyAPI/1").body(body) - .put("/registry/v3/groups/{groupId}/artifacts/{artifactId}/versions/branch=latest").then() - .statusCode(204); + .pathParam("artifactId", "testUpdateVersionState/EmptyAPI/1").body(newState) + .put("/registry/v3/groups/{groupId}/artifacts/{artifactId}/versions/branch=latest/state") + .then().statusCode(204); // Update the artifact state to DEPRECATED again. given().when().contentType(CT_JSON).pathParam("groupId", "testUpdateVersionState") - .pathParam("artifactId", "testUpdateVersionState/EmptyAPI/1").body(body) - .put("/registry/v3/groups/{groupId}/artifacts/{artifactId}/versions/branch=latest").then() - .statusCode(204); + .pathParam("artifactId", "testUpdateVersionState/EmptyAPI/1").body(newState) + .put("/registry/v3/groups/{groupId}/artifacts/{artifactId}/versions/branch=latest/state") + .then().statusCode(204); // Send a GET request to check if the artifact state is DEPRECATED. given().when().contentType(CT_JSON).pathParam("groupId", "testUpdateVersionState") @@ -468,37 +467,6 @@ public void testUpdateVersionState() throws Exception { .then().statusCode(200).header("X-Registry-Deprecated", "true"); } - @Test - public void testUpdateArtifactVersionState() throws Exception { - String oaiArtifactContent = resourceToString("openapi-empty.json"); - createArtifact("testUpdateArtifactVersionState", "testUpdateArtifactVersionState/EmptyAPI", - ArtifactType.OPENAPI, oaiArtifactContent, ContentTypes.APPLICATION_JSON); - - EditableVersionMetaData body = new EditableVersionMetaData(); - body.setState(VersionState.DEPRECATED); - - // Update the artifact state to DEPRECATED. - given().when().contentType(CT_JSON).pathParam("groupId", "testUpdateArtifactVersionState") - .pathParam("artifactId", "testUpdateArtifactVersionState/EmptyAPI") - .pathParam("versionId", "1").body(body) - .put("/registry/v3/groups/{groupId}/artifacts/{artifactId}/versions/{versionId}").then() - .statusCode(204); - - // Update the artifact state to DEPRECATED again. - given().when().contentType(CT_JSON).pathParam("groupId", "testUpdateArtifactVersionState") - .pathParam("artifactId", "testUpdateArtifactVersionState/EmptyAPI") - .pathParam("versionId", "1").body(body) - .put("/registry/v3/groups/{groupId}/artifacts/{artifactId}/versions/{versionId}").then() - .statusCode(204); - - // Send a GET request to check if the artifact state is DEPRECATED. - given().when().contentType(CT_JSON).pathParam("groupId", "testUpdateArtifactVersionState") - .pathParam("artifactId", "testUpdateArtifactVersionState/EmptyAPI") - .pathParam("versionId", "1") - .get("/registry/v3/groups/{groupId}/artifacts/{artifactId}/versions/{versionId}/content") - .then().statusCode(200).header("X-Registry-Deprecated", "true"); - } - @Test @DisabledIfEnvironmentVariable(named = CURRENT_ENV, matches = CURRENT_ENV_MAS_REGEX) @DisabledOnOs(OS.WINDOWS) diff --git a/app/src/test/java/io/apicurio/registry/rbac/RegistryClientTest.java b/app/src/test/java/io/apicurio/registry/rbac/RegistryClientTest.java index 19831bf090..bc98428fa9 100644 --- a/app/src/test/java/io/apicurio/registry/rbac/RegistryClientTest.java +++ b/app/src/test/java/io/apicurio/registry/rbac/RegistryClientTest.java @@ -15,7 +15,6 @@ import io.apicurio.registry.rest.client.models.CreateGroup; import io.apicurio.registry.rest.client.models.CreateVersion; import io.apicurio.registry.rest.client.models.EditableArtifactMetaData; -import io.apicurio.registry.rest.client.models.EditableVersionMetaData; import io.apicurio.registry.rest.client.models.GroupMetaData; import io.apicurio.registry.rest.client.models.GroupSearchResults; import io.apicurio.registry.rest.client.models.GroupSortBy; @@ -32,6 +31,7 @@ import io.apicurio.registry.rest.client.models.VersionMetaData; import io.apicurio.registry.rest.client.models.VersionSearchResults; import io.apicurio.registry.rest.client.models.VersionState; +import io.apicurio.registry.rest.client.models.WrappedVersionState; import io.apicurio.registry.rest.v2.beans.ArtifactContent; import io.apicurio.registry.storage.impl.sql.RegistryContentUtils; import io.apicurio.registry.types.ArtifactType; @@ -630,12 +630,12 @@ void testSearchDisabledArtifacts() throws Exception { // Preparation // Put 2 of the 5 artifacts in DISABLED state - EditableVersionMetaData eamd = new EditableVersionMetaData(); - eamd.setState(VersionState.DISABLED); + WrappedVersionState newState = new WrappedVersionState(); + newState.setState(VersionState.DISABLED); clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactIds.get(0)).versions() - .byVersionExpression("1").put(eamd); + .byVersionExpression("1").state().put(newState); clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactIds.get(3)).versions() - .byVersionExpression("1").put(eamd); + .byVersionExpression("1").state().put(newState); // Execution // Check the search results still include the DISABLED artifacts @@ -696,12 +696,12 @@ void testSearchDisabledVersions() throws Exception { // Preparation // Put 2 of the 3 versions in DISABLED state - EditableVersionMetaData evmd = new EditableVersionMetaData(); - evmd.setState(VersionState.DISABLED); + WrappedVersionState newState = new WrappedVersionState(); + newState.setState(VersionState.DISABLED); clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() - .byVersionExpression("1").put(evmd); + .byVersionExpression("1").state().put(newState); clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() - .byVersionExpression("3").put(evmd); + .byVersionExpression("3").state().put(newState); // Execution // Check that the search results still include the DISABLED versions diff --git a/app/src/test/java/io/apicurio/registry/storage/impl/readonly/ReadOnlyRegistryStorageTest.java b/app/src/test/java/io/apicurio/registry/storage/impl/readonly/ReadOnlyRegistryStorageTest.java index 535210a6d7..0c7c014325 100644 --- a/app/src/test/java/io/apicurio/registry/storage/impl/readonly/ReadOnlyRegistryStorageTest.java +++ b/app/src/test/java/io/apicurio/registry/storage/impl/readonly/ReadOnlyRegistryStorageTest.java @@ -103,6 +103,8 @@ public class ReadOnlyRegistryStorageTest { entry("getArtifactVersions2", new State(false, s -> s.getArtifactVersions(null, null))), entry("getArtifactVersions3", new State(false, s -> s.getArtifactVersions(null, null, RegistryStorage.RetrievalBehavior.DEFAULT))), + entry("getArtifactVersionState3", + new State(false, s -> s.getArtifactVersionState(null, null, null))), entry("getEnabledArtifactContentIds2", new State(false, s -> s.getEnabledArtifactContentIds(null, null))), entry("getArtifactVersionsByContentId1", @@ -181,6 +183,8 @@ public class ReadOnlyRegistryStorageTest { entry("updateGlobalRule2", new State(true, s -> s.updateGlobalRule(null, null))), entry("updateGroupMetaData2", new State(true, s -> s.updateGroupMetaData(null, null))), entry("updateRoleMapping2", new State(true, s -> s.updateRoleMapping(null, null))), + entry("updateArtifactVersionState5", + new State(true, s -> s.updateArtifactVersionState(null, null, null, null, false))), entry("getGroupRules1", new State(false, s -> s.getGroupRules(null))), entry("getGroupRule2", new State(false, s -> s.getGroupRule(null, null))), diff --git a/common/src/main/resources/META-INF/openapi.json b/common/src/main/resources/META-INF/openapi.json index 68af47f456..6aa8da9d64 100644 --- a/common/src/main/resources/META-INF/openapi.json +++ b/common/src/main/resources/META-INF/openapi.json @@ -3390,6 +3390,110 @@ } ] }, + "/groups/{groupId}/artifacts/{artifactId}/versions/{versionExpression}/state": { + "summary": "Manage the state of an artifact version.", + "get": { + "tags": [ + "Versions" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WrappedVersionState" + } + } + }, + "description": "The current artifact version state." + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "operationId": "getArtifactVersionState", + "summary": "Get artifact version state", + "description": "Gets the current state of an artifact version.\n\nThis operation can fail for the following reasons:\n\n* No artifact with this `artifactId` exists (HTTP error `404`)\n* No version with this `version` exists (HTTP error `404`)\n* A server error occurred (HTTP error `500`)\n" + }, + "put": { + "requestBody": { + "description": "The new state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WrappedVersionState" + } + } + }, + "required": true + }, + "tags": [ + "Versions" + ], + "parameters": [ + { + "name": "dryRun", + "description": "When set to `true`, the operation will not result in any changes. Instead, it\nwill return a result based on whether the operation **would have succeeded**.", + "schema": { + "type": "boolean" + }, + "in": "query" + } + ], + "responses": { + "204": { + "description": "The state was successfully updated." + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "operationId": "updateArtifactVersionState", + "summary": "Update the artifact version state", + "description": "Updates the state of an artifact version.\n\nNOTE: There are some restrictions on state transitions. Notably a version \ncannot be transitioned to the `DRAFT` state from any other state. The `DRAFT` \nstate can only be entered (optionally) when creating a new artifact/version.\nA version in `DRAFT` state can only be transitioned to `ENABLED`. When this\nhappens, any configured content rules will be applied. This may result in a\nfailure to change the state.\n\nThis operation can fail for the following reasons:\n\n* No artifact with this `artifactId` exists (HTTP error `404`)\n* No version with this `version` exists (HTTP error `404`)\n* An invalid new state was provided (HTTP error `400`)\n* The draft content violates one or more of the rules configured for the artifact (HTTP error `409`)\n* A server error occurred (HTTP error `500`)\n" + }, + "parameters": [ + { + "name": "groupId", + "description": "The artifact group ID. Must be a string provided by the client, representing the name of the grouping of artifacts. Must follow the \".{1,512}\" pattern.", + "schema": { + "$ref": "#/components/schemas/GroupId" + }, + "in": "path", + "required": true + }, + { + "name": "artifactId", + "description": "The artifact ID. Can be a string (client-provided) or UUID (server-generated), representing the unique artifact identifier. Must follow the \".{1,512}\" pattern.", + "schema": { + "$ref": "#/components/schemas/ArtifactId" + }, + "in": "path", + "required": true + }, + { + "name": "versionExpression", + "description": "An expression resolvable to a specific version ID within the given group and artifact. The following rules apply:\n\n - If the expression is in the form \"branch={branchId}\", and artifact branch {branchId} exists: The expression is resolved to a version that the branch points to.\n - Otherwise: The expression is resolved to a version with the same ID, which must follow the \"[a-zA-Z0-9._\\\\-+]{1,256}\" pattern.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, "x-codegen-contextRoot": "/apis/registry/v3" }, "components": { @@ -3973,6 +4077,15 @@ "groupId": { "$ref": "#/components/schemas/GroupId", "description": "" + }, + "modifiedBy": { + "description": "", + "type": "string" + }, + "modifiedOn": { + "format": "date-time", + "description": "", + "type": "string" } }, "example": { @@ -4814,16 +4927,11 @@ "labels": { "$ref": "#/components/schemas/Labels", "description": "" - }, - "state": { - "$ref": "#/components/schemas/VersionState", - "description": "" } }, "example": { "name": "Artifact Name", "description": "The description of the artifact.", - "state": "DEPRECATED", "labels": { "custom-1": "foo", "custom-2": "bar" @@ -5163,6 +5271,22 @@ } } } + }, + "WrappedVersionState": { + "title": "Root Type for WrappedVersionState", + "description": "", + "required": [ + "state" + ], + "type": "object", + "properties": { + "state": { + "$ref": "#/components/schemas/VersionState" + } + }, + "example": { + "state": "ENABLED" + } } }, "responses": { diff --git a/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_item_state_request_builder.go b/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_item_state_request_builder.go new file mode 100644 index 0000000000..ed25721877 --- /dev/null +++ b/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_item_state_request_builder.go @@ -0,0 +1,134 @@ +package groups + +import ( + "context" + i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71 "github.com/apicurio/apicurio-registry/go-sdk/pkg/registryclient-v3/models" + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f "github.com/microsoft/kiota-abstractions-go" +) + +// ItemArtifactsItemVersionsItemStateRequestBuilder manage the state of an artifact version. +type ItemArtifactsItemVersionsItemStateRequestBuilder struct { + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.BaseRequestBuilder +} + +// ItemArtifactsItemVersionsItemStateRequestBuilderGetRequestConfiguration configuration for the request such as headers, query parameters, and middleware options. +type ItemArtifactsItemVersionsItemStateRequestBuilderGetRequestConfiguration struct { + // Request headers + Headers *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestHeaders + // Request options + Options []i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestOption +} + +// ItemArtifactsItemVersionsItemStateRequestBuilderPutQueryParameters updates the state of an artifact version.NOTE: There are some restrictions on state transitions. Notably a version cannot be transitioned to the `DRAFT` state from any other state. The `DRAFT` state can only be entered (optionally) when creating a new artifact/version.A version in `DRAFT` state can only be transitioned to `ENABLED`. When thishappens, any configured content rules will be applied. This may result in afailure to change the state.This operation can fail for the following reasons:* No artifact with this `artifactId` exists (HTTP error `404`)* No version with this `version` exists (HTTP error `404`)* An invalid new state was provided (HTTP error `400`)* The draft content violates one or more of the rules configured for the artifact (HTTP error `409`)* A server error occurred (HTTP error `500`) +type ItemArtifactsItemVersionsItemStateRequestBuilderPutQueryParameters struct { + // When set to `true`, the operation will not result in any changes. Instead, itwill return a result based on whether the operation **would have succeeded**. + DryRun *bool `uriparametername:"dryRun"` +} + +// ItemArtifactsItemVersionsItemStateRequestBuilderPutRequestConfiguration configuration for the request such as headers, query parameters, and middleware options. +type ItemArtifactsItemVersionsItemStateRequestBuilderPutRequestConfiguration struct { + // Request headers + Headers *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestHeaders + // Request options + Options []i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestOption + // Request query parameters + QueryParameters *ItemArtifactsItemVersionsItemStateRequestBuilderPutQueryParameters +} + +// NewItemArtifactsItemVersionsItemStateRequestBuilderInternal instantiates a new ItemArtifactsItemVersionsItemStateRequestBuilder and sets the default values. +func NewItemArtifactsItemVersionsItemStateRequestBuilderInternal(pathParameters map[string]string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *ItemArtifactsItemVersionsItemStateRequestBuilder { + m := &ItemArtifactsItemVersionsItemStateRequestBuilder{ + BaseRequestBuilder: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewBaseRequestBuilder(requestAdapter, "{+baseurl}/groups/{groupId}/artifacts/{artifactId}/versions/{versionExpression}/state{?dryRun*}", pathParameters), + } + return m +} + +// NewItemArtifactsItemVersionsItemStateRequestBuilder instantiates a new ItemArtifactsItemVersionsItemStateRequestBuilder and sets the default values. +func NewItemArtifactsItemVersionsItemStateRequestBuilder(rawUrl string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *ItemArtifactsItemVersionsItemStateRequestBuilder { + urlParams := make(map[string]string) + urlParams["request-raw-url"] = rawUrl + return NewItemArtifactsItemVersionsItemStateRequestBuilderInternal(urlParams, requestAdapter) +} + +// Get gets the current state of an artifact version.This operation can fail for the following reasons:* No artifact with this `artifactId` exists (HTTP error `404`)* No version with this `version` exists (HTTP error `404`)* A server error occurred (HTTP error `500`) +// returns a WrappedVersionStateable when successful +// returns a ProblemDetails error when the service returns a 404 status code +// returns a ProblemDetails error when the service returns a 500 status code +func (m *ItemArtifactsItemVersionsItemStateRequestBuilder) Get(ctx context.Context, requestConfiguration *ItemArtifactsItemVersionsItemStateRequestBuilderGetRequestConfiguration) (i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.WrappedVersionStateable, error) { + requestInfo, err := m.ToGetRequestInformation(ctx, requestConfiguration) + if err != nil { + return nil, err + } + errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "404": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, + "500": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, + } + res, err := m.BaseRequestBuilder.RequestAdapter.Send(ctx, requestInfo, i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateWrappedVersionStateFromDiscriminatorValue, errorMapping) + if err != nil { + return nil, err + } + if res == nil { + return nil, nil + } + return res.(i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.WrappedVersionStateable), nil +} + +// Put updates the state of an artifact version.NOTE: There are some restrictions on state transitions. Notably a version cannot be transitioned to the `DRAFT` state from any other state. The `DRAFT` state can only be entered (optionally) when creating a new artifact/version.A version in `DRAFT` state can only be transitioned to `ENABLED`. When thishappens, any configured content rules will be applied. This may result in afailure to change the state.This operation can fail for the following reasons:* No artifact with this `artifactId` exists (HTTP error `404`)* No version with this `version` exists (HTTP error `404`)* An invalid new state was provided (HTTP error `400`)* The draft content violates one or more of the rules configured for the artifact (HTTP error `409`)* A server error occurred (HTTP error `500`) +// returns a ProblemDetails error when the service returns a 400 status code +// returns a ProblemDetails error when the service returns a 404 status code +// returns a ProblemDetails error when the service returns a 409 status code +// returns a ProblemDetails error when the service returns a 500 status code +func (m *ItemArtifactsItemVersionsItemStateRequestBuilder) Put(ctx context.Context, body i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.WrappedVersionStateable, requestConfiguration *ItemArtifactsItemVersionsItemStateRequestBuilderPutRequestConfiguration) error { + requestInfo, err := m.ToPutRequestInformation(ctx, body, requestConfiguration) + if err != nil { + return err + } + errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "400": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, + "404": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, + "409": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, + "500": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, + } + err = m.BaseRequestBuilder.RequestAdapter.SendNoContent(ctx, requestInfo, errorMapping) + if err != nil { + return err + } + return nil +} + +// ToGetRequestInformation gets the current state of an artifact version.This operation can fail for the following reasons:* No artifact with this `artifactId` exists (HTTP error `404`)* No version with this `version` exists (HTTP error `404`)* A server error occurred (HTTP error `500`) +// returns a *RequestInformation when successful +func (m *ItemArtifactsItemVersionsItemStateRequestBuilder) ToGetRequestInformation(ctx context.Context, requestConfiguration *ItemArtifactsItemVersionsItemStateRequestBuilderGetRequestConfiguration) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { + requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.GET, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) + if requestConfiguration != nil { + requestInfo.Headers.AddAll(requestConfiguration.Headers) + requestInfo.AddRequestOptions(requestConfiguration.Options) + } + requestInfo.Headers.TryAdd("Accept", "application/json") + return requestInfo, nil +} + +// ToPutRequestInformation updates the state of an artifact version.NOTE: There are some restrictions on state transitions. Notably a version cannot be transitioned to the `DRAFT` state from any other state. The `DRAFT` state can only be entered (optionally) when creating a new artifact/version.A version in `DRAFT` state can only be transitioned to `ENABLED`. When thishappens, any configured content rules will be applied. This may result in afailure to change the state.This operation can fail for the following reasons:* No artifact with this `artifactId` exists (HTTP error `404`)* No version with this `version` exists (HTTP error `404`)* An invalid new state was provided (HTTP error `400`)* The draft content violates one or more of the rules configured for the artifact (HTTP error `409`)* A server error occurred (HTTP error `500`) +// returns a *RequestInformation when successful +func (m *ItemArtifactsItemVersionsItemStateRequestBuilder) ToPutRequestInformation(ctx context.Context, body i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.WrappedVersionStateable, requestConfiguration *ItemArtifactsItemVersionsItemStateRequestBuilderPutRequestConfiguration) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { + requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.PUT, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) + if requestConfiguration != nil { + if requestConfiguration.QueryParameters != nil { + requestInfo.AddQueryParameters(*(requestConfiguration.QueryParameters)) + } + requestInfo.Headers.AddAll(requestConfiguration.Headers) + requestInfo.AddRequestOptions(requestConfiguration.Options) + } + requestInfo.Headers.TryAdd("Accept", "application/json") + err := requestInfo.SetContentFromParsable(ctx, m.BaseRequestBuilder.RequestAdapter, "application/json", body) + if err != nil { + return nil, err + } + return requestInfo, nil +} + +// WithUrl returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. +// returns a *ItemArtifactsItemVersionsItemStateRequestBuilder when successful +func (m *ItemArtifactsItemVersionsItemStateRequestBuilder) WithUrl(rawUrl string) *ItemArtifactsItemVersionsItemStateRequestBuilder { + return NewItemArtifactsItemVersionsItemStateRequestBuilder(rawUrl, m.BaseRequestBuilder.RequestAdapter) +} diff --git a/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_with_version_expression_item_request_builder.go b/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_with_version_expression_item_request_builder.go index 18813fb531..c7ae8d9fcc 100644 --- a/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_with_version_expression_item_request_builder.go +++ b/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_with_version_expression_item_request_builder.go @@ -137,6 +137,12 @@ func (m *ItemArtifactsItemVersionsWithVersionExpressionItemRequestBuilder) Refer return NewItemArtifactsItemVersionsItemReferencesRequestBuilderInternal(m.BaseRequestBuilder.PathParameters, m.BaseRequestBuilder.RequestAdapter) } +// State manage the state of an artifact version. +// returns a *ItemArtifactsItemVersionsItemStateRequestBuilder when successful +func (m *ItemArtifactsItemVersionsWithVersionExpressionItemRequestBuilder) State() *ItemArtifactsItemVersionsItemStateRequestBuilder { + return NewItemArtifactsItemVersionsItemStateRequestBuilderInternal(m.BaseRequestBuilder.PathParameters, m.BaseRequestBuilder.RequestAdapter) +} + // ToDeleteRequestInformation deletes a single version of the artifact. Parameters `groupId`, `artifactId` and the unique `version`are needed. If this is the only version of the artifact, this operation is the same as deleting the entire artifact.This feature is disabled by default and it's discouraged for normal usage. To enable it, set the `registry.rest.artifact.deletion.enabled` property to true. This operation can fail for the following reasons:* No artifact with this `artifactId` exists (HTTP error `404`)* No version with this `version` exists (HTTP error `404`) * Feature is disabled (HTTP error `405`) * A server error occurred (HTTP error `500`) // returns a *RequestInformation when successful func (m *ItemArtifactsItemVersionsWithVersionExpressionItemRequestBuilder) ToDeleteRequestInformation(ctx context.Context, requestConfiguration *ItemArtifactsItemVersionsWithVersionExpressionItemRequestBuilderDeleteRequestConfiguration) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { diff --git a/go-sdk/pkg/registryclient-v3/kiota-lock.json b/go-sdk/pkg/registryclient-v3/kiota-lock.json index f532f020a1..6cb6aeef90 100644 --- a/go-sdk/pkg/registryclient-v3/kiota-lock.json +++ b/go-sdk/pkg/registryclient-v3/kiota-lock.json @@ -1,5 +1,5 @@ { - "descriptionHash": "E5ED0C5C8F0A47793ABA0A9A0C89B7EC1A9389B126163E27488DD6DD6AA0A808B4EBEA968749621300166FFCD05769D335D8088A2A4FBF0FA24B3F59CCEABF4F", + "descriptionHash": "5F4C84AA016E80F478E11943494C4DDE8945B43653170814AFB022ABD1210EF836108E3529244BAB01A049E0D0CE5409EA1403870644B7F1F04B0F7FBE658A4B", "descriptionLocation": "../../v3.json", "lockFileVersion": "1.0.0", "kiotaVersion": "1.18.0", diff --git a/go-sdk/pkg/registryclient-v3/models/editable_version_meta_data.go b/go-sdk/pkg/registryclient-v3/models/editable_version_meta_data.go index ffdab100bb..892c82d43a 100644 --- a/go-sdk/pkg/registryclient-v3/models/editable_version_meta_data.go +++ b/go-sdk/pkg/registryclient-v3/models/editable_version_meta_data.go @@ -13,8 +13,6 @@ type EditableVersionMetaData struct { labels Labelsable // The name property name *string - // Describes the state of an artifact or artifact version. The following statesare possible:* ENABLED* DISABLED* DEPRECATED - state *VersionState } // NewEditableVersionMetaData instantiates a new EditableVersionMetaData and sets the default values. @@ -76,16 +74,6 @@ func (m *EditableVersionMetaData) GetFieldDeserializers() map[string]func(i878a8 } return nil } - res["state"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { - val, err := n.GetEnumValue(ParseVersionState) - if err != nil { - return err - } - if val != nil { - m.SetState(val.(*VersionState)) - } - return nil - } return res } @@ -101,12 +89,6 @@ func (m *EditableVersionMetaData) GetName() *string { return m.name } -// GetState gets the state property value. Describes the state of an artifact or artifact version. The following statesare possible:* ENABLED* DISABLED* DEPRECATED -// returns a *VersionState when successful -func (m *EditableVersionMetaData) GetState() *VersionState { - return m.state -} - // Serialize serializes information the current object func (m *EditableVersionMetaData) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { { @@ -127,13 +109,6 @@ func (m *EditableVersionMetaData) Serialize(writer i878a80d2330e89d26896388a3f48 return err } } - if m.GetState() != nil { - cast := (*m.GetState()).String() - err := writer.WriteStringValue("state", &cast) - if err != nil { - return err - } - } { err := writer.WriteAdditionalData(m.GetAdditionalData()) if err != nil { @@ -163,20 +138,13 @@ func (m *EditableVersionMetaData) SetName(value *string) { m.name = value } -// SetState sets the state property value. Describes the state of an artifact or artifact version. The following statesare possible:* ENABLED* DISABLED* DEPRECATED -func (m *EditableVersionMetaData) SetState(value *VersionState) { - m.state = value -} - type EditableVersionMetaDataable interface { i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable GetDescription() *string GetLabels() Labelsable GetName() *string - GetState() *VersionState SetDescription(value *string) SetLabels(value Labelsable) SetName(value *string) - SetState(value *VersionState) } diff --git a/go-sdk/pkg/registryclient-v3/models/searched_version.go b/go-sdk/pkg/registryclient-v3/models/searched_version.go index 6afa27c5d5..366986021d 100644 --- a/go-sdk/pkg/registryclient-v3/models/searched_version.go +++ b/go-sdk/pkg/registryclient-v3/models/searched_version.go @@ -23,6 +23,10 @@ type SearchedVersion struct { globalId *int64 // An ID of a single artifact group. groupId *string + // The modifiedBy property + modifiedBy *string + // The modifiedOn property + modifiedOn *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time // The name property name *string // The owner property @@ -156,6 +160,26 @@ func (m *SearchedVersion) GetFieldDeserializers() map[string]func(i878a80d2330e8 } return nil } + res["modifiedBy"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetModifiedBy(val) + } + return nil + } + res["modifiedOn"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetTimeValue() + if err != nil { + return err + } + if val != nil { + m.SetModifiedOn(val) + } + return nil + } res["name"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { val, err := n.GetStringValue() if err != nil { @@ -211,6 +235,18 @@ func (m *SearchedVersion) GetGroupId() *string { return m.groupId } +// GetModifiedBy gets the modifiedBy property value. The modifiedBy property +// returns a *string when successful +func (m *SearchedVersion) GetModifiedBy() *string { + return m.modifiedBy +} + +// GetModifiedOn gets the modifiedOn property value. The modifiedOn property +// returns a *Time when successful +func (m *SearchedVersion) GetModifiedOn() *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time { + return m.modifiedOn +} + // GetName gets the name property value. The name property // returns a *string when successful func (m *SearchedVersion) GetName() *string { @@ -279,6 +315,18 @@ func (m *SearchedVersion) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0 return err } } + { + err := writer.WriteStringValue("modifiedBy", m.GetModifiedBy()) + if err != nil { + return err + } + } + { + err := writer.WriteTimeValue("modifiedOn", m.GetModifiedOn()) + if err != nil { + return err + } + } { err := writer.WriteStringValue("name", m.GetName()) if err != nil { @@ -353,6 +401,16 @@ func (m *SearchedVersion) SetGroupId(value *string) { m.groupId = value } +// SetModifiedBy sets the modifiedBy property value. The modifiedBy property +func (m *SearchedVersion) SetModifiedBy(value *string) { + m.modifiedBy = value +} + +// SetModifiedOn sets the modifiedOn property value. The modifiedOn property +func (m *SearchedVersion) SetModifiedOn(value *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time) { + m.modifiedOn = value +} + // SetName sets the name property value. The name property func (m *SearchedVersion) SetName(value *string) { m.name = value @@ -383,6 +441,8 @@ type SearchedVersionable interface { GetDescription() *string GetGlobalId() *int64 GetGroupId() *string + GetModifiedBy() *string + GetModifiedOn() *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time GetName() *string GetOwner() *string GetState() *VersionState @@ -394,6 +454,8 @@ type SearchedVersionable interface { SetDescription(value *string) SetGlobalId(value *int64) SetGroupId(value *string) + SetModifiedBy(value *string) + SetModifiedOn(value *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time) SetName(value *string) SetOwner(value *string) SetState(value *VersionState) diff --git a/go-sdk/pkg/registryclient-v3/models/wrapped_version_state.go b/go-sdk/pkg/registryclient-v3/models/wrapped_version_state.go new file mode 100644 index 0000000000..fb60c2bc68 --- /dev/null +++ b/go-sdk/pkg/registryclient-v3/models/wrapped_version_state.go @@ -0,0 +1,89 @@ +package models + +import ( + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type WrappedVersionState struct { + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // Describes the state of an artifact or artifact version. The following statesare possible:* ENABLED* DISABLED* DEPRECATED + state *VersionState +} + +// NewWrappedVersionState instantiates a new WrappedVersionState and sets the default values. +func NewWrappedVersionState() *WrappedVersionState { + m := &WrappedVersionState{} + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateWrappedVersionStateFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateWrappedVersionStateFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewWrappedVersionState(), nil +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *WrappedVersionState) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *WrappedVersionState) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["state"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetEnumValue(ParseVersionState) + if err != nil { + return err + } + if val != nil { + m.SetState(val.(*VersionState)) + } + return nil + } + return res +} + +// GetState gets the state property value. Describes the state of an artifact or artifact version. The following statesare possible:* ENABLED* DISABLED* DEPRECATED +// returns a *VersionState when successful +func (m *WrappedVersionState) GetState() *VersionState { + return m.state +} + +// Serialize serializes information the current object +func (m *WrappedVersionState) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + if m.GetState() != nil { + cast := (*m.GetState()).String() + err := writer.WriteStringValue("state", &cast) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *WrappedVersionState) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetState sets the state property value. Describes the state of an artifact or artifact version. The following statesare possible:* ENABLED* DISABLED* DEPRECATED +func (m *WrappedVersionState) SetState(value *VersionState) { + m.state = value +} + +type WrappedVersionStateable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetState() *VersionState + SetState(value *VersionState) +} diff --git a/integration-tests/src/test/java/io/apicurio/tests/smokeTests/apicurio/ArtifactsIT.java b/integration-tests/src/test/java/io/apicurio/tests/smokeTests/apicurio/ArtifactsIT.java index e1f5e75728..41f940ab62 100644 --- a/integration-tests/src/test/java/io/apicurio/tests/smokeTests/apicurio/ArtifactsIT.java +++ b/integration-tests/src/test/java/io/apicurio/tests/smokeTests/apicurio/ArtifactsIT.java @@ -8,12 +8,12 @@ import io.apicurio.registry.rest.client.models.CreateArtifactResponse; import io.apicurio.registry.rest.client.models.CreateRule; import io.apicurio.registry.rest.client.models.CreateVersion; -import io.apicurio.registry.rest.client.models.EditableVersionMetaData; import io.apicurio.registry.rest.client.models.IfArtifactExists; import io.apicurio.registry.rest.client.models.RuleType; import io.apicurio.registry.rest.client.models.SortOrder; import io.apicurio.registry.rest.client.models.VersionMetaData; import io.apicurio.registry.rest.client.models.VersionState; +import io.apicurio.registry.rest.client.models.WrappedVersionState; import io.apicurio.registry.rest.v2.beans.ArtifactContent; import io.apicurio.registry.types.ArtifactType; import io.apicurio.registry.types.ContentTypes; @@ -52,10 +52,10 @@ class ArtifactsIT extends ApicurioRegistryBaseIT { private final ObjectMapper mapper = new ObjectMapper(); - private static final EditableVersionMetaData toEditableVersionMetaData(VersionState state) { - EditableVersionMetaData evmd = new EditableVersionMetaData(); - evmd.setState(state); - return evmd; + private static WrappedVersionState toWrappedVersionState(VersionState state) { + WrappedVersionState wvs = new WrappedVersionState(); + wvs.setState(state); + return wvs; } @Test @@ -298,8 +298,8 @@ void testDisableEnableArtifactVersion() throws Exception { // Disable v3 registryClient.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() - .byVersionExpression(String.valueOf(v3MD.getVersion())) - .put(toEditableVersionMetaData(VersionState.DISABLED)); + .byVersionExpression(String.valueOf(v3MD.getVersion())).state() + .put(toWrappedVersionState(VersionState.DISABLED)); // Verify artifact retryOp((rc) -> { @@ -323,8 +323,8 @@ void testDisableEnableArtifactVersion() throws Exception { // Re-enable v3 registryClient.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() - .byVersionExpression(String.valueOf(v3MD.getVersion())) - .put(toEditableVersionMetaData(VersionState.ENABLED)); + .byVersionExpression(String.valueOf(v3MD.getVersion())).state() + .put(toWrappedVersionState(VersionState.ENABLED)); retryOp((rc) -> { // Verify artifact (now v3) @@ -370,8 +370,8 @@ void testDeprecateArtifactVersion() throws Exception { // Deprecate v2 registryClient.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() - .byVersionExpression(String.valueOf(v2MD.getVersion())) - .put(toEditableVersionMetaData(VersionState.DEPRECATED)); + .byVersionExpression(String.valueOf(v2MD.getVersion())).state() + .put(toWrappedVersionState(VersionState.DEPRECATED)); retryOp((rc) -> { // Verify v1 From 66dcb981e5bd06dffd6cea6310894f6b9c72f5de Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Thu, 10 Oct 2024 13:39:44 -0400 Subject: [PATCH 06/12] Improvements/tweaks to DRAFT content --- .../io/apicurio/registry/rest/RestConfig.java | 11 +++++++- .../registry/rest/v3/GroupsResourceImpl.java | 11 ++++---- .../registry/rest/v3/IdsResourceImpl.java | 10 +++++-- .../storage/dto/ContentWrapperDto.java | 1 + .../impl/sql/AbstractSqlRegistryStorage.java | 27 +++++++++++++++---- .../storage/impl/sql/CommonSqlStatements.java | 6 +++-- .../impl/sql/mappers/ContentMapper.java | 1 + .../noprofile/rest/v3/DraftContentTest.java | 22 ++++++++++++++- .../noprofile/rest/v3/DryRunTest.java | 4 +-- .../src/main/resources/META-INF/openapi.json | 9 +++++++ .../ref-registry-all-configs.adoc | 7 ++++- ...m_versions_item_content_request_builder.go | 2 ++ ...acts_with_artifact_item_request_builder.go | 2 ++ .../groups/with_group_item_request_builder.go | 2 ++ go-sdk/pkg/registryclient-v3/kiota-lock.json | 2 +- .../utils/tests/MutabilityEnabledProfile.java | 16 +++++++++++ 16 files changed, 113 insertions(+), 20 deletions(-) create mode 100644 utils/tests/src/main/java/io/apicurio/registry/utils/tests/MutabilityEnabledProfile.java diff --git a/app/src/main/java/io/apicurio/registry/rest/RestConfig.java b/app/src/main/java/io/apicurio/registry/rest/RestConfig.java index e4b309eb91..48dd2a1208 100644 --- a/app/src/main/java/io/apicurio/registry/rest/RestConfig.java +++ b/app/src/main/java/io/apicurio/registry/rest/RestConfig.java @@ -30,9 +30,14 @@ public class RestConfig { @Dynamic(label = "Delete artifact version", description = "When selected, users are permitted to delete artifact versions.") @ConfigProperty(name = "apicurio.rest.deletion.artifact-version.enabled", defaultValue = "false") - @Info(category = "rest", description = "Enables artifact version deletion", availableSince = "2.4.2-SNAPSHOT") + @Info(category = "rest", description = "Enables artifact version deletion", availableSince = "2.4.2") Supplier artifactVersionDeletionEnabled; + @Dynamic(label = "Update artifact version content", description = "When selected, users are permitted to update the content of artifact versions (only when in the DRAFT state).") + @ConfigProperty(name = "apicurio.rest.mutability.artifact-version-content.enabled", defaultValue = "false") + @Info(category = "rest", description = "Enables artifact version mutability", availableSince = "3.0.2") + Supplier artifactVersionMutabilityEnabled; + public int getDownloadMaxSize() { return this.downloadMaxSize; } @@ -53,4 +58,8 @@ public boolean isArtifactVersionDeletionEnabled() { return artifactVersionDeletionEnabled.get(); } + public boolean isArtifactVersionMutabilityEnabled() { + return artifactVersionMutabilityEnabled.get(); + } + } diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java index 41e49eb791..0cffd54a2e 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java @@ -535,7 +535,10 @@ public void updateArtifactVersionContent(String groupId, String artifactId, Stri requireParameter("artifactId", artifactId); requireParameter("versionExpression", versionExpression); - // TODO check if DRAFT content is allowed (global config property) + if (!restConfig.isArtifactVersionMutabilityEnabled()) { + throw new NotAllowedException("Artifact version content update operation is not enabled.", + HttpMethod.GET, (String[]) null); + } // Resolve the GAV info var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), versionExpression, @@ -724,7 +727,7 @@ public Comment addArtifactVersionComment(String groupId, String artifactId, Stri */ @Override @Audited(extractParameters = { "0", KEY_GROUP_ID, "1", KEY_ARTIFACT_ID, "2", KEY_VERSION, "3", - "comment_id" }) // TODO + "comment_id" }) @Authorized(style = AuthorizedStyle.GroupAndArtifact, level = AuthorizedLevel.Write) public void deleteArtifactVersionComment(String groupId, String artifactId, String versionExpression, String commentId) { @@ -765,7 +768,7 @@ public List getArtifactVersionComments(String groupId, String artifactI */ @Override @Audited(extractParameters = { "0", KEY_GROUP_ID, "1", KEY_ARTIFACT_ID, "2", KEY_VERSION, "3", - "comment_id" }) // TODO + "comment_id" }) @Authorized(style = AuthorizedStyle.GroupAndArtifact, level = AuthorizedLevel.Write) public void updateArtifactVersionComment(String groupId, String artifactId, String versionExpression, String commentId, NewComment data) { @@ -999,8 +1002,6 @@ public VersionMetaData createArtifactVersion(String groupId, String artifactId, String ct = data.getContent().getContentType(); boolean isDraft = data.getIsDraft() != null && data.getIsDraft(); - // TODO Only allow DRAFT content if the global opt-in config property is set/enabled - // Transform the given references into dtos final List referencesAsDtos = toReferenceDtos( data.getContent().getReferences()); diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/IdsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/IdsResourceImpl.java index 6e373adbdb..05daf68adf 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/IdsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/IdsResourceImpl.java @@ -16,6 +16,7 @@ import io.apicurio.registry.storage.dto.ContentWrapperDto; import io.apicurio.registry.storage.dto.StoredArtifactVersionDto; import io.apicurio.registry.storage.error.ArtifactNotFoundException; +import io.apicurio.registry.storage.error.ContentNotFoundException; import io.apicurio.registry.types.ArtifactMediaTypes; import io.apicurio.registry.types.ReferenceType; import io.apicurio.registry.types.VersionState; @@ -47,7 +48,11 @@ private void checkIfDeprecated(Supplier stateSupplier, String arti @Override @Authorized(style = AuthorizedStyle.None, level = AuthorizedLevel.Read) public Response getContentById(long contentId) { - ContentHandle content = storage.getContentById(contentId).getContent(); + ContentWrapperDto dto = storage.getContentById(contentId); + if (dto.getContentHash() != null && dto.getContentHash().startsWith("draft:")) { + throw new ContentNotFoundException(contentId); + } + ContentHandle content = dto.getContent(); Response.ResponseBuilder builder = Response.ok(content, ArtifactMediaTypes.BINARY); return builder.build(); } @@ -60,7 +65,8 @@ public Response getContentById(long contentId) { @Authorized(style = AuthorizedStyle.GlobalId, level = AuthorizedLevel.Read) public Response getContentByGlobalId(long globalId, HandleReferencesType references) { ArtifactVersionMetaDataDto metaData = storage.getArtifactVersionMetaData(globalId); - if (VersionState.DISABLED.equals(metaData.getState())) { + if (VersionState.DISABLED.equals(metaData.getState()) + || VersionState.DRAFT.equals(metaData.getState())) { throw new ArtifactNotFoundException(null, String.valueOf(globalId)); } diff --git a/app/src/main/java/io/apicurio/registry/storage/dto/ContentWrapperDto.java b/app/src/main/java/io/apicurio/registry/storage/dto/ContentWrapperDto.java index e1da307bad..eb7900ca69 100644 --- a/app/src/main/java/io/apicurio/registry/storage/dto/ContentWrapperDto.java +++ b/app/src/main/java/io/apicurio/registry/storage/dto/ContentWrapperDto.java @@ -18,4 +18,5 @@ public class ContentWrapperDto { private ContentHandle content; private List references; private String artifactType; + private transient String contentHash; } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java index 4d107e9c5f..0b2c8c2c7d 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java @@ -530,7 +530,7 @@ public Pair createArtifact(Stri long cid = -1; if (versionContent != null) { // Put the content in the DB and get the unique content ID back. - cid = ensureContentAndGetId(artifactType, versionContent); + cid = ensureContentAndGetId(artifactType, versionContent, versionIsDraft); } final long contentId = cid; @@ -719,14 +719,27 @@ private void createOrUpdateSemverBranchesRaw(Handle handle, GAV gav) { * Make sure the content exists in the database (try to insert it). Regardless of whether it already * existed or not, return the contentId of the content in the DB. */ - private Long ensureContentAndGetId(String artifactType, ContentWrapperDto contentDto) { + private Long ensureContentAndGetId(String artifactType, ContentWrapperDto contentDto, boolean isDraft) { List references = contentDto.getReferences(); TypedContent content = TypedContent.create(contentDto.getContent(), contentDto.getContentType()); String contentHash; String canonicalContentHash; String serializedReferences; - if (notEmpty(references)) { + // Need to create the content hash and canonical content hash. If the content is DRAFT + // content, then do NOT calculate those hashes because we don't want DRAFT content to + // be looked up by those hashes. + // + // So we have three different paths to calculate the hashes: + // 1. If DRAFT state + // 2. If the content has references + // 3. If the content has no references + + if (isDraft) { + contentHash = "draft:" + UUID.randomUUID().toString(); + canonicalContentHash = "draft:" + UUID.randomUUID().toString(); + serializedReferences = null; + } else if (notEmpty(references)) { Function, Map> referenceResolver = (refs) -> { return handles.withHandle(handle -> { return resolveReferencesRaw(handle, refs); @@ -863,7 +876,7 @@ public ArtifactVersionMetaDataDto createArtifactVersion(String groupId, String a Date createdOn = new Date(); // Put the content in the DB and get the unique content ID back. - long contentId = ensureContentAndGetId(artifactType, content); + long contentId = ensureContentAndGetId(artifactType, content, isDraft); try { // Create version and return @@ -1769,7 +1782,7 @@ public void updateArtifactVersionContent(String groupId, String artifactId, Stri log.debug("Updating content for artifact version: {} {} @ {}", groupId, artifactId, version); // Put the new content in the DB and get the unique content ID back. - long contentId = ensureContentAndGetId(artifactType, content); + long contentId = ensureContentAndGetId(artifactType, content, true); String modifiedBy = securityIdentity.getPrincipal().getName(); Date modifiedOn = new Date(); @@ -1781,6 +1794,10 @@ public void updateArtifactVersionContent(String groupId, String artifactId, Stri if (rowCount == 0) { throw new VersionNotFoundException(groupId, artifactId, version); } + + // Updating content will typically leave a row in the content table orphaned. + deleteAllOrphanedContentRaw(handle); + return null; }); } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java index 3cea7a5c31..602a36f0c2 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java @@ -607,7 +607,8 @@ public String selectContentCountByHash() { */ @Override public String selectContentById() { - return "SELECT c.content, c.contentType, c.refs FROM content c " + "WHERE c.contentId = ?"; + return "SELECT c.content, c.contentType, c.refs, c.contentHash FROM content c " + + "WHERE c.contentId = ?"; } /** @@ -615,7 +616,8 @@ public String selectContentById() { */ @Override public String selectContentByContentHash() { - return "SELECT c.content, c.contentType, c.refs FROM content c " + "WHERE c.contentHash = ?"; + return "SELECT c.content, c.contentType, c.refs, c.contentHash FROM content c " + + "WHERE c.contentHash = ?"; } @Override diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/ContentMapper.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/ContentMapper.java index ef6951e0a7..819c7e2658 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/ContentMapper.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/ContentMapper.java @@ -29,6 +29,7 @@ public ContentWrapperDto map(ResultSet rs) throws SQLException { contentWrapperDto.setContent(content); contentWrapperDto.setContentType(rs.getString("contentType")); contentWrapperDto.setReferences(RegistryContentUtils.deserializeReferences(rs.getString("refs"))); + contentWrapperDto.setContentHash(rs.getString("contentHash")); return contentWrapperDto; } diff --git a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java index 09f20adb27..27c2024274 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java @@ -2,6 +2,7 @@ import io.apicurio.registry.AbstractResourceTestBase; import io.apicurio.registry.rest.client.models.CreateArtifact; +import io.apicurio.registry.rest.client.models.CreateArtifactResponse; import io.apicurio.registry.rest.client.models.CreateGroup; import io.apicurio.registry.rest.client.models.CreateRule; import io.apicurio.registry.rest.client.models.CreateVersion; @@ -15,8 +16,10 @@ import io.apicurio.registry.rules.validity.ValidityLevel; import io.apicurio.registry.types.ArtifactType; import io.apicurio.registry.types.ContentTypes; +import io.apicurio.registry.utils.tests.MutabilityEnabledProfile; import io.apicurio.registry.utils.tests.TestUtils; import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -25,6 +28,7 @@ import java.nio.charset.StandardCharsets; @QuarkusTest +@TestProfile(MutabilityEnabledProfile.class) public class DraftContentTest extends AbstractResourceTestBase { private static final String AVRO_CONTENT_V1 = """ @@ -70,12 +74,28 @@ public void testCreateDraftArtifact() throws Exception { createArtifact.getFirstVersion().setIsDraft(true); createArtifact.getFirstVersion().setVersion("1.0.0"); - clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + CreateArtifactResponse car = clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + Assertions.assertNotNull(car); + Assertions.assertNotNull(car.getVersion()); VersionMetaData vmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId) .versions().byVersionExpression("1.0.0").get(); Assertions.assertNotNull(vmd); Assertions.assertEquals(VersionState.DRAFT, vmd.getState()); + + // Note: Should NOT be able to fetch its content by globalId (disallowed for DRAFT content) + Long globalId = car.getVersion().getGlobalId(); + Assertions.assertNotNull(globalId); + Assertions.assertThrows(ProblemDetails.class, () -> { + clientV3.ids().globalIds().byGlobalId(globalId).get(); + }); + + // Note: Should NOT be able to fetch its content by contentId (disallowed for DRAFT content) + Long contentId = car.getVersion().getContentId(); + Assertions.assertNotNull(contentId); + Assertions.assertThrows(ProblemDetails.class, () -> { + clientV3.ids().contentIds().byContentId(contentId).get(); + }); } @Test diff --git a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DryRunTest.java b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DryRunTest.java index 245f933d67..8a33ceb1ce 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DryRunTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DryRunTest.java @@ -17,7 +17,7 @@ import io.apicurio.registry.rules.validity.ValidityLevel; import io.apicurio.registry.types.ArtifactType; import io.apicurio.registry.types.ContentTypes; -import io.apicurio.registry.utils.tests.DeletionEnabledProfile; +import io.apicurio.registry.utils.tests.MutabilityEnabledProfile; import io.apicurio.registry.utils.tests.TestUtils; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; @@ -25,7 +25,7 @@ import org.junit.jupiter.api.Test; @QuarkusTest -@TestProfile(DeletionEnabledProfile.class) +@TestProfile(MutabilityEnabledProfile.class) public class DryRunTest extends AbstractResourceTestBase { private static final String SCHEMA_SIMPLE = """ diff --git a/common/src/main/resources/META-INF/openapi.json b/common/src/main/resources/META-INF/openapi.json index 6aa8da9d64..25983056c0 100644 --- a/common/src/main/resources/META-INF/openapi.json +++ b/common/src/main/resources/META-INF/openapi.json @@ -2357,6 +2357,9 @@ "404": { "$ref": "#/components/responses/NotFound" }, + "405": { + "$ref": "#/components/responses/MethodNotAllowed" + }, "500": { "$ref": "#/components/responses/ServerError" } @@ -2574,6 +2577,9 @@ "404": { "$ref": "#/components/responses/NotFound" }, + "405": { + "$ref": "#/components/responses/MethodNotAllowed" + }, "500": { "$ref": "#/components/responses/ServerError" } @@ -2659,6 +2665,9 @@ "404": { "$ref": "#/components/responses/NotFound" }, + "405": { + "$ref": "#/components/responses/MethodNotAllowed" + }, "409": { "$ref": "#/components/responses/Conflict" }, diff --git a/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc b/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc index e9be6e73dd..9a28bd12b3 100644 --- a/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc +++ b/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc @@ -540,7 +540,7 @@ The following {registry} configuration options are available for each component |`apicurio.rest.deletion.artifact-version.enabled` |`boolean [dynamic]` |`false` -|`2.4.2-SNAPSHOT` +|`2.4.2` |Enables artifact version deletion |`apicurio.rest.deletion.artifact.enabled` |`boolean [dynamic]` @@ -552,6 +552,11 @@ The following {registry} configuration options are available for each component |`false` |`3.0.0` |Enables group deletion +|`apicurio.rest.mutability.artifact-version-content.enabled` +|`boolean [dynamic]` +|`false` +|`3.0.2` +|Enables artifact version mutability |=== == semver diff --git a/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_item_content_request_builder.go b/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_item_content_request_builder.go index 2d0c8ff0d0..36b89c78b7 100644 --- a/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_item_content_request_builder.go +++ b/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_item_content_request_builder.go @@ -80,6 +80,7 @@ func (m *ItemArtifactsItemVersionsItemContentRequestBuilder) Get(ctx context.Con // Put updates the content of a single version of an artifact.NOTE: the artifact must be in `DRAFT` status.Both the `artifactId` and the unique `version` number must be provided to identifythe version to update.This operation can fail for the following reasons:* No artifact with this `artifactId` exists (HTTP error `404`)* No version with this `version` exists (HTTP error `404`)* Artifact version not in `DRAFT` status (HTTP error `409`)* A server error occurred (HTTP error `500`) // returns a ProblemDetails error when the service returns a 404 status code +// returns a ProblemDetails error when the service returns a 405 status code // returns a ProblemDetails error when the service returns a 409 status code // returns a ProblemDetails error when the service returns a 500 status code func (m *ItemArtifactsItemVersionsItemContentRequestBuilder) Put(ctx context.Context, body i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.VersionContentable, requestConfiguration *ItemArtifactsItemVersionsItemContentRequestBuilderPutRequestConfiguration) error { @@ -89,6 +90,7 @@ func (m *ItemArtifactsItemVersionsItemContentRequestBuilder) Put(ctx context.Con } errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ "404": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, + "405": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, "409": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, "500": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, } diff --git a/go-sdk/pkg/registryclient-v3/groups/item_artifacts_with_artifact_item_request_builder.go b/go-sdk/pkg/registryclient-v3/groups/item_artifacts_with_artifact_item_request_builder.go index 82052f63e9..c6da0f4f25 100644 --- a/go-sdk/pkg/registryclient-v3/groups/item_artifacts_with_artifact_item_request_builder.go +++ b/go-sdk/pkg/registryclient-v3/groups/item_artifacts_with_artifact_item_request_builder.go @@ -58,6 +58,7 @@ func NewItemArtifactsWithArtifactItemRequestBuilder(rawUrl string, requestAdapte // Delete deletes an artifact completely, resulting in all versions of the artifact also beingdeleted. This may fail for one of the following reasons:* No artifact with the `artifactId` exists (HTTP error `404`)* A server error occurred (HTTP error `500`) // returns a ProblemDetails error when the service returns a 404 status code +// returns a ProblemDetails error when the service returns a 405 status code // returns a ProblemDetails error when the service returns a 500 status code func (m *ItemArtifactsWithArtifactItemRequestBuilder) Delete(ctx context.Context, requestConfiguration *ItemArtifactsWithArtifactItemRequestBuilderDeleteRequestConfiguration) error { requestInfo, err := m.ToDeleteRequestInformation(ctx, requestConfiguration) @@ -66,6 +67,7 @@ func (m *ItemArtifactsWithArtifactItemRequestBuilder) Delete(ctx context.Context } errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ "404": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, + "405": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, "500": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, } err = m.BaseRequestBuilder.RequestAdapter.SendNoContent(ctx, requestInfo, errorMapping) diff --git a/go-sdk/pkg/registryclient-v3/groups/with_group_item_request_builder.go b/go-sdk/pkg/registryclient-v3/groups/with_group_item_request_builder.go index 927b5efe2c..caae83ca46 100644 --- a/go-sdk/pkg/registryclient-v3/groups/with_group_item_request_builder.go +++ b/go-sdk/pkg/registryclient-v3/groups/with_group_item_request_builder.go @@ -58,6 +58,7 @@ func NewWithGroupItemRequestBuilder(rawUrl string, requestAdapter i2ae4187f7daee // Delete deletes a group by identifier. This operation also deletes all artifacts withinthe group, so should be used very carefully.This operation can fail for the following reasons:* A server error occurred (HTTP error `500`)* The group does not exist (HTTP error `404`) // returns a ProblemDetails error when the service returns a 404 status code +// returns a ProblemDetails error when the service returns a 405 status code // returns a ProblemDetails error when the service returns a 500 status code func (m *WithGroupItemRequestBuilder) Delete(ctx context.Context, requestConfiguration *WithGroupItemRequestBuilderDeleteRequestConfiguration) error { requestInfo, err := m.ToDeleteRequestInformation(ctx, requestConfiguration) @@ -66,6 +67,7 @@ func (m *WithGroupItemRequestBuilder) Delete(ctx context.Context, requestConfigu } errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ "404": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, + "405": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, "500": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, } err = m.BaseRequestBuilder.RequestAdapter.SendNoContent(ctx, requestInfo, errorMapping) diff --git a/go-sdk/pkg/registryclient-v3/kiota-lock.json b/go-sdk/pkg/registryclient-v3/kiota-lock.json index 6cb6aeef90..d5c2c32f11 100644 --- a/go-sdk/pkg/registryclient-v3/kiota-lock.json +++ b/go-sdk/pkg/registryclient-v3/kiota-lock.json @@ -1,5 +1,5 @@ { - "descriptionHash": "5F4C84AA016E80F478E11943494C4DDE8945B43653170814AFB022ABD1210EF836108E3529244BAB01A049E0D0CE5409EA1403870644B7F1F04B0F7FBE658A4B", + "descriptionHash": "0DD5D7FB5AB616D59BD7C21D29C19401ADA735DD30E366CE1946E44209EFEFA3896C1AE4B2F3204F00EE2E6B58AE47F43F999A27E251F2FE5D5C9C65E34380EC", "descriptionLocation": "../../v3.json", "lockFileVersion": "1.0.0", "kiotaVersion": "1.18.0", diff --git a/utils/tests/src/main/java/io/apicurio/registry/utils/tests/MutabilityEnabledProfile.java b/utils/tests/src/main/java/io/apicurio/registry/utils/tests/MutabilityEnabledProfile.java new file mode 100644 index 0000000000..03ee5cf9b6 --- /dev/null +++ b/utils/tests/src/main/java/io/apicurio/registry/utils/tests/MutabilityEnabledProfile.java @@ -0,0 +1,16 @@ +package io.apicurio.registry.utils.tests; + +import io.quarkus.test.junit.QuarkusTestProfile; + +import java.util.HashMap; +import java.util.Map; + +public class MutabilityEnabledProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + Map props = new HashMap<>(); + props.put("apicurio.rest.mutability.artifact-version-content.enabled", "true"); + return props; + } +} From 1408647a6ee0c4af42a957aa9e208e4d197e30f8 Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Thu, 10 Oct 2024 14:22:48 -0400 Subject: [PATCH 07/12] Code reformatting --- .../registry/storage/impl/sql/AbstractSqlRegistryStorage.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java index 0b2c8c2c7d..07a6eb2ecd 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java @@ -1896,8 +1896,7 @@ public void updateArtifactVersionMetaData(String groupId, String artifactId, Str int rowCount = handle.createUpdate(sqlStatements.updateArtifactVersionLabelsByGAV()) .bind(0, RegistryContentUtils.serializeLabels(editableMetadata.getLabels())) - .bind(1, normalizeGroupId(groupId)) - .bind(2, artifactId).bind(3, version).execute(); + .bind(1, normalizeGroupId(groupId)).bind(2, artifactId).bind(3, version).execute(); if (rowCount == 0) { throw new VersionNotFoundException(groupId, artifactId, version); } From b2ca216a03ffc8ffaa2c9b1b0b193a2e09d98826 Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Thu, 10 Oct 2024 14:49:19 -0400 Subject: [PATCH 08/12] Add a UI label for draft, deprecated, and disabled versions --- .../components/tabs/VersionsTable.tsx | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/ui/ui-app/src/app/pages/artifact/components/tabs/VersionsTable.tsx b/ui/ui-app/src/app/pages/artifact/components/tabs/VersionsTable.tsx index 9111ef81a0..6cf0f86782 100644 --- a/ui/ui-app/src/app/pages/artifact/components/tabs/VersionsTable.tsx +++ b/ui/ui-app/src/app/pages/artifact/components/tabs/VersionsTable.tsx @@ -14,6 +14,7 @@ import { VersionSortByObject } from "@sdk/lib/generated-client/models"; import { ConfigService, useConfigService } from "@services/useConfigService.ts"; +import { Flex, FlexItem, Label } from "@patternfly/react-core"; export type VersionsTableProps = { artifact: ArtifactMetaData; @@ -56,15 +57,30 @@ export const VersionsTable: FunctionComponent = (props: Vers const version: string = encodeURIComponent(column.version!); return (
- - { column.version } - - ({column.name}) - - + + + + { column.version } + + ({column.name}) + + + + + + + + + + + + + + + From db9adeb97fff146121f2b83b690d8d5f90dc4ddb Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Fri, 11 Oct 2024 07:00:08 -0400 Subject: [PATCH 09/12] Added new "drafts" system branch --- .../kafkasql/serde/KafkaSqlMessageIndex.java | 3 ++ .../impl/sql/AbstractSqlRegistryStorage.java | 52 ++++++++++++++----- .../storage/impl/sql/CommonSqlStatements.java | 15 +++++- .../impl/sql/SQLServerSqlStatements.java | 6 +++ .../storage/impl/sql/SqlStatements.java | 16 +++++- .../noprofile/rest/v3/DraftContentTest.java | 51 ++++++++++++++++++ .../io/apicurio/registry/model/BranchId.java | 1 + 7 files changed, 127 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/serde/KafkaSqlMessageIndex.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/serde/KafkaSqlMessageIndex.java index d04dc009ea..33d917c7c6 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/serde/KafkaSqlMessageIndex.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/serde/KafkaSqlMessageIndex.java @@ -53,7 +53,9 @@ import io.apicurio.registry.storage.impl.kafkasql.messages.UpdateArtifactMetaData3Message; import io.apicurio.registry.storage.impl.kafkasql.messages.UpdateArtifactRule4Message; import io.apicurio.registry.storage.impl.kafkasql.messages.UpdateArtifactVersionComment5Message; +import io.apicurio.registry.storage.impl.kafkasql.messages.UpdateArtifactVersionContent5Message; import io.apicurio.registry.storage.impl.kafkasql.messages.UpdateArtifactVersionMetaData4Message; +import io.apicurio.registry.storage.impl.kafkasql.messages.UpdateArtifactVersionState5Message; import io.apicurio.registry.storage.impl.kafkasql.messages.UpdateBranchMetaData3Message; import io.apicurio.registry.storage.impl.kafkasql.messages.UpdateContentCanonicalHash3Message; import io.apicurio.registry.storage.impl.kafkasql.messages.UpdateGlobalRule2Message; @@ -106,6 +108,7 @@ private static void indexMessageClasses(Class... mcla UpdateArtifactVersionMetaData4Message.class, UpdateBranchMetaData3Message.class, UpdateContentCanonicalHash3Message.class, UpdateGlobalRule2Message.class, UpdateGroupMetaData2Message.class, UpdateRoleMapping2Message.class, + UpdateArtifactVersionState5Message.class, UpdateArtifactVersionContent5Message.class, UpdateGroupRule3Message.class, DeleteGroupRule2Message.class, DeleteGroupRules1Message.class, ImportGroupRule1Message.class, ExecuteSqlStatement1Message.class); } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java index 07a6eb2ecd..bb9a195c71 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java @@ -647,8 +647,12 @@ private ArtifactVersionMetaDataDto createArtifactVersionRaw(Handle handle, boole } // Update system generated branches - createOrUpdateBranchRaw(handle, gav, BranchId.LATEST, true); - createOrUpdateSemverBranchesRaw(handle, gav); + if (isDraft) { + createOrUpdateBranchRaw(handle, gav, BranchId.DRAFTS, true); + } else { + createOrUpdateBranchRaw(handle, gav, BranchId.LATEST, true); + createOrUpdateSemverBranchesRaw(handle, gav); + } // Create any user defined branches if (branches != null && !branches.isEmpty()) { @@ -2046,20 +2050,29 @@ public void updateArtifactVersionState(String groupId, String artifactId, String handle.setRollback(true); } - int rowCount = handle.createUpdate(sqlStatements.updateArtifactVersionStateByGAV()) - .bind(0, newState.name()).bind(1, normalizeGroupId(groupId)).bind(2, artifactId) - .bind(3, version).execute(); - if (rowCount == 0) { - throw new VersionNotFoundException(groupId, artifactId, version); - } + Optional res = handle + .createQuery(sqlStatements.selectArtifactVersionStateForUpdate()) + .bind(0, normalizeGroupId(groupId)).bind(1, artifactId).bind(2, version) + .map(VersionStateMapper.instance).findOne(); + VersionState currentState = res + .orElseThrow(() -> new VersionNotFoundException(groupId, artifactId, version)); + + handle.createUpdate(sqlStatements.updateArtifactVersionStateByGAV()).bind(0, newState.name()) + .bind(1, normalizeGroupId(groupId)).bind(2, artifactId).bind(3, version).execute(); + String modifiedBy = securityIdentity.getPrincipal().getName(); Date modifiedOn = new Date(); + handle.createUpdate(sqlStatements.updateArtifactVersionModifiedByOn()).bind(0, modifiedBy) + .bind(1, modifiedOn).bind(2, normalizeGroupId(groupId)).bind(3, artifactId) + .bind(4, version).execute(); - rowCount = handle.createUpdate(sqlStatements.updateArtifactVersionModifiedByOn()) - .bind(0, modifiedBy).bind(1, modifiedOn).bind(2, normalizeGroupId(groupId)) - .bind(3, artifactId).bind(4, version).execute(); - if (rowCount == 0) { - throw new VersionNotFoundException(groupId, artifactId, version); + // If transitioning from DRAFT state to something else, then we need to maintain + // the system branches. + if (currentState == VersionState.DRAFT) { + GAV gav = new GAV(groupId, artifactId, version); + createOrUpdateBranchRaw(handle, gav, BranchId.LATEST, true); + createOrUpdateSemverBranchesRaw(handle, gav); + removeVersionFromBranchRaw(handle, gav, BranchId.DRAFTS); } return null; @@ -3633,6 +3646,19 @@ private void createOrUpdateBranchRaw(Handle handle, GAV gav, BranchId branchId, appendVersionToBranchRaw(handle, gav, branchId, gav.getVersionId()); } + /** + * Removes a version from the given branch. + * + * @param handle + * @param gav + * @param branchId + */ + private void removeVersionFromBranchRaw(Handle handle, GAV gav, BranchId branchId) { + handle.createUpdate(sqlStatements.deleteVersionFromBranch()).bind(0, gav.getRawGroupIdWithNull()) + .bind(1, gav.getRawArtifactId()).bind(2, branchId.getRawBranchId()) + .bind(3, gav.getRawVersionId()).execute(); + } + private void updateBranchModifiedTimeRaw(Handle handle, GA ga, BranchId branchId) { String user = securityIdentity.getPrincipal().getName(); Date now = new Date(); diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java index 602a36f0c2..2900d756ba 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java @@ -182,6 +182,12 @@ public String selectArtifactVersionState() { + "WHERE v.groupId = ? AND v.artifactId = ? AND v.version = ?"; } + @Override + public String selectArtifactVersionStateForUpdate() { + return "SELECT v.state FROM versions v " + + "WHERE v.groupId = ? AND v.artifactId = ? AND v.version = ? FOR UPDATE"; + } + /** * @see io.apicurio.registry.storage.impl.sql.SqlStatements#selectArtifactVersionMetaDataByContentHash() */ @@ -1142,12 +1148,17 @@ public String appendBranchVersion() { @Override public String deleteBranchVersions() { - return "DELETE FROM branch_versions " + "WHERE groupId = ? AND artifactId = ? AND branchId = ?"; + return "DELETE FROM branch_versions WHERE groupId = ? AND artifactId = ? AND branchId = ?"; + } + + @Override + public String deleteVersionFromBranch() { + return "DELETE FROM branch_versions WHERE groupId = ? AND artifactId = ? AND branchId = ? AND version = ?"; } @Override public String deleteBranch() { - return "DELETE FROM branches " + "WHERE groupId = ? AND artifactId = ? AND branchId = ?"; + return "DELETE FROM branches WHERE groupId = ? AND artifactId = ? AND branchId = ?"; } @Override diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SQLServerSqlStatements.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SQLServerSqlStatements.java index e65cd0dcaf..39dad36cdc 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SQLServerSqlStatements.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SQLServerSqlStatements.java @@ -129,6 +129,12 @@ public String deleteAllOrphanedContent() { return "DELETE FROM content WHERE NOT EXISTS (SELECT 1 FROM versions v WHERE v.contentId = contentId )"; } + @Override + public String selectArtifactVersionStateForUpdate() { + return "SELECT v.state FROM versions v WITH (UPDLOCK, ROWLOCK)" + + "WHERE v.groupId = ? AND v.artifactId = ? AND v.version = ?"; + } + @Override public String createDataSnapshot() { throw new IllegalStateException("Snapshot creation is not supported for Sqlserver storage"); diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java index 9b34374b32..ea3425ec8e 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java @@ -455,6 +455,11 @@ public interface SqlStatements { */ public String selectArtifactVersionState(); + /** + * A statement used to select the state of a version. + */ + public String selectArtifactVersionStateForUpdate(); + /* * The next few statements support globalId and contentId management. */ @@ -631,11 +636,18 @@ public interface SqlStatements { public String deleteAllBranches(); + public String deleteVersionFromBranch(); + + // ========== Snapshots ========== + public String createDataSnapshot(); public String restoreFromSnapshot(); - String createOutboxEvent(); + // ========== Events ========== + + public String createOutboxEvent(); + + public String deleteOutboxEvent(); - String deleteOutboxEvent(); } diff --git a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java index 27c2024274..c6a7aca677 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java @@ -324,4 +324,55 @@ public void testCreateInvalidDraftVersion() throws Exception { Assertions.assertEquals("Syntax violation for Avro artifact.", error.getTitle()); } + @Test + public void testDraftVersionsWithBranches() throws Exception { + String content = resourceToString("openapi-empty.json"); + String groupId = TestUtils.generateGroupId(); + String artifactId = TestUtils.generateArtifactId(); + + // First version is ENABLED + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, + content, ContentTypes.APPLICATION_JSON); + createArtifact.getFirstVersion().setIsDraft(false); + createArtifact.getFirstVersion().setVersion("1.0.0"); + + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + + VersionSearchResults latestBranch = clientV3.groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).branches().byBranchId("latest").versions().get(); + Assertions.assertEquals(1, latestBranch.getVersions().size()); + ProblemDetails problemDetails = Assertions.assertThrows(ProblemDetails.class, () -> { + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).branches() + .byBranchId("drafts").versions().get(); + }); + Assertions.assertEquals("BranchNotFoundException", problemDetails.getName()); + + // Second version is DRAFT + CreateVersion createVersion = TestUtils.clientCreateVersion(content, ContentTypes.APPLICATION_JSON); + createVersion.setVersion("1.0.1"); + createVersion.setIsDraft(true); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() + .post(createVersion); + + latestBranch = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).branches() + .byBranchId("latest").versions().get(); + Assertions.assertEquals(1, latestBranch.getVersions().size()); + VersionSearchResults draftsBranch = clientV3.groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).branches().byBranchId("drafts").versions().get(); + Assertions.assertEquals(1, draftsBranch.getVersions().size()); + + // Transition draft content to enabled + WrappedVersionState enabled = new WrappedVersionState(); + enabled.setState(VersionState.ENABLED); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() + .byVersionExpression("1.0.1").state().put(enabled); + + latestBranch = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).branches() + .byBranchId("latest").versions().get(); + Assertions.assertEquals(2, latestBranch.getVersions().size()); + draftsBranch = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).branches() + .byBranchId("drafts").versions().get(); + Assertions.assertEquals(0, draftsBranch.getVersions().size()); + } + } diff --git a/common/src/main/java/io/apicurio/registry/model/BranchId.java b/common/src/main/java/io/apicurio/registry/model/BranchId.java index d0b1ced7ca..a5e7b72ecb 100644 --- a/common/src/main/java/io/apicurio/registry/model/BranchId.java +++ b/common/src/main/java/io/apicurio/registry/model/BranchId.java @@ -17,6 +17,7 @@ public final class BranchId { private static final Pattern VALID_PATTERN = Pattern.compile("[a-zA-Z0-9._\\-+]{1,256}"); public static final BranchId LATEST = new BranchId("latest"); + public static final BranchId DRAFTS = new BranchId("drafts"); private final String rawBranchId; From a8621ffd2293c24d90069c1373f0c6af3acf8272 Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Tue, 15 Oct 2024 13:52:30 -0400 Subject: [PATCH 10/12] Ensure that ccompat and v2 APIs do not return DRAFT content --- .../io/apicurio/registry/auth/AuthConfig.java | 8 +- .../rest/v7/impl/AbstractResource.java | 2 +- .../v7/impl/SubjectVersionsResourceImpl.java | 9 +- .../io/apicurio/registry/rest/RestConfig.java | 4 +- .../registry/rest/v2/GroupsResourceImpl.java | 34 +++++++- .../registry/rest/v3/GroupsResourceImpl.java | 31 +++---- .../registry/storage/RegistryStorage.java | 50 ++++++++--- .../storage/StorageBehaviorProperties.java | 9 +- .../RegistryStorageDecoratorReadOnlyBase.java | 4 +- .../impl/gitops/GitOpsRegistryStorage.java | 4 +- .../impl/sql/AbstractHandleFactory.java | 4 +- .../impl/sql/AbstractSqlRegistryStorage.java | 64 ++++++-------- .../storage/impl/sql/CommonSqlStatements.java | 10 +-- .../impl/sql/SQLServerSqlStatements.java | 4 +- .../storage/impl/sql/SqlStatements.java | 4 +- .../noprofile/rest/v3/DraftContentTest.java | 85 +++++++++++++++++++ .../noprofile/rest/v3/GroupsResourceTest.java | 2 +- .../storage/RegistryStorageSmokeTest.java | 2 +- .../readonly/ReadOnlyRegistryStorageTest.java | 6 +- 19 files changed, 231 insertions(+), 105 deletions(-) diff --git a/app/src/main/java/io/apicurio/registry/auth/AuthConfig.java b/app/src/main/java/io/apicurio/registry/auth/AuthConfig.java index b9019a20c3..b84e179210 100644 --- a/app/src/main/java/io/apicurio/registry/auth/AuthConfig.java +++ b/app/src/main/java/io/apicurio/registry/auth/AuthConfig.java @@ -17,16 +17,16 @@ public class AuthConfig { Logger log; @ConfigProperty(name = "quarkus.oidc.tenant-enabled", defaultValue = "false") - @Info(category = "auth", description = "Enable auth", availableSince = "0.1.18-SNAPSHOT", registryAvailableSince = "2.0.0.Final", studioAvailableSince = "1.0.0") + @Info(category = "auth", description = "Enable auth", availableSince = "0.1.18", registryAvailableSince = "2.0.0.Final", studioAvailableSince = "1.0.0") boolean oidcAuthEnabled; @Dynamic(label = "HTTP basic authentication", description = "When selected, users are permitted to authenticate using HTTP basic authentication (in addition to OAuth).", requires = "apicurio.authn.enabled=true") @ConfigProperty(name = "apicurio.authn.basic-client-credentials.enabled", defaultValue = "false") - @Info(category = "auth", description = "Enable basic auth client credentials", availableSince = "0.1.18-SNAPSHOT", registryAvailableSince = "2.1.0.Final", studioAvailableSince = "1.0.0") + @Info(category = "auth", description = "Enable basic auth client credentials", availableSince = "0.1.18", registryAvailableSince = "2.1.0.Final", studioAvailableSince = "1.0.0") Supplier basicClientCredentialsAuthEnabled; @ConfigProperty(name = "quarkus.http.auth.basic", defaultValue = "false") - @Info(category = "auth", description = "Enable basic auth", availableSince = "1.1.X-SNAPSHOT", registryAvailableSince = "3.X.X.Final", studioAvailableSince = "1.0.0") + @Info(category = "auth", description = "Enable basic auth", availableSince = "1.1.x", registryAvailableSince = "3.0.0", studioAvailableSince = "1.0.0") boolean basicAuthEnabled; @ConfigProperty(name = "apicurio.auth.role-based-authorization", defaultValue = "false") @@ -96,7 +96,7 @@ public class AuthConfig { String adminOverrideClaimValue; @ConfigProperty(name = "apicurio.auth.admin-override.user", defaultValue = "admin") - @Info(category = "auth", description = "Auth admin override user name", availableSince = "3.0.0.Final") + @Info(category = "auth", description = "Auth admin override user name", availableSince = "3.0.0") String adminOverrideUser; @PostConstruct diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java index c384002036..e6be4a4f5b 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java @@ -238,7 +238,7 @@ protected boolean isArtifactActive(String artifactId, String groupId) { protected String getLatestArtifactVersionForSubject(String artifactId, String groupId) { try { GAV latestGAV = storage.getBranchTip(new GA(groupId, artifactId), BranchId.LATEST, - RetrievalBehavior.SKIP_DISABLED_LATEST); + RetrievalBehavior.ACTIVE_STATES); return latestGAV.getRawVersionId(); } catch (ArtifactNotFoundException ex) { throw new VersionNotFoundException(groupId, artifactId, "latest"); diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java index a106773c5d..1a73f59170 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java @@ -19,6 +19,7 @@ import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.model.GA; +import io.apicurio.registry.storage.RegistryStorage.RetrievalBehavior; import io.apicurio.registry.storage.dto.ArtifactVersionMetaDataDto; import io.apicurio.registry.storage.dto.StoredArtifactVersionDto; import io.apicurio.registry.storage.error.ArtifactNotFoundException; @@ -37,8 +38,6 @@ import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_ARTIFACT_ID; import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_VERSION; -import static io.apicurio.registry.storage.RegistryStorage.RetrievalBehavior.DEFAULT; -import static io.apicurio.registry.storage.RegistryStorage.RetrievalBehavior.SKIP_DISABLED_LATEST; @Interceptors({ ResponseErrorLivenessCheck.class, ResponseTimeoutReadinessCheck.class }) @Logged @@ -55,13 +54,15 @@ public List listVersions(String subject, String groupId, Boolean delete List rval; if (fdeleted) { - rval = storage.getArtifactVersions(ga.getRawGroupIdWithNull(), ga.getRawArtifactId(), DEFAULT) + rval = storage + .getArtifactVersions(ga.getRawGroupIdWithNull(), ga.getRawArtifactId(), + RetrievalBehavior.NON_DRAFT_STATES) .stream().map(VersionUtil::toLong).map(converter::convertUnsigned).sorted() .collect(Collectors.toList()); } else { rval = storage .getArtifactVersions(ga.getRawGroupIdWithNull(), ga.getRawArtifactId(), - SKIP_DISABLED_LATEST) + RetrievalBehavior.ACTIVE_STATES) .stream().map(VersionUtil::toLong).map(converter::convertUnsigned).sorted() .collect(Collectors.toList()); } diff --git a/app/src/main/java/io/apicurio/registry/rest/RestConfig.java b/app/src/main/java/io/apicurio/registry/rest/RestConfig.java index 48dd2a1208..1761cc1fa7 100644 --- a/app/src/main/java/io/apicurio/registry/rest/RestConfig.java +++ b/app/src/main/java/io/apicurio/registry/rest/RestConfig.java @@ -11,11 +11,11 @@ public class RestConfig { @ConfigProperty(name = "apicurio.rest.artifact.download.max-size.bytes", defaultValue = "1000000") - @Info(category = "rest", description = "Max size of the artifact allowed to be downloaded from URL", availableSince = "2.2.6-SNAPSHOT") + @Info(category = "rest", description = "Max size of the artifact allowed to be downloaded from URL", availableSince = "2.2.6") int downloadMaxSize; @ConfigProperty(name = "apicurio.rest.artifact.download.ssl-validation.disabled", defaultValue = "false") - @Info(category = "rest", description = "Skip SSL validation when downloading artifacts from URL", availableSince = "2.2.6-SNAPSHOT") + @Info(category = "rest", description = "Skip SSL validation when downloading artifacts from URL", availableSince = "2.2.6") boolean downloadSkipSSLValidation; @Dynamic(label = "Delete group", description = "When selected, users are permitted to delete groups.") diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java index 99e856baae..eafab333ab 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java @@ -16,6 +16,7 @@ import io.apicurio.registry.model.BranchId; import io.apicurio.registry.model.GA; import io.apicurio.registry.model.GAV; +import io.apicurio.registry.model.VersionExpressionParser; import io.apicurio.registry.rest.HeadersHack; import io.apicurio.registry.rest.MissingRequiredParameterException; import io.apicurio.registry.rest.ParametersConflictException; @@ -182,7 +183,7 @@ public Response getLatestArtifact(String groupId, String artifactId, Boolean der try { GAV latestGAV = storage.getBranchTip(new GA(groupId, artifactId), BranchId.LATEST, - RetrievalBehavior.SKIP_DISABLED_LATEST); + RetrievalBehavior.ACTIVE_STATES); ArtifactVersionMetaDataDto metaData = storage.getArtifactVersionMetaData( latestGAV.getRawGroupIdWithNull(), latestGAV.getRawArtifactId(), latestGAV.getRawVersionId()); @@ -261,6 +262,13 @@ public ArtifactMetaData updateArtifact(String groupId, String artifactId, String @Override public List getArtifactVersionReferences(String groupId, String artifactId, String version, ReferenceType refType) { + + if ("latest".equals(version)) { + var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), "branch=latest", + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.ALL_STATES)); + version = gav.getRawVersionId(); + } + if (refType == null || refType == ReferenceType.OUTBOUND) { return storage.getArtifactVersionContent(defaultGroupIdToNull(groupId), artifactId, version) .getReferences().stream().map(V2ApiUtil::referenceDtoToReference) @@ -324,7 +332,7 @@ public ArtifactMetaData getArtifactMetaData(String groupId, String artifactId) { ArtifactMetaDataDto dto = storage.getArtifactMetaData(defaultGroupIdToNull(groupId), artifactId); GAV latestGAV = storage.getBranchTip(new GA(groupId, artifactId), BranchId.LATEST, - RetrievalBehavior.SKIP_DISABLED_LATEST); + RetrievalBehavior.ACTIVE_STATES); ArtifactVersionMetaDataDto vdto = storage.getArtifactVersionMetaData( latestGAV.getRawGroupIdWithNull(), latestGAV.getRawArtifactId(), latestGAV.getRawVersionId()); @@ -352,7 +360,7 @@ public ArtifactMetaData getArtifactMetaData(String groupId, String artifactId) { @Authorized(style = AuthorizedStyle.GroupAndArtifact, level = AuthorizedLevel.Write) public void updateArtifactMetaData(String groupId, String artifactId, EditableMetaData data) { GAV latestGAV = storage.getBranchTip(new GA(groupId, artifactId), BranchId.LATEST, - RetrievalBehavior.DEFAULT); + RetrievalBehavior.ALL_STATES); storage.updateArtifactVersionMetaData(groupId, artifactId, latestGAV.getRawVersionId(), EditableVersionMetaDataDto.builder().name(data.getName()).description(data.getDescription()) .labels(V2ApiUtil.toV3Labels(data.getLabels(), data.getProperties())).build()); @@ -614,7 +622,7 @@ public void updateArtifactState(String groupId, String artifactId, UpdateState d // Possible race condition here. Worst case should be that the update fails with a reasonable message. GAV latestGAV = storage.getBranchTip(new GA(defaultGroupIdToNull(groupId), artifactId), - BranchId.LATEST, RetrievalBehavior.DEFAULT); + BranchId.LATEST, RetrievalBehavior.ALL_STATES); updateArtifactVersionState(groupId, artifactId, latestGAV.getRawVersionId(), data); } @@ -662,6 +670,12 @@ public Response getArtifactVersion(String groupId, String artifactId, String ver dereference = Boolean.FALSE; } + if ("latest".equals(version)) { + var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), "branch=latest", + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.ALL_STATES)); + version = gav.getRawVersionId(); + } + ArtifactVersionMetaDataDto metaData = storage .getArtifactVersionMetaData(defaultGroupIdToNull(groupId), artifactId, version); if (VersionState.DISABLED.equals(metaData.getState())) { @@ -726,6 +740,12 @@ public VersionMetaData getArtifactVersionMetaData(String groupId, String artifac requireParameter("artifactId", artifactId); requireParameter("version", version); + if ("latest".equals(version)) { + var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), "branch=latest", + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.ALL_STATES)); + version = gav.getRawVersionId(); + } + ArtifactVersionMetaDataDto dto = storage.getArtifactVersionMetaData(defaultGroupIdToNull(groupId), artifactId, version); return V2ApiUtil.dtoToVersionMetaData(defaultGroupIdToNull(groupId), artifactId, @@ -813,6 +833,12 @@ public List getArtifactVersionComments(String groupId, String artifactI requireParameter("artifactId", artifactId); requireParameter("version", version); + if ("latest".equals(version)) { + var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), "branch=latest", + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.ALL_STATES)); + version = gav.getRawVersionId(); + } + return storage.getArtifactVersionComments(defaultGroupIdToNull(groupId), artifactId, version).stream() .map(V2ApiUtil::commentDtoToComment).collect(Collectors.toList()); } diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java index 0cffd54a2e..49fc658ee9 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java @@ -160,7 +160,7 @@ public List getArtifactVersionReferences(String groupId, Stri String versionExpression, ReferenceType refType) { var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), versionExpression, - (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.DEFAULT)); + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.ALL_STATES)); if (refType == null || refType == ReferenceType.OUTBOUND) { return storage @@ -540,9 +540,9 @@ public void updateArtifactVersionContent(String groupId, String artifactId, Stri HttpMethod.GET, (String[]) null); } - // Resolve the GAV info + // Resolve the GAV info (only look for DRAFT versions) var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), versionExpression, - (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.SKIP_DISABLED_LATEST)); + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.ALL_STATES)); // Ensure the artifact version is in DRAFT status ArtifactVersionMetaDataDto vmd = storage.getArtifactVersionMetaData(gav.getRawGroupIdWithNull(), @@ -586,7 +586,7 @@ public void deleteArtifactVersion(String groupId, String artifactId, String vers requireParameter("version", version); var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), version, - (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.DEFAULT)); + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.ALL_STATES)); storage.deleteArtifactVersion(gav.getRawGroupIdWithNull(), gav.getRawArtifactId(), gav.getRawVersionId()); @@ -626,7 +626,7 @@ public void updateArtifactVersionMetaData(String groupId, String artifactId, Str requireParameter("versionExpression", versionExpression); var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), versionExpression, - (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.DEFAULT)); + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.SKIP_DISABLED_LATEST)); EditableVersionMetaDataDto dto = new EditableVersionMetaDataDto(); dto.setName(data.getName()); @@ -645,7 +645,7 @@ public WrappedVersionState getArtifactVersionState(String groupId, String artifa requireParameter("version", versionExpression); var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), versionExpression, - (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.DEFAULT)); + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.ALL_STATES)); VersionState state = storage.getArtifactVersionState(gav.getRawGroupIdWithNull(), gav.getRawArtifactId(), gav.getRawVersionId()); @@ -667,7 +667,7 @@ public void updateArtifactVersionState(String groupId, String artifactId, String } var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), versionExpression, - (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.DEFAULT)); + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.ALL_STATES)); // Get current state. VersionState currentState = storage.getArtifactVersionState(gav.getRawGroupIdWithNull(), @@ -680,7 +680,7 @@ public void updateArtifactVersionState(String groupId, String artifactId, String // If the current state is DRAFT, apply rules. if (currentState == VersionState.DRAFT) { - VersionMetaData vmd = getArtifactVersionMetaData(gav.getRawGroupIdWithNull(), + VersionMetaData vmd = getArtifactVersionMetaData(gav.getRawGroupIdWithDefaultString(), gav.getRawArtifactId(), gav.getRawVersionId()); StoredArtifactVersionDto artifact = storage.getArtifactVersionContent(gav.getRawGroupIdWithNull(), gav.getRawArtifactId(), gav.getRawVersionId()); @@ -714,7 +714,7 @@ public Comment addArtifactVersionComment(String groupId, String artifactId, Stri requireParameter("versionExpression", versionExpression); var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), versionExpression, - (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.DEFAULT)); + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.ALL_STATES)); CommentDto newComment = storage.createArtifactVersionComment(gav.getRawGroupIdWithNull(), gav.getRawArtifactId(), gav.getRawVersionId(), data.getValue()); @@ -737,7 +737,7 @@ public void deleteArtifactVersionComment(String groupId, String artifactId, Stri requireParameter("commentId", commentId); var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), versionExpression, - (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.DEFAULT)); + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.ALL_STATES)); storage.deleteArtifactVersionComment(gav.getRawGroupIdWithNull(), gav.getRawArtifactId(), gav.getRawVersionId(), commentId); @@ -755,7 +755,7 @@ public List getArtifactVersionComments(String groupId, String artifactI requireParameter("version", version); var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), version, - (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.DEFAULT)); + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.ALL_STATES)); return storage.getArtifactVersionComments(gav.getRawGroupIdWithNull(), gav.getRawArtifactId(), gav.getRawVersionId()).stream().map(V3ApiUtil::commentDtoToComment).collect(toList()); @@ -779,7 +779,7 @@ public void updateArtifactVersionComment(String groupId, String artifactId, Stri requireParameter("value", data.getValue()); var gav = VersionExpressionParser.parse(new GA(groupId, artifactId), versionExpression, - (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.DEFAULT)); + (ga, branchId) -> storage.getBranchTip(ga, branchId, RetrievalBehavior.ALL_STATES)); storage.updateArtifactVersionComment(gav.getRawGroupIdWithNull(), gav.getRawArtifactId(), gav.getRawVersionId(), commentId, data.getValue()); @@ -1227,13 +1227,6 @@ private CreateArtifactResponse handleIfExists(String groupId, String artifactId, switch (ifExists) { case CREATE_VERSION: return updateArtifactInternal(groupId, artifactId, theVersion); - // case RETURN: - // GAV latestGAV = storage.getBranchTip(new GA(groupId, artifactId), BranchId.LATEST, - // ArtifactRetrievalBehavior.DEFAULT); - // ArtifactVersionMetaDataDto latestVersionMD = - // storage.getArtifactVersionMetaData(latestGAV.getRawGroupIdWithNull(), - // latestGAV.getRawArtifactId(), latestGAV.getRawVersionId()); - // return V3ApiUtil.dtoToVersionMetaData(latestVersionMD); case FIND_OR_CREATE_VERSION: return handleIfExistsReturnOrUpdate(groupId, artifactId, theVersion, canonical); default: diff --git a/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java index e3e359c25b..713ed7e25c 100644 --- a/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java @@ -7,7 +7,30 @@ import io.apicurio.registry.model.GA; import io.apicurio.registry.model.GAV; import io.apicurio.registry.model.VersionId; -import io.apicurio.registry.storage.dto.*; +import io.apicurio.registry.storage.dto.ArtifactMetaDataDto; +import io.apicurio.registry.storage.dto.ArtifactReferenceDto; +import io.apicurio.registry.storage.dto.ArtifactSearchResultsDto; +import io.apicurio.registry.storage.dto.ArtifactVersionMetaDataDto; +import io.apicurio.registry.storage.dto.BranchMetaDataDto; +import io.apicurio.registry.storage.dto.BranchSearchResultsDto; +import io.apicurio.registry.storage.dto.CommentDto; +import io.apicurio.registry.storage.dto.ContentWrapperDto; +import io.apicurio.registry.storage.dto.DownloadContextDto; +import io.apicurio.registry.storage.dto.EditableArtifactMetaDataDto; +import io.apicurio.registry.storage.dto.EditableBranchMetaDataDto; +import io.apicurio.registry.storage.dto.EditableGroupMetaDataDto; +import io.apicurio.registry.storage.dto.EditableVersionMetaDataDto; +import io.apicurio.registry.storage.dto.GroupMetaDataDto; +import io.apicurio.registry.storage.dto.GroupSearchResultsDto; +import io.apicurio.registry.storage.dto.OrderBy; +import io.apicurio.registry.storage.dto.OrderDirection; +import io.apicurio.registry.storage.dto.OutboxEvent; +import io.apicurio.registry.storage.dto.RoleMappingDto; +import io.apicurio.registry.storage.dto.RoleMappingSearchResultsDto; +import io.apicurio.registry.storage.dto.RuleConfigurationDto; +import io.apicurio.registry.storage.dto.SearchFilter; +import io.apicurio.registry.storage.dto.StoredArtifactVersionDto; +import io.apicurio.registry.storage.dto.VersionSearchResultsDto; import io.apicurio.registry.storage.error.ArtifactAlreadyExistsException; import io.apicurio.registry.storage.error.ArtifactNotFoundException; import io.apicurio.registry.storage.error.ContentNotFoundException; @@ -395,7 +418,7 @@ List getArtifactVersions(String groupId, String artifactId) * @throws ArtifactNotFoundException * @throws RegistryStorageException */ - List getArtifactVersions(String groupId, String artifactId, RetrievalBehavior behavior) + List getArtifactVersions(String groupId, String artifactId, Set filterBy) throws ArtifactNotFoundException, RegistryStorageException; /** @@ -958,7 +981,7 @@ boolean isArtifactRuleExists(String groupId, String artifactId, RuleType rule) void deleteBranch(GA ga, BranchId branchId); - GAV getBranchTip(GA ga, BranchId branchId, RetrievalBehavior behavior); + GAV getBranchTip(GA ga, BranchId branchId, Set filterBy); VersionSearchResultsDto getBranchVersions(GA ga, BranchId branchId, int offset, int limit); @@ -995,12 +1018,19 @@ boolean isArtifactRuleExists(String groupId, String artifactId, RuleType rule) */ boolean supportsDatabaseEvents(); - enum RetrievalBehavior { - DEFAULT, - /** - * Skip artifact versions with DISABLED state - */ - SKIP_DISABLED_LATEST + /** + * Legacy code: we used to have an enum that drove how to retrieve versions. This has since been converted + * to a filtered set of states. For now, this class replicates the names of the old enum values, aiding in + * converting existing code with fewer changes. + */ + class RetrievalBehavior { + public static final Set SKIP_DISABLED_LATEST = Set.of(VersionState.ENABLED, + VersionState.DEPRECATED, VersionState.DRAFT); + public static final Set ALL_STATES = Set.of(); // Note: empty set means just include + // everything (no filtering) + public static final Set ACTIVE_STATES = Set.of(VersionState.ENABLED, + VersionState.DEPRECATED); + public static final Set NON_DRAFT_STATES = Set.of(VersionState.ENABLED, + VersionState.DEPRECATED, VersionState.DISABLED); } - } diff --git a/app/src/main/java/io/apicurio/registry/storage/StorageBehaviorProperties.java b/app/src/main/java/io/apicurio/registry/storage/StorageBehaviorProperties.java index e3dbafeb2e..2d82035c19 100644 --- a/app/src/main/java/io/apicurio/registry/storage/StorageBehaviorProperties.java +++ b/app/src/main/java/io/apicurio/registry/storage/StorageBehaviorProperties.java @@ -2,21 +2,24 @@ import io.apicurio.common.apps.config.Info; import io.apicurio.registry.storage.RegistryStorage.RetrievalBehavior; +import io.apicurio.registry.types.VersionState; import jakarta.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.config.inject.ConfigProperty; +import java.util.Set; + @ApplicationScoped public class StorageBehaviorProperties { @ConfigProperty(name = "artifacts.skip.disabled.latest", defaultValue = "true") - @Info(category = "storage", description = "Skip artifact versions with DISABLED state when retrieving latest artifact version", availableSince = "2.4.2-SNAPSHOT") + @Info(category = "storage", description = "Skip artifact versions with DISABLED state when retrieving latest artifact version", availableSince = "2.4.2") boolean skipLatestDisabledArtifacts; - public RetrievalBehavior getDefaultArtifactRetrievalBehavior() { + public Set getDefaultArtifactRetrievalBehavior() { if (skipLatestDisabledArtifacts) { return RetrievalBehavior.SKIP_DISABLED_LATEST; } else { - return RetrievalBehavior.DEFAULT; + return RetrievalBehavior.ALL_STATES; } } } diff --git a/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorReadOnlyBase.java b/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorReadOnlyBase.java index 7e7f074724..97694f08c9 100644 --- a/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorReadOnlyBase.java +++ b/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorReadOnlyBase.java @@ -364,13 +364,13 @@ public List getEnabledArtifactContentIds(String groupId, String artifactId } @Override - public List getArtifactVersions(String groupId, String artifactId, RetrievalBehavior behavior) + public List getArtifactVersions(String groupId, String artifactId, Set behavior) throws ArtifactNotFoundException, RegistryStorageException { return delegate.getArtifactVersions(groupId, artifactId, behavior); } @Override - public GAV getBranchTip(GA ga, BranchId branchId, RetrievalBehavior behavior) { + public GAV getBranchTip(GA ga, BranchId branchId, Set behavior) { return delegate.getBranchTip(ga, branchId, behavior); } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java index 0579c7dcee..65a8965fc7 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java @@ -271,7 +271,7 @@ public List getArtifactVersions(String groupId, String artifactId) { } @Override - public List getArtifactVersions(String groupId, String artifactId, RetrievalBehavior behavior) { + public List getArtifactVersions(String groupId, String artifactId, Set behavior) { return proxy(storage -> storage.getArtifactVersions(groupId, artifactId, behavior)); } @@ -485,7 +485,7 @@ public VersionSearchResultsDto getBranchVersions(GA ga, BranchId branchId, int o } @Override - public GAV getBranchTip(GA ga, BranchId branchId, RetrievalBehavior behavior) { + public GAV getBranchTip(GA ga, BranchId branchId, Set behavior) { return proxy(storage -> storage.getBranchTip(ga, branchId, behavior)); } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractHandleFactory.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractHandleFactory.java index fc20d8b35f..d593a5ceec 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractHandleFactory.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractHandleFactory.java @@ -45,7 +45,9 @@ public R withHandle(HandleCallback callback) thro return callback.withHandle(state.handle); } catch (SQLException e) { // If a SQL exception is thrown, set the handle to rollback. - state.handle.setRollback(true); + if (state.handle != null) { + state.handle.setRollback(true); + } // Wrap the SQL exception. throw new RegistryStorageException(e); } catch (Exception e) { diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java index bb9a195c71..de31495217 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java @@ -157,6 +157,7 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.Stream; import static io.apicurio.registry.storage.impl.sql.RegistryContentUtils.normalizeGroupId; @@ -1538,31 +1539,20 @@ public List getArtifactVersions(String groupId, String artifactId) } @Override - public List getArtifactVersions(String groupId, String artifactId, RetrievalBehavior behavior) + public List getArtifactVersions(String groupId, String artifactId, Set filterBy) throws ArtifactNotFoundException, RegistryStorageException { log.debug("Getting a list of versions for artifact: {} {}", groupId, artifactId); - try { - List versions = handles.withHandle(handle -> { - switch (behavior) { - case DEFAULT -> { - return getArtifactVersionsRaw(handle, groupId, artifactId, - sqlStatements.selectArtifactVersions()); - } - case SKIP_DISABLED_LATEST -> { - return getArtifactVersionsRaw(handle, groupId, artifactId, - sqlStatements.selectArtifactVersionsNotDisabled()); - } - } - return null; - }); - if (versions != null) { - return versions; + return handles.withHandle(handle -> { + String sql = sqlStatements.selectArtifactVersions(); + if (filterBy != null && !filterBy.isEmpty()) { + sql = sqlStatements.selectArtifactVersionsFilteredByState(); + String jclause = filterBy.stream().map(vs -> "'" + vs.name() + "'") + .collect(Collectors.joining(",", "(", ")")); + sql = sql.replace("(?)", jclause); } - } catch (BranchNotFoundException ex) { - throw new ArtifactNotFoundException(groupId, artifactId); - } - throw new UnsupportedOperationException("Retrieval behavior not implemented: " + behavior.name()); + return getArtifactVersionsRaw(handle, groupId, artifactId, sql); + }); } private List getArtifactVersionsRaw(Handle handle, String groupId, String artifactId, @@ -3669,26 +3659,20 @@ private void updateBranchModifiedTimeRaw(Handle handle, GA ga, BranchId branchId } @Override - public GAV getBranchTip(GA ga, BranchId branchId, RetrievalBehavior behavior) { + public GAV getBranchTip(GA ga, BranchId branchId, Set filterBy) { return handles.withHandleNoException(handle -> { - switch (behavior) { - case DEFAULT: - return handle.createQuery(sqlStatements.selectBranchTip()).bind(0, ga.getRawGroupId()) - .bind(1, ga.getRawArtifactId()).bind(2, branchId.getRawBranchId()) - .map(GAVMapper.instance).findOne() - .orElseThrow(() -> new VersionNotFoundException( - ga.getRawGroupIdWithDefaultString(), ga.getRawArtifactId(), - "")); - case SKIP_DISABLED_LATEST: - return handle.createQuery(sqlStatements.selectBranchTipNotDisabled()) - .bind(0, ga.getRawGroupId()).bind(1, ga.getRawArtifactId()) - .bind(2, branchId.getRawBranchId()).map(GAVMapper.instance).findOne() - .orElseThrow(() -> new VersionNotFoundException( - ga.getRawGroupIdWithDefaultString(), ga.getRawArtifactId(), - "")); - } - throw new UnreachableCodeException(); + String sql = sqlStatements.selectBranchTip(); + if (filterBy != null && !filterBy.isEmpty()) { + sql = sqlStatements.selectBranchTipFilteredByState(); + String jclause = filterBy.stream().map(vs -> "'" + vs.name() + "'") + .collect(Collectors.joining(",", "(", ")")); + sql = sql.replace("(?)", jclause); + } + return handle.createQuery(sql).bind(0, ga.getRawGroupId()).bind(1, ga.getRawArtifactId()) + .bind(2, branchId.getRawBranchId()).map(GAVMapper.instance).findOne() + .orElseThrow(() -> new VersionNotFoundException(ga.getRawGroupIdWithDefaultString(), + ga.getRawArtifactId(), + "")); }); } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java index 2900d756ba..28546af1c7 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java @@ -162,8 +162,8 @@ public String selectArtifactVersions() { } @Override - public String selectArtifactVersionsNotDisabled() { - return "SELECT version FROM versions WHERE groupId = ? AND artifactId = ? AND state != 'DISABLED'"; + public String selectArtifactVersionsFilteredByState() { + return "SELECT version FROM versions WHERE groupId = ? AND artifactId = ? AND state IN (?)"; } /** @@ -1126,10 +1126,10 @@ public String selectBranchTip() { } @Override - public String selectBranchTipNotDisabled() { - return "SELECT bv.groupId, bv.artifactId, bv.version " + "FROM branch_versions bv " + public String selectBranchTipFilteredByState() { + return "SELECT bv.groupId, bv.artifactId, bv.version FROM branch_versions bv " + "JOIN versions v ON bv.groupId = v.groupId AND bv.artifactId = v.artifactId AND bv.version = v.version " - + "WHERE bv.groupId = ? AND bv.artifactId = ? AND bv.branchId = ? AND v.state != 'DISABLED' " + + "WHERE bv.groupId = ? AND bv.artifactId = ? AND bv.branchId = ? AND v.state IN (?) " + "ORDER BY bv.branchOrder DESC LIMIT 1"; } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SQLServerSqlStatements.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SQLServerSqlStatements.java index 39dad36cdc..3f2e0bd6f6 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SQLServerSqlStatements.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SQLServerSqlStatements.java @@ -117,10 +117,10 @@ public String selectBranchTip() { } @Override - public String selectBranchTipNotDisabled() { + public String selectBranchTipFilteredByState() { return "SELECT ab.groupId, ab.artifactId, ab.version FROM artifact_branches ab " + "JOIN versions v ON ab.groupId = v.groupId AND ab.artifactId = v.artifactId AND ab.version = v.version " - + "WHERE ab.groupId = ? AND ab.artifactId = ? AND ab.branchId = ? AND v.state != 'DISABLED' " + + "WHERE ab.groupId = ? AND ab.artifactId = ? AND ab.branchId = ? AND v.state IN (?) " + "ORDER BY ab.branchOrder DESC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY"; } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java index ea3425ec8e..f329a51d75 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java @@ -128,7 +128,7 @@ public interface SqlStatements { /** * A statement used to select non-disabled version numbers (only) for a given artifactId. */ - public String selectArtifactVersionsNotDisabled(); + public String selectArtifactVersionsFilteredByState(); /** * A statement used to select all versions for a given artifactId. @@ -622,7 +622,7 @@ public interface SqlStatements { public String selectBranchTip(); - public String selectBranchTipNotDisabled(); + public String selectBranchTipFilteredByState(); public String updateBranchModifiedTime(); diff --git a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java index c6a7aca677..69796eb4db 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java @@ -1,6 +1,8 @@ package io.apicurio.registry.noprofile.rest.v3; import io.apicurio.registry.AbstractResourceTestBase; +import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.model.GroupId; import io.apicurio.registry.rest.client.models.CreateArtifact; import io.apicurio.registry.rest.client.models.CreateArtifactResponse; import io.apicurio.registry.rest.client.models.CreateGroup; @@ -14,10 +16,13 @@ import io.apicurio.registry.rest.client.models.VersionState; import io.apicurio.registry.rest.client.models.WrappedVersionState; import io.apicurio.registry.rules.validity.ValidityLevel; +import io.apicurio.registry.storage.dto.ContentWrapperDto; +import io.apicurio.registry.storage.impl.sql.RegistryContentUtils; import io.apicurio.registry.types.ArtifactType; import io.apicurio.registry.types.ContentTypes; import io.apicurio.registry.utils.tests.MutabilityEnabledProfile; import io.apicurio.registry.utils.tests.TestUtils; +import io.confluent.kafka.schemaregistry.client.rest.exceptions.RestClientException; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; import org.apache.commons.io.IOUtils; @@ -26,6 +31,10 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.UUID; + +import static io.restassured.RestAssured.given; @QuarkusTest @TestProfile(MutabilityEnabledProfile.class) @@ -66,6 +75,8 @@ public class DraftContentTest extends AbstractResourceTestBase { @Test public void testCreateDraftArtifact() throws Exception { String content = resourceToString("openapi-empty.json"); + // Ensure the content is unique because we will do a contentHash check later in the test. + content = content.replace("Empty API", "Unique API: " + UUID.randomUUID().toString()); String groupId = TestUtils.generateGroupId(); String artifactId = TestUtils.generateArtifactId(); @@ -96,6 +107,14 @@ public void testCreateDraftArtifact() throws Exception { Assertions.assertThrows(ProblemDetails.class, () -> { clientV3.ids().contentIds().byContentId(contentId).get(); }); + + // Note: Should NOT be able to fetch its content by contentHash (disallowed for DRAFT content) + ContentWrapperDto contentWrapperDto = ContentWrapperDto.builder() + .content(ContentHandle.create(content)).contentType(ContentTypes.APPLICATION_JSON).build(); + String contentHash = RegistryContentUtils.contentHash(contentWrapperDto); + Assertions.assertThrows(ProblemDetails.class, () -> { + clientV3.ids().contentHashes().byContentHash(contentHash).get(); + }); } @Test @@ -375,4 +394,70 @@ public void testDraftVersionsWithBranches() throws Exception { Assertions.assertEquals(0, draftsBranch.getVersions().size()); } + @Test + public void testDraftVersionsInCcompat() throws Exception { + String content = AVRO_CONTENT_V1; + String groupId = GroupId.DEFAULT.getRawGroupIdWithDefaultString(); + String draftArtifactId = TestUtils.generateArtifactId(); + String enabledArtifactId = TestUtils.generateArtifactId(); + + // Create artifact with version as DRAFT + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(draftArtifactId, ArtifactType.AVRO, + content, ContentTypes.APPLICATION_JSON); + createArtifact.getFirstVersion().setIsDraft(true); + CreateArtifactResponse car = clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + Assertions.assertNotNull(car); + Assertions.assertNotNull(car.getVersion()); + + // Create artifact with version as ENABLED + createArtifact = TestUtils.clientCreateArtifact(enabledArtifactId, ArtifactType.AVRO, content, + ContentTypes.APPLICATION_JSON); + car = clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + Assertions.assertNotNull(car); + Assertions.assertNotNull(car.getVersion()); + + // Should be able to fetch the subject in ccompat + List allSubjects = confluentClient.getAllSubjects(); + Assertions.assertTrue(!allSubjects.isEmpty()); + + // Should not be able to list versions - no versions are visible + Assertions.assertThrows(RestClientException.class, () -> { + confluentClient.getAllVersions(draftArtifactId); + }); + + List allVersions = confluentClient.getAllVersions(enabledArtifactId); + Assertions.assertEquals(1, allVersions.size()); + } + + @Test + public void testDraftVersionsInCoreV2() throws Exception { + String content = AVRO_CONTENT_V1; + String groupId = GroupId.DEFAULT.getRawGroupIdWithDefaultString(); + String artifactId = TestUtils.generateArtifactId(); + + // Create artifact with version as DRAFT + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.AVRO, + content, ContentTypes.APPLICATION_JSON); + createArtifact.getFirstVersion().setIsDraft(true); + createArtifact.getFirstVersion().setVersion("1.0"); + CreateArtifactResponse car = clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + Assertions.assertNotNull(car); + Assertions.assertNotNull(car.getVersion()); + + // The version of the artifact is DRAFT so v2 will report the artifact as 404 not found + given().when().contentType(CT_JSON).pathParam("groupId", groupId).pathParam("artifactId", artifactId) + .get("/registry/v2/groups/{groupId}/artifacts/{artifactId}/versions/latest").then() + .statusCode(404); + + // Transition draft content to enabled + WrappedVersionState enabled = new WrappedVersionState(); + enabled.setState(VersionState.ENABLED); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions() + .byVersionExpression("1.0").state().put(enabled); + + // Now we can get the artifact + given().when().contentType(CT_JSON).pathParam("groupId", groupId).pathParam("artifactId", artifactId) + .get("/registry/v2/groups/{groupId}/artifacts/{artifactId}/versions/latest").then() + .statusCode(200); + } } diff --git a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/GroupsResourceTest.java b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/GroupsResourceTest.java index 060aa1502d..a035da6f9a 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/GroupsResourceTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/GroupsResourceTest.java @@ -368,7 +368,7 @@ public void testGetArtifact() throws Exception { given().when().pathParam("groupId", GROUP).pathParam("artifactId", "testGetArtifact/MissingAPI") .get("/registry/v3/groups/{groupId}/artifacts/{artifactId}/versions/branch=latest").then() .statusCode(404).body("status", equalTo(404)).body("title", equalTo( - "No version '' found for artifact with ID 'testGetArtifact/MissingAPI' in group 'GroupsResourceTest'.")); + "No version '' found for artifact with ID 'testGetArtifact/MissingAPI' in group 'GroupsResourceTest'.")); } @Test diff --git a/app/src/test/java/io/apicurio/registry/noprofile/storage/RegistryStorageSmokeTest.java b/app/src/test/java/io/apicurio/registry/noprofile/storage/RegistryStorageSmokeTest.java index 591f2d008c..ce378fb540 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/storage/RegistryStorageSmokeTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/storage/RegistryStorageSmokeTest.java @@ -117,7 +117,7 @@ public void testArtifactsAndMeta() throws Exception { assertNotNull(a1.getContent()); GAV latestGAV = getStorage().getBranchTip(new GA(GROUP_ID, artifactId1), BranchId.LATEST, - RetrievalBehavior.DEFAULT); + RetrievalBehavior.ALL_STATES); ArtifactVersionMetaDataDto metaLatest = getStorage().getArtifactVersionMetaData(GROUP_ID, artifactId1, latestGAV.getRawVersionId()); assertEquals(vmdDto1_2, metaLatest); diff --git a/app/src/test/java/io/apicurio/registry/storage/impl/readonly/ReadOnlyRegistryStorageTest.java b/app/src/test/java/io/apicurio/registry/storage/impl/readonly/ReadOnlyRegistryStorageTest.java index 0c7c014325..31839057a7 100644 --- a/app/src/test/java/io/apicurio/registry/storage/impl/readonly/ReadOnlyRegistryStorageTest.java +++ b/app/src/test/java/io/apicurio/registry/storage/impl/readonly/ReadOnlyRegistryStorageTest.java @@ -101,8 +101,10 @@ public class ReadOnlyRegistryStorageTest { new State(false, s -> s.getArtifactVersionMetaDataByContent(null, null, false, null, null))), entry("getArtifactVersions2", new State(false, s -> s.getArtifactVersions(null, null))), - entry("getArtifactVersions3", new State(false, - s -> s.getArtifactVersions(null, null, RegistryStorage.RetrievalBehavior.DEFAULT))), + entry("getArtifactVersions3", + new State(false, + s -> s.getArtifactVersions(null, null, + RegistryStorage.RetrievalBehavior.ALL_STATES))), entry("getArtifactVersionState3", new State(false, s -> s.getArtifactVersionState(null, null, null))), entry("getEnabledArtifactContentIds2", From 0a9bedda73defa5058e402d2e53cb5c622d9213c Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Tue, 15 Oct 2024 14:26:29 -0400 Subject: [PATCH 11/12] Minor fixes while debugging an environmental issue that was resulting in test failures --- app/src/main/resources/application.properties | 2 +- .../noprofile/rest/v3/DraftContentTest.java | 4 +-- .../ref-registry-all-configs.adoc | 8 +++--- ...afkasqlRecoverFromSnapshotTestProfile.java | 27 ------------------- 4 files changed, 7 insertions(+), 34 deletions(-) delete mode 100644 utils/tests/src/main/java/io/apicurio/registry/utils/tests/KafkasqlRecoverFromSnapshotTestProfile.java diff --git a/app/src/main/resources/application.properties b/app/src/main/resources/application.properties index d53c3c73f3..104293b6fd 100644 --- a/app/src/main/resources/application.properties +++ b/app/src/main/resources/application.properties @@ -160,7 +160,7 @@ apicurio.import.work-dir=${java.io.tmpdir} ## SQL Storage apicurio.storage.sql.kind=h2 -apicurio.datasource.url=jdbc:h2:mem:${quarkus.uuid} +apicurio.datasource.url=jdbc:h2:mem:db_${quarkus.uuid} apicurio.datasource.username=sa apicurio.datasource.password=sa apicurio.datasource.jdbc.initial-size=20 diff --git a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java index 69796eb4db..9daa069b6d 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java @@ -436,8 +436,8 @@ public void testDraftVersionsInCoreV2() throws Exception { String artifactId = TestUtils.generateArtifactId(); // Create artifact with version as DRAFT - CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.AVRO, - content, ContentTypes.APPLICATION_JSON); + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.AVRO, content, + ContentTypes.APPLICATION_JSON); createArtifact.getFirstVersion().setIsDraft(true); createArtifact.getFirstVersion().setVersion("1.0"); CreateArtifactResponse car = clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); diff --git a/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc b/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc index 9a28bd12b3..5c76e1e944 100644 --- a/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc +++ b/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc @@ -71,7 +71,7 @@ The following {registry} configuration options are available for each component |`apicurio.auth.admin-override.user` |`string` |`admin` -|`3.0.0.Final` +|`3.0.0` |Auth admin override user name |`apicurio.auth.anonymous-read-access.enabled` |`boolean [dynamic]` @@ -530,12 +530,12 @@ The following {registry} configuration options are available for each component |`apicurio.rest.artifact.download.max-size.bytes` |`int` |`1000000` -|`2.2.6-SNAPSHOT` +|`2.2.6` |Max size of the artifact allowed to be downloaded from URL |`apicurio.rest.artifact.download.ssl-validation.disabled` |`boolean` |`false` -|`2.2.6-SNAPSHOT` +|`2.2.6` |Skip SSL validation when downloading artifacts from URL |`apicurio.rest.deletion.artifact-version.enabled` |`boolean [dynamic]` @@ -832,7 +832,7 @@ The following {registry} configuration options are available for each component |`artifacts.skip.disabled.latest` |`boolean` |`true` -|`2.4.2-SNAPSHOT` +|`2.4.2` |Skip artifact versions with DISABLED state when retrieving latest artifact version |=== diff --git a/utils/tests/src/main/java/io/apicurio/registry/utils/tests/KafkasqlRecoverFromSnapshotTestProfile.java b/utils/tests/src/main/java/io/apicurio/registry/utils/tests/KafkasqlRecoverFromSnapshotTestProfile.java deleted file mode 100644 index 3ff76d54d3..0000000000 --- a/utils/tests/src/main/java/io/apicurio/registry/utils/tests/KafkasqlRecoverFromSnapshotTestProfile.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.apicurio.registry.utils.tests; - -import io.quarkus.test.junit.QuarkusTestProfile; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -public class KafkasqlRecoverFromSnapshotTestProfile implements QuarkusTestProfile { - - @Override - public Map getConfigOverrides() { - return Map.of("apicurio.storage.kind", "kafkasql", "apicurio.datasource.url", - "jdbc:h2:mem:" + UUID.randomUUID()); - } - - @Override - public List testResources() { - if (!Boolean.parseBoolean(System.getProperty("cluster.tests"))) { - return List.of(new TestResourceEntry(KafkaTestContainerManager.class)); - } else { - return Collections.emptyList(); - } - } - -} From b1cd676e581411524b26e877b771a14769c15aac Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Tue, 15 Oct 2024 14:39:40 -0400 Subject: [PATCH 12/12] Update all workflows to use a newer ubuntu image --- .github/workflows/dependabot-autoapprove.yaml | 2 +- .github/workflows/dependabot-automerge.yaml | 2 +- .github/workflows/integration-tests.yaml | 16 ++++++++-------- .github/workflows/maven-snapshot-release.yaml | 2 +- .github/workflows/operator.yaml | 2 +- .github/workflows/publish-docs.yaml | 2 +- .github/workflows/qodana.yaml | 2 +- .github/workflows/registry-rhbq-build.yaml | 2 +- .github/workflows/release-images.yaml | 2 +- .github/workflows/release-maven-artifacts.yaml | 2 +- .github/workflows/release-sdk-go.yaml | 2 +- .github/workflows/release-sdk-python.yaml | 2 +- .github/workflows/release-sdk-typescript.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/tool-exportV1-release.yaml | 2 +- .github/workflows/update-openapi.yaml | 2 +- .github/workflows/update-website.yaml | 2 +- .github/workflows/validate-docs.yaml | 2 +- .github/workflows/validate-openapi.yaml | 2 +- .github/workflows/verify.yaml | 12 ++++++------ 20 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/dependabot-autoapprove.yaml b/.github/workflows/dependabot-autoapprove.yaml index 0077686365..85f7e29d50 100644 --- a/.github/workflows/dependabot-autoapprove.yaml +++ b/.github/workflows/dependabot-autoapprove.yaml @@ -4,7 +4,7 @@ on: pull_request_target jobs: auto-approve: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: pull-requests: write if: github.actor == 'dependabot[bot]' diff --git a/.github/workflows/dependabot-automerge.yaml b/.github/workflows/dependabot-automerge.yaml index 70f9734ef8..0f6da0a0db 100644 --- a/.github/workflows/dependabot-automerge.yaml +++ b/.github/workflows/dependabot-automerge.yaml @@ -8,7 +8,7 @@ permissions: jobs: dependabot: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: ${{ github.actor == 'dependabot[bot]' }} steps: - name: Dependabot metadata diff --git a/.github/workflows/integration-tests.yaml b/.github/workflows/integration-tests.yaml index 6709d4280b..a4454d62fc 100644 --- a/.github/workflows/integration-tests.yaml +++ b/.github/workflows/integration-tests.yaml @@ -24,7 +24,7 @@ concurrency: jobs: prepare-integration-tests: name: Prepare for Integration Tests - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: github.repository_owner == 'Apicurio' && !contains(github.event.*.labels.*.name, 'DO NOT MERGE') steps: - name: Show Actor @@ -67,7 +67,7 @@ jobs: prepare-ui-tests: name: Prepare for UI Integration Tests - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: github.repository_owner == 'Apicurio' && !contains(github.event.*.labels.*.name, 'DO NOT MERGE') steps: - name: Show Actor @@ -119,7 +119,7 @@ jobs: integration-tests-h2: name: Integration Tests H2 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: prepare-integration-tests steps: - name: Checkout Code @@ -164,7 +164,7 @@ jobs: integration-tests-postgresql: name: Integration Tests Postgresql - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: prepare-integration-tests steps: - name: Checkout Code @@ -210,7 +210,7 @@ jobs: integration-tests-kafkasql: name: Integration Tests KafkaSql - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: prepare-integration-tests steps: - name: Checkout Code @@ -258,7 +258,7 @@ jobs: integration-tests-ui: name: Integration Tests UI - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: [prepare-ui-tests, prepare-integration-tests] steps: - name: Checkout Code @@ -331,7 +331,7 @@ jobs: integration-tests-legacy-v2: name: Integration Tests Legacy V2 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: prepare-integration-tests steps: - name: Checkout Registry 2.5 @@ -369,7 +369,7 @@ jobs: build-examples: name: Build and Run Application examples - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: prepare-integration-tests steps: - name: Checkout Code with Ref '${{ github.ref }}' diff --git a/.github/workflows/maven-snapshot-release.yaml b/.github/workflows/maven-snapshot-release.yaml index 333723700c..038afb2512 100644 --- a/.github/workflows/maven-snapshot-release.yaml +++ b/.github/workflows/maven-snapshot-release.yaml @@ -3,7 +3,7 @@ on: workflow_dispatch jobs: deploy: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/operator.yaml b/.github/workflows/operator.yaml index 405f516f9a..8671d830ab 100644 --- a/.github/workflows/operator.yaml +++ b/.github/workflows/operator.yaml @@ -24,7 +24,7 @@ concurrency: jobs: tests: name: Operator Basic tests - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout Code uses: actions/checkout@v3 diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml index 38f483bec8..0533770790 100644 --- a/.github/workflows/publish-docs.yaml +++ b/.github/workflows/publish-docs.yaml @@ -9,7 +9,7 @@ on: jobs: publish-docs: if: github.repository_owner == 'Apicurio' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Apicurio Website Checkout diff --git a/.github/workflows/qodana.yaml b/.github/workflows/qodana.yaml index e22b9a2720..6ff3db1f92 100644 --- a/.github/workflows/qodana.yaml +++ b/.github/workflows/qodana.yaml @@ -22,7 +22,7 @@ concurrency: jobs: lint: name: Qodana - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: > github.repository_owner == 'Apicurio' && contains(github.event.*.labels.*.name, 'QODANA') permissions: diff --git a/.github/workflows/registry-rhbq-build.yaml b/.github/workflows/registry-rhbq-build.yaml index d8c598209d..6e052912a8 100644 --- a/.github/workflows/registry-rhbq-build.yaml +++ b/.github/workflows/registry-rhbq-build.yaml @@ -26,7 +26,7 @@ on: jobs: build: name: Build Project - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: github.repository_owner == 'Apicurio' steps: diff --git a/.github/workflows/release-images.yaml b/.github/workflows/release-images.yaml index 286896e040..23f246bf08 100644 --- a/.github/workflows/release-images.yaml +++ b/.github/workflows/release-images.yaml @@ -19,7 +19,7 @@ env: jobs: release-images: if: github.repository_owner == 'Apicurio' && (github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, '3.')) - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 120 env: RELEASE_TYPE: release diff --git a/.github/workflows/release-maven-artifacts.yaml b/.github/workflows/release-maven-artifacts.yaml index 2d1fa5ff46..33932e4b90 100644 --- a/.github/workflows/release-maven-artifacts.yaml +++ b/.github/workflows/release-maven-artifacts.yaml @@ -19,7 +19,7 @@ env: jobs: release-maven: if: github.repository_owner == 'Apicurio' && (github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, '3.')) - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 30 env: RELEASE_TYPE: release diff --git a/.github/workflows/release-sdk-go.yaml b/.github/workflows/release-sdk-go.yaml index 006bd4c254..4c42340cf6 100644 --- a/.github/workflows/release-sdk-go.yaml +++ b/.github/workflows/release-sdk-go.yaml @@ -19,7 +19,7 @@ env: jobs: release-sdk: if: github.repository_owner == 'Apicurio' && (github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, '3.')) - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 env: RELEASE_TYPE: release diff --git a/.github/workflows/release-sdk-python.yaml b/.github/workflows/release-sdk-python.yaml index 968a97fb97..07dac1abda 100644 --- a/.github/workflows/release-sdk-python.yaml +++ b/.github/workflows/release-sdk-python.yaml @@ -19,7 +19,7 @@ env: jobs: release-sdk: if: github.repository_owner == 'Apicurio' && (github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, '3.')) - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 env: RELEASE_TYPE: release diff --git a/.github/workflows/release-sdk-typescript.yaml b/.github/workflows/release-sdk-typescript.yaml index c474c24069..916652a76e 100644 --- a/.github/workflows/release-sdk-typescript.yaml +++ b/.github/workflows/release-sdk-typescript.yaml @@ -19,7 +19,7 @@ env: jobs: release-sdk: if: github.repository_owner == 'Apicurio' && (github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, '3.')) - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 env: RELEASE_TYPE: release diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 78f53dce14..473fedb39a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -14,7 +14,7 @@ on: default: 'main' jobs: release: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: github.repository_owner == 'Apicurio' env: IS_PRE_RELEASE: false diff --git a/.github/workflows/tool-exportV1-release.yaml b/.github/workflows/tool-exportV1-release.yaml index 6d7b79ae63..e1b3718e11 100644 --- a/.github/workflows/tool-exportV1-release.yaml +++ b/.github/workflows/tool-exportV1-release.yaml @@ -9,7 +9,7 @@ on: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 - name: Set up JDK 17 diff --git a/.github/workflows/update-openapi.yaml b/.github/workflows/update-openapi.yaml index 5d690c1f9b..8dfa165dfc 100644 --- a/.github/workflows/update-openapi.yaml +++ b/.github/workflows/update-openapi.yaml @@ -10,7 +10,7 @@ on: jobs: update-openapi: name: Update OpenAPI - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: github.repository_owner == 'Apicurio' steps: - name: Apicurio Registry Checkout diff --git a/.github/workflows/update-website.yaml b/.github/workflows/update-website.yaml index 930b3d79b7..b5a5903b3f 100644 --- a/.github/workflows/update-website.yaml +++ b/.github/workflows/update-website.yaml @@ -7,7 +7,7 @@ on: jobs: update-website: if: github.repository_owner == 'Apicurio' && (github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, '3.')) - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Apicurio Website Checkout diff --git a/.github/workflows/validate-docs.yaml b/.github/workflows/validate-docs.yaml index c05be6e3d0..25659efb91 100644 --- a/.github/workflows/validate-docs.yaml +++ b/.github/workflows/validate-docs.yaml @@ -10,7 +10,7 @@ on: jobs: validate: name: Validate Docs - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout Code uses: actions/checkout@v3 diff --git a/.github/workflows/validate-openapi.yaml b/.github/workflows/validate-openapi.yaml index 76a9b3831b..55e9cc918e 100644 --- a/.github/workflows/validate-openapi.yaml +++ b/.github/workflows/validate-openapi.yaml @@ -9,7 +9,7 @@ on: jobs: validate: name: Validate - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 with: diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index d907511a13..fe5e95c87c 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -24,7 +24,7 @@ concurrency: jobs: build-verify: name: Verify Application Build - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: github.repository_owner == 'Apicurio' && !contains(github.event.*.labels.*.name, 'DO NOT MERGE') steps: - name: Checkout Code with Ref '${{ github.ref }}' @@ -103,7 +103,7 @@ jobs: build-verify-ui: name: Verify UI Build - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: github.repository_owner == 'Apicurio' && !contains(github.event.*.labels.*.name, 'DO NOT MERGE') steps: - name: Checkout Code with Ref '${{ github.ref }}' @@ -203,7 +203,7 @@ jobs: build-native-images: name: Build and Test Native images - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: github.repository_owner == 'Apicurio' && !contains(github.event.*.labels.*.name, 'DO NOT MERGE') steps: - name: Checkout Code with Ref '${{ github.ref }}' @@ -307,7 +307,7 @@ jobs: build-verify-python-sdk: name: Verify Python SDK - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: github.repository_owner == 'Apicurio' && !contains(github.event.*.labels.*.name, 'DO NOT MERGE') steps: - name: Checkout Code with Ref '${{ github.ref }}' @@ -344,7 +344,7 @@ jobs: build-verify-go-sdk: name: Verify Go SDK - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: github.repository_owner == 'Apicurio' && !contains(github.event.*.labels.*.name, 'DO NOT MERGE') steps: - name: Checkout Code with Ref '${{ github.ref }}' @@ -371,7 +371,7 @@ jobs: notify-sdk: if: github.repository_owner == 'Apicurio' && github.event_name == 'push' && github.ref == 'refs/heads/main' && !contains(github.event.*.labels.*.name, 'DO NOT MERGE') - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: language: [ js ]