Merge "Send project-deleted event after project deletion"
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/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);
+ }
+}