Skip to content

Commit 440df86

Browse files
committed
By default, require a registry to be empty before allowing an import
1 parent eeae909 commit 440df86

File tree

19 files changed

+154
-45
lines changed

19 files changed

+154
-45
lines changed

app/src/main/java/io/apicurio/registry/ImportLifecycleBean.java

+7-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.apicurio.registry;
22

3-
import io.apicurio.common.apps.config.Info;
3+
import io.apicurio.registry.rest.ConflictException;
44
import io.apicurio.registry.rest.v3.AdminResourceImpl;
55
import io.apicurio.registry.storage.RegistryStorage;
66
import io.apicurio.registry.storage.StorageEvent;
@@ -11,14 +11,12 @@
1111
import jakarta.enterprise.context.ApplicationScoped;
1212
import jakarta.enterprise.event.ObservesAsync;
1313
import jakarta.inject.Inject;
14-
import org.eclipse.microprofile.config.inject.ConfigProperty;
1514
import org.slf4j.Logger;
1615

1716
import java.io.BufferedInputStream;
1817
import java.io.IOException;
1918
import java.io.InputStream;
2019
import java.net.URL;
21-
import java.util.Optional;
2220

2321
@ApplicationScoped
2422
public class ImportLifecycleBean {
@@ -33,26 +31,25 @@ public class ImportLifecycleBean {
3331
@Inject
3432
ImportExportConfigProperties importExportProps;
3533

36-
@ConfigProperty(name = "apicurio.import.url")
37-
@Info(category = "import", description = "The import URL", availableSince = "2.1.0.Final")
38-
Optional<URL> registryImportUrlProp;
39-
4034
@Inject
4135
AdminResourceImpl v3Admin;
4236

4337
void onStorageReady(@ObservesAsync StorageEvent ev) {
44-
if (StorageEventType.READY.equals(ev.getType()) && registryImportUrlProp.isPresent()) {
38+
if (StorageEventType.READY.equals(ev.getType())
39+
&& importExportProps.registryImportUrlProp.isPresent()) {
4540
log.info("Import URL exists.");
46-
final URL registryImportUrl = registryImportUrlProp.get();
41+
final URL registryImportUrl = importExportProps.registryImportUrlProp.get();
4742
try (final InputStream registryImportZip = new BufferedInputStream(
4843
registryImportUrl.openStream())) {
4944
log.info("Importing {} on startup.", registryImportUrl);
50-
v3Admin.importData(false, false, registryImportZip);
45+
v3Admin.importData(null, null, null, registryImportZip);
5146
log.info("Registry successfully imported from {}", registryImportUrl);
5247
} catch (IOException ioe) {
5348
log.error("Registry import from {} failed", registryImportUrl, ioe);
5449
} catch (ReadOnlyStorageException rose) {
5550
log.error("Registry import failed, because the storage is in read-only mode.");
51+
} catch (ConflictException ce) {
52+
log.info("Import skipped, registry not empty.");
5653
} catch (Exception e) {
5754
log.error("Registry import failed", e);
5855
}

app/src/main/java/io/apicurio/registry/rest/v2/AdminResourceImpl.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ public void deleteGlobalRule(RuleType rule) {
224224
@Authorized(style = AuthorizedStyle.None, level = AuthorizedLevel.Admin)
225225
public void importData(Boolean xRegistryPreserveGlobalId, Boolean xRegistryPreserveContentId,
226226
InputStream data) {
227-
v3Admin.importData(xRegistryPreserveGlobalId, xRegistryPreserveContentId, data);
227+
v3Admin.importData(xRegistryPreserveGlobalId, xRegistryPreserveContentId, false, data);
228228
}
229229

230230
/**

app/src/main/java/io/apicurio/registry/rest/v3/AdminResourceImpl.java

+16-6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.apicurio.registry.auth.RoleBasedAccessApiOperation;
1414
import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck;
1515
import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck;
16+
import io.apicurio.registry.rest.ConflictException;
1617
import io.apicurio.registry.rest.MissingRequiredParameterException;
1718
import io.apicurio.registry.rest.v3.beans.ArtifactTypeInfo;
1819
import io.apicurio.registry.rest.v3.beans.ConfigurationProperty;
@@ -263,13 +264,24 @@ public void deleteGlobalRule(RuleType rule) {
263264
}
264265

265266
/**
266-
* @see io.apicurio.registry.rest.v3.AdminResource#importData(Boolean, Boolean, java.io.InputStream)
267+
* @see io.apicurio.registry.rest.v3.AdminResource#importData(Boolean, Boolean, Boolean, InputStream)
267268
*/
268269
@Override
269270
@Audited
270271
@Authorized(style = AuthorizedStyle.None, level = AuthorizedLevel.Admin)
271272
public void importData(Boolean xRegistryPreserveGlobalId, Boolean xRegistryPreserveContentId,
272-
InputStream data) {
273+
Boolean requireEmptyRegistry, InputStream data) {
274+
boolean preserveGlobalId = xRegistryPreserveGlobalId == null ? importExportProps.preserveGlobalId
275+
: xRegistryPreserveGlobalId;
276+
boolean preserveContentId = xRegistryPreserveContentId == null ? importExportProps.preserveContentId
277+
: xRegistryPreserveContentId;
278+
boolean requireEmpty = requireEmptyRegistry == null ? importExportProps.requireEmptyRegistry
279+
: requireEmptyRegistry;
280+
281+
if (requireEmpty && !storage.isEmpty()) {
282+
throw new ConflictException("Registry is not empty.");
283+
}
284+
273285
// The input should be a ZIP file
274286
final ZipInputStream zip = new ZipInputStream(data, StandardCharsets.UTF_8);
275287

@@ -317,11 +329,9 @@ public void importData(Boolean xRegistryPreserveGlobalId, Boolean xRegistryPrese
317329

318330
// Import or upgrade the data into the storage
319331
if (upgrade) {
320-
this.storage.upgradeData(stream, isNullOrTrue(xRegistryPreserveGlobalId),
321-
isNullOrTrue(xRegistryPreserveContentId));
332+
this.storage.upgradeData(stream, preserveGlobalId, preserveContentId);
322333
} else {
323-
this.storage.importData(stream, isNullOrTrue(xRegistryPreserveGlobalId),
324-
isNullOrTrue(xRegistryPreserveContentId));
334+
this.storage.importData(stream, preserveGlobalId, preserveContentId);
325335
}
326336
} finally {
327337
try {

app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java

+5
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ public interface RegistryStorage extends DynamicConfigStorage {
9797
*/
9898
boolean isReadOnly();
9999

100+
/**
101+
* Returns true if the storage is empty (and ready for data to be imported).
102+
*/
103+
boolean isEmpty();
104+
100105
/**
101106
* Create a new artifact in the storage, with or without an initial/first version. Throws an exception if
102107
* the artifact already exists. The first version information can be null, in which case an empty artifact

app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorReadOnlyBase.java

+5
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,11 @@ public Map<String, TypedContent> resolveReferences(List<ArtifactReferenceDto> re
277277
return delegate.resolveReferences(references);
278278
}
279279

280+
@Override
281+
public boolean isEmpty() {
282+
return delegate.isEmpty();
283+
}
284+
280285
@Override
281286
public boolean isArtifactExists(String groupId, String artifactId) throws RegistryStorageException {
282287
return delegate.isArtifactExists(groupId, artifactId);

app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java

+5
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,11 @@ public boolean isArtifactExists(String groupId, String artifactId) {
426426
return proxy(storage -> storage.isArtifactExists(groupId, artifactId));
427427
}
428428

429+
@Override
430+
public boolean isEmpty() {
431+
return proxy(storage -> storage.isEmpty());
432+
}
433+
429434
@Override
430435
public boolean isGroupExists(String groupId) {
431436
return proxy(storage -> storage.isGroupExists(groupId));

app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java

+7
Original file line numberDiff line numberDiff line change
@@ -2925,6 +2925,13 @@ public void importComment(CommentEntity entity) {
29252925
});
29262926
}
29272927

2928+
@Override
2929+
public boolean isEmpty() {
2930+
return handles.withHandle(handle -> {
2931+
return handle.createQuery(sqlStatements.selectAllContentCount()).mapTo(Long.class).one() == 0;
2932+
});
2933+
}
2934+
29282935
private boolean isContentExists(Handle handle, long contentId) {
29292936
return handle.createQuery(sqlStatements().selectContentExists()).bind(0, contentId)
29302937
.mapTo(Integer.class).one() > 0;

app/src/main/java/io/apicurio/registry/storage/impl/sql/CommonSqlStatements.java

+8
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,14 @@ public String selectAllArtifactCount() {
519519
return "SELECT COUNT(a.artifactId) FROM artifacts a ";
520520
}
521521

522+
/**
523+
* @see SqlStatements#selectAllContentCount()
524+
*/
525+
@Override
526+
public String selectAllContentCount() {
527+
return "SELECT COUNT(c.contentId) FROM content c ";
528+
}
529+
522530
/**
523531
* @see io.apicurio.registry.storage.impl.sql.SqlStatements#selectAllArtifactVersionsCount()
524532
*/

app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlStatements.java

+3
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,8 @@ public interface SqlStatements {
501501

502502
public String selectGlobalIdExists();
503503

504+
public String selectAllContentCount();
505+
504506
/*
505507
* The next few statements support role mappings
506508
*/
@@ -616,4 +618,5 @@ public interface SqlStatements {
616618
public String createDataSnapshot();
617619

618620
public String restoreFromSnapshot();
621+
619622
}

app/src/main/java/io/apicurio/registry/storage/importing/ImportExportConfigProperties.java

+19
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,30 @@
44
import jakarta.inject.Singleton;
55
import org.eclipse.microprofile.config.inject.ConfigProperty;
66

7+
import java.net.URL;
8+
import java.util.Optional;
9+
710
@Singleton
811
public class ImportExportConfigProperties {
912

1013
@ConfigProperty(name = "apicurio.import.workDir")
1114
@Info(category = "import", description = "Temporary work directory to use when importing data.", availableSince = "3.0.0")
1215
public String workDir;
1316

17+
@ConfigProperty(name = "apicurio.import.requireEmptyRegistry", defaultValue = "true")
18+
@Info(category = "import", description = "When set to true, importing data will only work when the registry is empty. Defaults to 'true'.", availableSince = "3.0.0")
19+
public boolean requireEmptyRegistry;
20+
21+
@ConfigProperty(name = "apicurio.import.preserveGlobalId", defaultValue = "true")
22+
@Info(category = "import", description = "When set to true, global IDs from the import file will be used (otherwise new IDs will be generated). Defaults to 'true'.", availableSince = "3.0.0")
23+
public boolean preserveGlobalId;
24+
25+
@ConfigProperty(name = "apicurio.import.preserveContentId", defaultValue = "true")
26+
@Info(category = "import", description = "When set to true, content IDs from the import file will be used (otherwise new IDs will be generated). Defaults to 'true'.", availableSince = "3.0.0")
27+
public boolean preserveContentId;
28+
29+
@ConfigProperty(name = "apicurio.import.url")
30+
@Info(category = "import", description = "The import URL", availableSince = "2.1.0.Final")
31+
public Optional<URL> registryImportUrlProp;
32+
1433
}

app/src/test/java/io/apicurio/registry/MigrationTest.java

+9
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
package io.apicurio.registry;
22

3+
import io.apicurio.registry.storage.RegistryStorage;
4+
import io.apicurio.registry.types.Current;
35
import io.quarkus.test.junit.QuarkusTest;
6+
import jakarta.inject.Inject;
47
import org.junit.jupiter.api.Test;
58

69
import java.io.InputStream;
710

811
@QuarkusTest
912
public class MigrationTest extends AbstractResourceTestBase {
1013

14+
@Inject
15+
@Current
16+
RegistryStorage storage;
17+
1118
@Test
1219
public void migrateData() throws Exception {
20+
storage.deleteAllUserData();
1321

1422
InputStream originalData = getClass().getResource("rest/v3/destination_original_data.zip")
1523
.openStream();
@@ -25,6 +33,7 @@ public void migrateData() throws Exception {
2533
config.headers.add("Content-Type", "application/zip");
2634
config.headers.add("X-Registry-Preserve-GlobalId", "false");
2735
config.headers.add("X-Registry-Preserve-ContentId", "false");
36+
config.queryParameters.requireEmptyRegistry = false;
2837
});
2938
}
3039
}

app/src/test/java/io/apicurio/registry/storage/impl/readonly/ReadOnlyRegistryStorageTest.java

+1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ public class ReadOnlyRegistryStorageTest {
134134
entry("importGroup1", new State(true, s -> s.importGroup(null))),
135135
entry("initialize0", new State(false, RegistryStorage::initialize)),
136136
entry("isAlive0", new State(false, RegistryStorage::isAlive)),
137+
entry("isEmpty0", new State(false, RegistryStorage::isEmpty)),
137138
entry("isArtifactExists2", new State(false, s -> s.isArtifactExists(null, null))),
138139
entry("isArtifactRuleExists3",
139140
new State(false, s -> s.isArtifactRuleExists(null, null, null))),

common/src/main/resources/META-INF/openapi.json

+12
Original file line numberDiff line numberDiff line change
@@ -565,12 +565,24 @@
565565
},
566566
"in": "header",
567567
"required": false
568+
},
569+
{
570+
"name": "requireEmptyRegistry",
571+
"description": "Query parameter indicating whether the registry must be empty before allowing\ndata to be imported. Defaults to `true` if omitted.",
572+
"schema": {
573+
"type": "boolean"
574+
},
575+
"in": "query",
576+
"required": false
568577
}
569578
],
570579
"responses": {
571580
"201": {
572581
"description": "Indicates that the import was successful."
573582
},
583+
"409": {
584+
"$ref": "#/components/responses/Conflict"
585+
},
574586
"500": {
575587
"$ref": "#/components/responses/ServerError"
576588
}

docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc

+15
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,21 @@ The following {registry} configuration options are available for each component
379379
|Default
380380
|Available from
381381
|Description
382+
|`apicurio.import.preserveContentId`
383+
|`boolean`
384+
|`true`
385+
|`3.0.0`
386+
|When set to true, content IDs from the import file will be used (otherwise new IDs will be generated). Defaults to 'true'.
387+
|`apicurio.import.preserveGlobalId`
388+
|`boolean`
389+
|`true`
390+
|`3.0.0`
391+
|When set to true, global IDs from the import file will be used (otherwise new IDs will be generated). Defaults to 'true'.
392+
|`apicurio.import.requireEmptyRegistry`
393+
|`boolean`
394+
|`true`
395+
|`3.0.0`
396+
|When set to true, importing data will only work when the registry is empty. Defaults to 'true'.
382397
|`apicurio.import.url`
383398
|`optional<url>`
384399
|

go-sdk/pkg/registryclient-v3/admin/import_request_builder.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,26 @@ type ImportRequestBuilder struct {
1111
i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.BaseRequestBuilder
1212
}
1313

14+
// ImportRequestBuilderPostQueryParameters imports registry data that was previously exported using the `/admin/export` operation.
15+
type ImportRequestBuilderPostQueryParameters struct {
16+
// Query parameter indicating whether the registry must be empty before allowingdata to be imported. Defaults to `true` if omitted.
17+
RequireEmptyRegistry *bool `uriparametername:"requireEmptyRegistry"`
18+
}
19+
1420
// ImportRequestBuilderPostRequestConfiguration configuration for the request such as headers, query parameters, and middleware options.
1521
type ImportRequestBuilderPostRequestConfiguration struct {
1622
// Request headers
1723
Headers *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestHeaders
1824
// Request options
1925
Options []i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestOption
26+
// Request query parameters
27+
QueryParameters *ImportRequestBuilderPostQueryParameters
2028
}
2129

2230
// NewImportRequestBuilderInternal instantiates a new ImportRequestBuilder and sets the default values.
2331
func NewImportRequestBuilderInternal(pathParameters map[string]string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *ImportRequestBuilder {
2432
m := &ImportRequestBuilder{
25-
BaseRequestBuilder: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewBaseRequestBuilder(requestAdapter, "{+baseurl}/admin/import", pathParameters),
33+
BaseRequestBuilder: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewBaseRequestBuilder(requestAdapter, "{+baseurl}/admin/import{?requireEmptyRegistry*}", pathParameters),
2634
}
2735
return m
2836
}
@@ -41,6 +49,7 @@ func (m *ImportRequestBuilder) Post(ctx context.Context, body []byte, requestCon
4149
return err
4250
}
4351
errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{
52+
"409": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateErrorFromDiscriminatorValue,
4453
"500": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateErrorFromDiscriminatorValue,
4554
}
4655
err = m.BaseRequestBuilder.RequestAdapter.SendNoContent(ctx, requestInfo, errorMapping)
@@ -54,6 +63,9 @@ func (m *ImportRequestBuilder) Post(ctx context.Context, body []byte, requestCon
5463
func (m *ImportRequestBuilder) ToPostRequestInformation(ctx context.Context, body []byte, requestConfiguration *ImportRequestBuilderPostRequestConfiguration) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) {
5564
requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.POST, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters)
5665
if requestConfiguration != nil {
66+
if requestConfiguration.QueryParameters != nil {
67+
requestInfo.AddQueryParameters(*(requestConfiguration.QueryParameters))
68+
}
5769
requestInfo.Headers.AddAll(requestConfiguration.Headers)
5870
requestInfo.AddRequestOptions(requestConfiguration.Options)
5971
}

go-sdk/pkg/registryclient-v3/kiota-lock.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"descriptionHash": "B46056EBAC7BEBE57948B9F0255841628F4C6DC6F79CC57C6A2C964E2E2A7C36A2D8F58110E85785A37D0C304C9023C70B1AB1322AC2267D304EAA3C503DE5C7",
2+
"descriptionHash": "7B7042289B28CB539FFBCB39E2D202C413EC9BC04B2FF368E1C7301E7B8A5D4BE084F17F1803ED552CA1AF09991A30CCCC6B2ED7D9E4437FCA093A2BB33C5BB5",
33
"descriptionLocation": "../../v3.json",
44
"lockFileVersion": "1.0.0",
55
"kiotaVersion": "1.10.1",

0 commit comments

Comments
 (0)