From 94ea1f0b392d574728666cc5b4372f2cd4ea2166 Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Tue, 15 Oct 2024 15:17:13 -0400 Subject: [PATCH] Mutable artifact versions via DRAFT artifact version state (#5330) * Implemented basic support for DRAFT (mutable) artifact versions * Updated REST API to support searching for versions by state * Formatting * Regenerate go-sdk and fix generate.sh to put kiota_tmp into ./target * Added support for state changes from DRAFT to ENABLED * Improvements/tweaks to DRAFT content * Code reformatting * Add a UI label for draft, deprecated, and disabled versions * Added new "drafts" system branch * Ensure that ccompat and v2 APIs do not return DRAFT content * Minor fixes while debugging an environmental issue that was resulting in test failures * 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 +- .../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 +- .../io/apicurio/registry/auth/AuthConfig.java | 6 +- .../rest/v7/impl/AbstractResource.java | 6 +- .../v7/impl/SubjectVersionsResourceImpl.java | 15 +- .../rest/v7/impl/SubjectsResourceImpl.java | 7 +- .../limits/RegistryStorageLimitsEnforcer.java | 9 +- .../io/apicurio/registry/rest/RestConfig.java | 15 +- .../registry/rest/v2/GroupsResourceImpl.java | 45 +- .../registry/rest/v3/GroupsResourceImpl.java | 229 +++++++-- .../registry/rest/v3/IdsResourceImpl.java | 10 +- .../registry/rest/v3/SearchResourceImpl.java | 10 +- .../apicurio/registry/rest/v3/V3ApiUtil.java | 7 + .../registry/storage/RegistryStorage.java | 95 +++- .../storage/StorageBehaviorProperties.java | 9 +- .../ReadOnlyRegistryStorageDecorator.java | 24 +- .../RegistryStorageDecoratorBase.java | 22 +- .../RegistryStorageDecoratorReadOnlyBase.java | 10 +- .../storage/dto/ContentWrapperDto.java | 1 + .../dto/EditableVersionMetaDataDto.java | 2 - .../storage/dto/SearchedVersionDto.java | 2 + .../AbstractReadOnlyRegistryStorage.java | 18 +- .../impl/gitops/GitOpsRegistryStorage.java | 10 +- .../kafkasql/KafkaSqlRegistryStorage.java | 41 +- .../messages/CreateArtifact10Message.java | 55 +++ .../messages/CreateArtifact9Message.java | 3 +- .../CreateArtifactVersion8Message.java | 3 +- .../CreateArtifactVersion9Message.java | 53 ++ .../UpdateArtifactVersionContent5Message.java | 48 ++ .../UpdateArtifactVersionState5Message.java | 38 ++ .../kafkasql/serde/KafkaSqlMessageIndex.java | 8 +- .../impl/sql/AbstractHandleFactory.java | 4 +- .../impl/sql/AbstractSqlRegistryStorage.java | 296 ++++++++--- .../storage/impl/sql/CommonSqlStatements.java | 47 +- .../impl/sql/SQLServerSqlStatements.java | 10 +- .../storage/impl/sql/SqlStatements.java | 36 +- .../impl/sql/mappers/ContentMapper.java | 1 + .../sql/mappers/SearchedVersionMapper.java | 2 + .../impl/sql/mappers/VersionStateMapper.java | 27 + app/src/main/resources/application.properties | 2 +- .../registry/noprofile/VersionStateTest.java | 28 +- .../noprofile/rest/v3/DraftContentTest.java | 463 ++++++++++++++++++ .../noprofile/rest/v3/DryRunTest.java | 4 +- .../noprofile/rest/v3/GroupsResourceTest.java | 50 +- .../RegistryStoragePerformanceTest.java | 2 +- .../storage/RegistryStorageSmokeTest.java | 16 +- .../registry/rbac/RegistryClientTest.java | 18 +- .../readonly/ReadOnlyRegistryStorageTest.java | 22 +- .../io/apicurio/registry/model/BranchId.java | 1 + .../src/main/resources/META-INF/openapi.json | 191 +++++++- .../ref-registry-all-configs.adoc | 13 +- go-sdk/generate.sh | 14 +- ...m_versions_item_content_request_builder.go | 47 ++ ...tem_versions_item_state_request_builder.go | 134 +++++ ...version_expression_item_request_builder.go | 6 + ...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 +- .../models/create_version.go | 31 ++ .../models/editable_version_meta_data.go | 32 -- .../models/searched_version.go | 62 +++ .../registryclient-v3/models/version_state.go | 5 +- .../models/wrapped_version_state.go | 89 ++++ .../search/versions_request_builder.go | 7 +- .../smokeTests/apicurio/ArtifactsIT.java | 22 +- .../components/tabs/VersionsTable.tsx | 34 +- ...afkasqlRecoverFromSnapshotTestProfile.java | 27 - .../utils/tests/MutabilityEnabledProfile.java | 16 + 86 files changed, 2198 insertions(+), 432 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/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 app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.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 delete mode 100644 utils/tests/src/main/java/io/apicurio/registry/utils/tests/KafkasqlRecoverFromSnapshotTestProfile.java create mode 100644 utils/tests/src/main/java/io/apicurio/registry/utils/tests/MutabilityEnabledProfile.java 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 ] 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 4a00d7b57b..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") 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..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 @@ -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) { @@ -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 8fc067b11a..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,8 +19,8 @@ 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.EditableVersionMetaDataDto; import io.apicurio.registry.storage.dto.StoredArtifactVersionDto; import io.apicurio.registry.storage.error.ArtifactNotFoundException; import io.apicurio.registry.storage.error.InvalidArtifactTypeException; @@ -38,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 @@ -56,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()); } @@ -190,9 +190,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/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/RestConfig.java b/app/src/main/java/io/apicurio/registry/rest/RestConfig.java index e4b309eb91..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.") @@ -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/v2/GroupsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java index 4751ae1032..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()); } @@ -852,9 +878,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); } /** @@ -1131,7 +1156,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 +1282,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 +1418,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..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 @@ -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,34 @@ 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.rest.v3.beans.WrappedVersionState; 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; @@ -138,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 @@ -505,6 +527,48 @@ 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); + + if (!restConfig.isArtifactVersionMutabilityEnabled()) { + throw new NotAllowedException("Artifact version content update operation is not enabled.", + HttpMethod.GET, (String[]) null); + } + + // 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.ALL_STATES)); + + // 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) @@ -522,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()); @@ -562,17 +626,80 @@ 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()); 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.ALL_STATES)); + + 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.ALL_STATES)); + + // 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.getRawGroupIdWithDefaultString(), + 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) @@ -587,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()); @@ -600,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) { @@ -610,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); @@ -628,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()); @@ -641,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) { @@ -652,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()); @@ -770,20 +897,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 +904,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 +917,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 +1000,25 @@ 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(); // 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 +1026,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); } @@ -1092,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: @@ -1141,6 +1269,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 +1277,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/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/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/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 80b6b72499..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; @@ -19,6 +42,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; @@ -88,7 +112,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 +185,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; /** @@ -393,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; /** @@ -433,6 +458,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. * @@ -852,6 +894,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. * @@ -917,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); @@ -954,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/ReadOnlyRegistryStorageDecorator.java b/app/src/main/java/io/apicurio/registry/storage/decorator/ReadOnlyRegistryStorageDecorator.java index e944ffa8e2..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; @@ -75,10 +76,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 +99,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 @@ -181,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 { @@ -302,6 +311,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..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; @@ -41,9 +42,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 +62,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 @@ -119,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..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 @@ -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); @@ -358,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/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/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 bdb756de1e..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; @@ -51,7 +52,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 +61,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(); @@ -138,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 e1303914ba..a387ca308e 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; @@ -270,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)); } @@ -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)); @@ -479,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/kafkasql/KafkaSqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java index 1758b37092..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; @@ -385,13 +386,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 +436,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 +449,18 @@ 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) @@ -590,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) @@ -683,8 +706,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 +731,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/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/kafkasql/serde/KafkaSqlMessageIndex.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/serde/KafkaSqlMessageIndex.java index 039800ef09..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 @@ -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; @@ -51,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; @@ -81,7 +85,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, @@ -103,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/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 cfba4fae70..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 @@ -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; @@ -69,11 +107,12 @@ 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; import io.apicurio.registry.utils.IoUtil; import io.apicurio.registry.utils.StringUtil; @@ -118,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; @@ -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(); @@ -490,7 +531,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; @@ -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); @@ -607,8 +648,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()) { @@ -679,14 +724,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); @@ -815,7 +873,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); @@ -823,7 +881,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 @@ -839,7 +897,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) { @@ -1142,10 +1200,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); } @@ -1153,10 +1212,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); } @@ -1164,10 +1224,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); } @@ -1191,10 +1252,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 { @@ -1478,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, @@ -1613,6 +1663,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; } @@ -1713,6 +1770,32 @@ 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, true); + + 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); + } + + // Updating content will typically leave a row in the content table orphaned. + deleteAllOrphanedContentRaw(handle); + + return null; + }); + } + @Override public void deleteArtifactVersion(String groupId, String artifactId, String version) throws ArtifactNotFoundException, VersionNotFoundException, RegistryStorageException { @@ -1777,7 +1860,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(); @@ -1787,6 +1874,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(); @@ -1795,16 +1884,10 @@ 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(); @@ -1817,13 +1900,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); + } } } @@ -1930,6 +2022,53 @@ 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); + } + + 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(); + + // 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; + }); + } + @Override public List getGlobalRules() throws RegistryStorageException { return handles.withHandleNoException(handle -> { @@ -3497,6 +3636,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(); @@ -3507,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 9e37948822..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 (?)"; } /** @@ -176,6 +176,18 @@ 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 = ?"; + } + + @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() */ @@ -284,6 +296,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() */ @@ -596,7 +613,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 = ?"; } /** @@ -604,7 +622,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 @@ -1051,6 +1070,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 = ?"; @@ -1102,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"; } @@ -1124,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..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"; } @@ -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 f09534174a..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 @@ -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. */ @@ -122,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. @@ -220,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. */ @@ -439,6 +450,16 @@ public interface SqlStatements { */ public String selectGroupByGroupId(); + /** + * A statement used to select the state of a version. + */ + 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. */ @@ -601,7 +622,7 @@ public interface SqlStatements { public String selectBranchTip(); - public String selectBranchTipNotDisabled(); + public String selectBranchTipFilteredByState(); public String updateBranchModifiedTime(); @@ -615,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/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/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/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/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 new file mode 100644 index 0000000000..9daa069b6d --- /dev/null +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/DraftContentTest.java @@ -0,0 +1,463 @@ +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; +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.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; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +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) +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" } + ] + } + """; + + 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"); + // 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(); + + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, + content, ContentTypes.APPLICATION_JSON); + createArtifact.getFirstVersion().setIsDraft(true); + createArtifact.getFirstVersion().setVersion("1.0.0"); + + 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(); + }); + + // 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 + 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)); + } + } + + @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()); + } + + @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()); + } + + @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()); + } + + @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/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/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..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 @@ -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; @@ -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 @@ -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/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..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 @@ -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)); @@ -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); @@ -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/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 ac40a41635..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 @@ -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))), @@ -99,8 +101,12 @@ 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", new State(false, s -> s.getEnabledArtifactContentIds(null, null))), entry("getArtifactVersionsByContentId1", @@ -168,6 +174,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", @@ -177,6 +185,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/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; diff --git a/common/src/main/resources/META-INF/openapi.json b/common/src/main/resources/META-INF/openapi.json index 40c4fd1e48..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" } @@ -2637,6 +2643,42 @@ "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" + }, + "405": { + "$ref": "#/components/responses/MethodNotAllowed" + }, + "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 +2914,14 @@ "type": "string" }, "in": "query" + }, + { + "name": "state", + "description": "Filter by version state.", + "schema": { + "$ref": "#/components/schemas/VersionState" + }, + "in": "query" } ], "responses": { @@ -3349,6 +3399,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": { @@ -3932,6 +4086,15 @@ "groupId": { "$ref": "#/components/schemas/GroupId", "description": "" + }, + "modifiedBy": { + "description": "", + "type": "string" + }, + "modifiedOn": { + "format": "date-time", + "description": "", + "type": "string" } }, "example": { @@ -4773,16 +4936,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" @@ -4794,7 +4952,8 @@ "enum": [ "ENABLED", "DISABLED", - "DEPRECATED" + "DEPRECATED", + "DRAFT" ], "type": "string", "x-codegen-package": "io.apicurio.registry.types" @@ -4885,6 +5044,10 @@ "items": { "type": "string" } + }, + "isDraft": { + "description": "", + "type": "boolean" } } }, @@ -5117,6 +5280,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/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc b/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc index 5382b12b9a..ba89c145ed 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 @@ -530,17 +530,17 @@ 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]` |`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 @@ -827,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/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..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 @@ -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,29 @@ 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 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 { + requestInfo, err := m.ToPutRequestInformation(ctx, body, requestConfiguration) + if err != nil { + return err + } + errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "404": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateProblemDetailsFromDiscriminatorValue, + "405": 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 +116,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/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/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 cc2b7bc5e4..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": "63458FA9F74D289AF7723F27696C3DBA642832DEC01628597EEF51657803AE44946245B0BD2B46A22E96261A48CF659102629051B9F248F60040E7914A2A029C", + "descriptionHash": "0DD5D7FB5AB616D59BD7C21D29C19401ADA735DD30E366CE1946E44209EFEFA3896C1AE4B2F3204F00EE2E6B58AE47F43F999A27E251F2FE5D5C9C65E34380EC", "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/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/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/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/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 } 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 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}) + + + + + + + + + + + + + + + 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(); - } - } - -} 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; + } +}