Merge branch 'stable-3.7' into stable-3.8 * stable-3.7: ConfigurationTest: Remove flaky invalidTargetArchiveFolder test Send project-deleted event after project deletion Change-Id: I2ac01b00d7ce442b96a032d90b4037670e65d79a
diff --git a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteAction.java b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteAction.java index 459d819..47feddf 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteAction.java +++ b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteAction.java
@@ -14,8 +14,12 @@ package com.googlesource.gerrit.plugins.deleteproject; +import com.google.gerrit.common.Nullable; +import com.google.gerrit.extensions.registration.DynamicItem; import com.google.gerrit.extensions.webui.UiAction; import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.config.GerritInstanceId; +import com.google.gerrit.server.events.EventDispatcher; import com.google.gerrit.server.project.ProjectResource; import com.google.inject.Inject; import com.google.inject.Provider; @@ -36,7 +40,9 @@ DeleteLog deleteLog, DeletePreconditions preConditions, Configuration cfg, - HideProject hideProject) { + HideProject hideProject, + DynamicItem<EventDispatcher> dispatcher, + @Nullable @GerritInstanceId String instanceId) { super( dbHandler, fsHandler, @@ -45,7 +51,9 @@ deleteLog, preConditions, cfg, - hideProject); + hideProject, + dispatcher, + instanceId); this.protectedProjects = protectedProjects; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProject.java b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProject.java index 40463b2..75a948a 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProject.java +++ b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProject.java
@@ -14,13 +14,17 @@ package com.googlesource.gerrit.plugins.deleteproject; +import com.google.gerrit.common.Nullable; import com.google.gerrit.entities.Project; +import com.google.gerrit.extensions.registration.DynamicItem; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.RestModifyView; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.config.GerritInstanceId; +import com.google.gerrit.server.events.EventDispatcher; import com.google.gerrit.server.project.ProjectResource; import com.google.inject.Inject; import com.google.inject.Provider; @@ -48,6 +52,8 @@ private final DeleteLog deleteLog; private final Configuration cfg; private final HideProject hideProject; + private final DynamicItem<EventDispatcher> dispatcher; + private final String instanceId; @Inject DeleteProject( @@ -58,7 +64,9 @@ DeleteLog deleteLog, DeletePreconditions preConditions, Configuration cfg, - HideProject hideProject) { + HideProject hideProject, + DynamicItem<EventDispatcher> dispatcher, + @Nullable @GerritInstanceId String instanceId) { this.dbHandler = dbHandler; this.fsHandler = fsHandler; this.cacheHandler = cacheHandler; @@ -67,6 +75,8 @@ this.preConditions = preConditions; this.cfg = cfg; this.hideProject = hideProject; + this.dispatcher = dispatcher; + this.instanceId = instanceId; } @Override @@ -94,6 +104,20 @@ } else { hideProject.apply(rsrc); } + + ProjectDeletedEvent event = new ProjectDeletedEvent(); + event.projectName = project.getName(); + event.instanceId = instanceId; + + /** + * EventBroker checks if user has the permission to access the project. But because this + * project is already deleted, check will always fail. That's why this event will be delivered + * only to the unrestricted listeners. Unrestricted events listeners are implementing {@link + * com.google.gerrit.server.events.EventListener} which allows to listen to events without + * user visibility restrictions. For example these events are going to be delivered to + * multi-site {@link com.googlesource.gerrit.plugins.multisite.event.EventHandler} + */ + dispatcher.get().postEvent(project.getNameKey(), event); } catch (Exception e) { ex = e; throw e;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/PluginModule.java b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/PluginModule.java index 4d0a802..a2e3f15 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/PluginModule.java +++ b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/PluginModule.java
@@ -22,6 +22,7 @@ import com.google.gerrit.extensions.config.CapabilityDefinition; import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.extensions.restapi.RestApiModule; +import com.google.gerrit.server.events.EventTypes; import com.google.inject.AbstractModule; import com.google.inject.Inject; import com.google.inject.internal.UniqueAnnotations; @@ -60,6 +61,8 @@ .to(ArchiveRepositoryRemover.class); } + EventTypes.register(ProjectDeletedEvent.TYPE, ProjectDeletedEvent.class); + install( new RestApiModule() { @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/ProjectDeletedEvent.java b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/ProjectDeletedEvent.java new file mode 100644 index 0000000..31a41cc --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/ProjectDeletedEvent.java
@@ -0,0 +1,33 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.deleteproject; + +import com.google.gerrit.entities.Project; +import com.google.gerrit.entities.Project.NameKey; +import com.google.gerrit.server.events.ProjectEvent; + +public class ProjectDeletedEvent extends ProjectEvent { + public static final String TYPE = "project-deleted"; + public String projectName; + + public ProjectDeletedEvent() { + super(TYPE); + } + + @Override + public NameKey getProjectNameKey() { + return Project.nameKey(projectName); + } +}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md index b72ee3a..2f0e7f6 100644 --- a/src/main/resources/Documentation/about.md +++ b/src/main/resources/Documentation/about.md
@@ -35,6 +35,28 @@ can be configured to listen to the project deletion event and to replicate project deletions. +Event after project deletion +----------------------------------- + +This plugin generates an event after project deletion. Format of +the event: + +=== Project Deleted + +Sent after project deletion. + +type:: "project-deleted" + +projectName:: Name of the deleted project + +eventCreatedOn:: Time in seconds since the UNIX epoch when this event was +created. + +*NOTE*: This event will be delivered only to the unrestricted listeners. +Unrestricted events listeners implement +`com.google.gerrit.server.events.EventListener` without performing any +permission checking. + Access ------
diff --git a/src/test/java/com/googlesource/gerrit/plugins/deleteproject/ConfigurationTest.java b/src/test/java/com/googlesource/gerrit/plugins/deleteproject/ConfigurationTest.java index 9b59f63..6740bd3 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/deleteproject/ConfigurationTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/deleteproject/ConfigurationTest.java
@@ -36,7 +36,6 @@ private static final long DEFAULT_ARCHIVE_DURATION_MS = TimeUnit.DAYS.toMillis(180); private static final String CUSTOM_DURATION = "100"; private static final String CUSTOM_PARENT = "customParent"; - private static final String INVALID_CUSTOM_FOLDER = "//\\\\\\///////"; private static final String INVALID_ARCHIVE_DURATION = "180weeks180years"; private static final String PLUGIN_NAME = "delete-project"; @@ -114,16 +113,4 @@ assertThat(deleteConfig.getArchiveDuration()).isEqualTo(DEFAULT_ARCHIVE_DURATION_MS); } - - @Test - public void invalidTargetArchiveFolder() { - PluginConfig.Update pluginConfig = PluginConfig.Update.forTest(PLUGIN_NAME, new Config()); - pluginConfig.setString("archiveFolder", INVALID_CUSTOM_FOLDER); - - when(pluginConfigFactoryMock.getFromGerritConfig(PLUGIN_NAME)) - .thenReturn(pluginConfig.asPluginConfig()); - deleteConfig = new Configuration(pluginConfigFactoryMock, PLUGIN_NAME, pluginDataDir); - - assertThat(deleteConfig.getArchiveFolder().toString()).isEqualTo(pluginDataDir.toString()); - } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/deleteproject/ProjectDeletedEventTest.java b/src/test/java/com/googlesource/gerrit/plugins/deleteproject/ProjectDeletedEventTest.java new file mode 100644 index 0000000..07e3ee4 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/deleteproject/ProjectDeletedEventTest.java
@@ -0,0 +1,99 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.deleteproject; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.gerrit.entities.Project; +import com.google.gerrit.entities.Project.NameKey; +import com.google.gerrit.extensions.registration.DynamicItem; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.events.EventDispatcher; +import com.google.gerrit.server.project.ProjectResource; +import com.google.gerrit.server.project.ProjectState; +import com.google.inject.Provider; +import com.googlesource.gerrit.plugins.deleteproject.DeleteProject.Input; +import com.googlesource.gerrit.plugins.deleteproject.cache.CacheDeleteHandler; +import com.googlesource.gerrit.plugins.deleteproject.database.DatabaseDeleteHandler; +import com.googlesource.gerrit.plugins.deleteproject.fs.FilesystemDeleteHandler; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ProjectDeletedEventTest { + + private static final NameKey PROJECT_NAME_KEY = Project.nameKey("test-project"); + private static final String INSTANCE_ID = "test-instance-id"; + + @Mock private DatabaseDeleteHandler dbHandler; + @Mock private FilesystemDeleteHandler fsHandler; + @Mock private CacheDeleteHandler cacheHandler; + @Mock private Provider<CurrentUser> userProvider; + @Mock private DeleteLog deleteLog; + @Mock private DeletePreconditions preConditions; + @Mock private Configuration cfg; + @Mock private EventDispatcher dispatcher; + @Mock private DynamicItem<EventDispatcher> dispatcherProvider; + @Mock private HideProject hideProject; + @Mock private IdentifiedUser currentUser; + @Mock private ProjectState state; + @Captor private ArgumentCaptor<ProjectDeletedEvent> projectDeletedEventCaptor; + + private Project project = Project.builder(PROJECT_NAME_KEY).build(); + + private DeleteProject objectUnderTest; + + @Before + public void setup() throws Exception { + when(dispatcherProvider.get()).thenReturn(dispatcher); + when(userProvider.get()).thenReturn(currentUser); + when(state.getProject()).thenReturn(project); + objectUnderTest = + new DeleteProject( + dbHandler, + fsHandler, + cacheHandler, + userProvider, + deleteLog, + preConditions, + cfg, + hideProject, + dispatcherProvider, + INSTANCE_ID); + } + + @Test + public void shouldSendProjectDeletedEventAfterProjectDeletion() throws Exception { + Input input = new Input(); + input.force = false; + input.preserve = false; + objectUnderTest.doDelete(new ProjectResource(state, currentUser), input); + + verify(dispatcher).postEvent(eq(PROJECT_NAME_KEY), projectDeletedEventCaptor.capture()); + ProjectDeletedEvent event = projectDeletedEventCaptor.getValue(); + assertThat(event.instanceId).isEqualTo(INSTANCE_ID); + assertThat(event.getProjectNameKey()).isEqualTo(PROJECT_NAME_KEY); + assertThat(event.type).isEqualTo(ProjectDeletedEvent.TYPE); + } +}