diff --git a/compose/.env b/compose/.env index 48d629c82..1daae4120 100644 --- a/compose/.env +++ b/compose/.env @@ -4,6 +4,8 @@ TAG=1.9-SNAPSHOT ACL_REPOSITORY=geoservercloud ACL_TAG=2.3.1 +GATEWAY_PORT=9090 + GS_USER="1000:1000" GATEWAY_SHARED_AUTH=true diff --git a/compose/compose.yml b/compose/compose.yml index 06f7d5fd4..cb6323e75 100644 --- a/compose/compose.yml +++ b/compose/compose.yml @@ -115,7 +115,7 @@ services: SPRING_PROFILES_ACTIVE: "${GATEWAY_DEFAULT_PROFILES}" GATEWAY_SHARED_AUTH: "${GATEWAY_SHARED_AUTH}" #same as in gstemplate ports: - - 9090:8080 + - ${GATEWAY_PORT}:8080 deploy: resources: limits: diff --git a/src/catalog/backends/pgconfig/src/main/resources/db/pgconfig/migration/postgresql/V1_8__ServiceInfoSettingsInfoConstraints.sql b/src/catalog/backends/pgconfig/src/main/resources/db/pgconfig/migration/postgresql/V1_8__ServiceInfoSettingsInfoConstraints.sql new file mode 100644 index 000000000..e5ce1b9c3 --- /dev/null +++ b/src/catalog/backends/pgconfig/src/main/resources/db/pgconfig/migration/postgresql/V1_8__ServiceInfoSettingsInfoConstraints.sql @@ -0,0 +1,43 @@ +/* + * Add referential integrity constraint from: + * + * - settingsinfo(workspace) -> workspaceindo(id) + * - serviceinfo(workspace) -> workspaceinfo(id) + * + * And unique indexes for: + * + * - settingsinfo(workspace) + * - serviceinfo("@type", workspace) + */ + + +ALTER TABLE settingsinfo + ADD CONSTRAINT fk_settingsinfo_workspace + FOREIGN KEY (workspace) + REFERENCES workspaceinfo(id) + ON DELETE CASCADE; + +CREATE UNIQUE INDEX settingsinfo_workspace_key + ON settingsinfo (workspace) + NULLS NOT DISTINCT; + +/* + * Delete possible duplicate serviceinfos by type and workspace before enforcing uniqueness. + */ +DELETE FROM serviceinfo WHERE id IN( select id FROM ( + SELECT id, + ROW_NUMBER() OVER(PARTITION BY "@type", workspace ORDER BY id ASC) AS duprow + FROM serviceinfo +) dups +WHERE +dups.duprow > 1); + +ALTER TABLE serviceinfo + ADD CONSTRAINT fk_serviceinfo_workspace + FOREIGN KEY (workspace) + REFERENCES workspaceinfo(id) + ON DELETE CASCADE; + +CREATE UNIQUE INDEX serviceinfo_workspace_key + ON serviceinfo ("@type", workspace) + NULLS NOT DISTINCT; diff --git a/src/catalog/backends/pgconfig/src/test/java/org/geoserver/cloud/backend/pgconfig/config/PgconfigConfigRepositoryConformanceTest.java b/src/catalog/backends/pgconfig/src/test/java/org/geoserver/cloud/backend/pgconfig/config/PgconfigConfigRepositoryConformanceTest.java index 9824ff752..e53f38540 100644 --- a/src/catalog/backends/pgconfig/src/test/java/org/geoserver/cloud/backend/pgconfig/config/PgconfigConfigRepositoryConformanceTest.java +++ b/src/catalog/backends/pgconfig/src/test/java/org/geoserver/cloud/backend/pgconfig/config/PgconfigConfigRepositoryConformanceTest.java @@ -4,13 +4,19 @@ */ package org.geoserver.cloud.backend.pgconfig.config; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + import org.geoserver.catalog.Catalog; +import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.cloud.backend.pgconfig.PgconfigBackendBuilder; import org.geoserver.cloud.backend.pgconfig.support.PgConfigTestContainer; import org.geoserver.config.GeoServer; import org.geoserver.config.GeoServerConfigConformanceTest; +import org.geoserver.config.ServiceInfo; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -18,7 +24,6 @@ * @since 1.4 */ @Testcontainers(disabledWithoutDocker = true) -@SuppressWarnings("java:S2187") class PgconfigConfigRepositoryConformanceTest extends GeoServerConfigConformanceTest { @Container static PgConfigTestContainer container = new PgConfigTestContainer<>(); @@ -40,4 +45,32 @@ void cleanDb() { Catalog catalog = builder.createCatalog(); return builder.createGeoServer(catalog); } + + @Test + void testAddDuplicateServiceToWorkspace() { + // Make a workspace + WorkspaceInfo ws1 = geoServer.getCatalog().getFactory().createWorkspace(); + ws1.setName("TEST-WORKSPACE-1"); + geoServer.getCatalog().add(ws1); + + // Make a service for that workspace + ServiceInfo service1 = createService(); + service1.setWorkspace(ws1); + service1.setName("TEST-OWS"); + service1.setTitle("Service for WS1"); + geoServer.add(service1); + + ServiceInfo dupTypeAndWorkspace = createService(); + dupTypeAndWorkspace.setWorkspace(ws1); + dupTypeAndWorkspace.setName("TEST-OWS"); + dupTypeAndWorkspace.setTitle("Service for WS1"); + + var ex = + assertThrows( + IllegalArgumentException.class, () -> geoServer.add(dupTypeAndWorkspace)); + + assertThat(ex.getMessage()) + .contains( + "service with name 'TEST-OWS' already exists in workspace 'TEST-WORKSPACE-1'"); + } } diff --git a/src/catalog/cache/src/main/java/org/geoserver/cloud/catalog/cache/CachingGeoServerFacade.java b/src/catalog/cache/src/main/java/org/geoserver/cloud/catalog/cache/CachingGeoServerFacade.java index 6b9f5ca96..aaf2c206f 100644 --- a/src/catalog/cache/src/main/java/org/geoserver/cloud/catalog/cache/CachingGeoServerFacade.java +++ b/src/catalog/cache/src/main/java/org/geoserver/cloud/catalog/cache/CachingGeoServerFacade.java @@ -16,8 +16,10 @@ import org.geoserver.cloud.event.config.GeoServerInfoSet; import org.geoserver.cloud.event.config.LoggingInfoModified; import org.geoserver.cloud.event.config.LoggingInfoSet; +import org.geoserver.cloud.event.config.ServiceAdded; import org.geoserver.cloud.event.config.ServiceModified; import org.geoserver.cloud.event.config.ServiceRemoved; +import org.geoserver.cloud.event.config.SettingsAdded; import org.geoserver.cloud.event.config.SettingsModified; import org.geoserver.cloud.event.config.SettingsRemoved; import org.geoserver.cloud.event.info.ConfigInfoType; @@ -90,8 +92,8 @@ void onLoggingInfoModifyEvent(LoggingInfoModified event) { evictConfigEntry(event); } - @EventListener(classes = SettingsModified.class) - void onSettingsInfoModifyEvent(SettingsModified event) { + @EventListener(classes = ServiceAdded.class) + void onServiceInfoAddEvent(ServiceAdded event) { evictConfigEntry(event); } @@ -100,13 +102,23 @@ void onServiceInfoModifyEvent(ServiceModified event) { evictConfigEntry(event); } - @EventListener(classes = SettingsRemoved.class) - void onSettingsInfoRemoveEvent(SettingsRemoved event) { + @EventListener(classes = ServiceRemoved.class) + void onServiceInfoRemoveEvent(ServiceRemoved event) { evictConfigEntry(event); } - @EventListener(classes = ServiceRemoved.class) - void onServiceInfoRemoveEvent(ServiceRemoved event) { + @EventListener(classes = SettingsAdded.class) + void onSettingsInfoAddEvent(SettingsAdded event) { + evictConfigEntry(event); + } + + @EventListener(classes = SettingsModified.class) + void onSettingsInfoModifyEvent(SettingsModified event) { + evictConfigEntry(event); + } + + @EventListener(classes = SettingsRemoved.class) + void onSettingsInfoRemoveEvent(SettingsRemoved event) { evictConfigEntry(event); }