import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
-import static org.sonar.api.utils.DateUtils.formatDateTime;
+import static org.sonar.server.webhook.ws.WebhookWsSupport.copyDtoToProtobuf;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
public class WebhookDeliveriesAction implements WebhooksWsAction {
WebService.NewAction action = controller.createAction("deliveries")
.setSince("6.2")
.setDescription("Get the recent deliveries for a specified project or Compute Engine task.<br/>" +
- "Require 'Administer System' permission.<br/>" +
+ "Require 'Administer' permission on the related project.<br/>" +
"Note that additional information are returned by api/webhooks/delivery.")
.setResponseExample(Resources.getResource(this.getClass(), "example-deliveries.json"))
.setInternal(true)
private static class Data {
private final ComponentDto component;
- private final List<WebhookDeliveryLiteDto> deliveries;
+ private final List<WebhookDeliveryLiteDto> deliveryDtos;
Data(@Nullable ComponentDto component, List<WebhookDeliveryLiteDto> deliveries) {
- this.deliveries = deliveries;
+ this.deliveryDtos = deliveries;
if (deliveries.isEmpty()) {
this.component = null;
} else {
void writeTo(Request request, Response response) {
Webhooks.DeliveriesWsResponse.Builder responseBuilder = Webhooks.DeliveriesWsResponse.newBuilder();
Webhooks.Delivery.Builder deliveryBuilder = Webhooks.Delivery.newBuilder();
- for (WebhookDeliveryLiteDto dto : deliveries) {
- deliveryBuilder
- .clear()
- .setId(dto.getUuid())
- .setAt(formatDateTime(dto.getCreatedAt()))
- .setName(dto.getName())
- .setUrl(dto.getUrl())
- .setSuccess(dto.isSuccess())
- .setCeTaskId(dto.getCeTaskUuid())
- .setComponentKey(component.getKey());
- if (dto.getHttpStatus() != null) {
- deliveryBuilder.setHttpStatus(dto.getHttpStatus());
- }
- if (dto.getDurationMs() != null) {
- deliveryBuilder.setDurationMs(dto.getDurationMs());
- }
+ for (WebhookDeliveryLiteDto dto : deliveryDtos) {
+ copyDtoToProtobuf(component, dto, deliveryBuilder);
responseBuilder.addDeliveries(deliveryBuilder);
}
writeProtobuf(responseBuilder.build(), request, response);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.webhook.ws;
+
+import com.google.common.io.Resources;
+import java.util.Optional;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.webhook.WebhookDeliveryDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.Webhooks;
+
+import static java.util.Objects.requireNonNull;
+import static org.sonar.server.webhook.ws.WebhookWsSupport.copyDtoToProtobuf;
+import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+
+public class WebhookDeliveryAction implements WebhooksWsAction {
+
+ private static final String PARAM_ID = "deliveryId";
+
+ private final DbClient dbClient;
+ private final UserSession userSession;
+ private final ComponentFinder componentFinder;
+
+ public WebhookDeliveryAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder) {
+ this.dbClient = dbClient;
+ this.userSession = userSession;
+ this.componentFinder = componentFinder;
+ }
+
+ @Override
+ public void define(WebService.NewController controller) {
+ WebService.NewAction action = controller.createAction("delivery")
+ .setSince("6.2")
+ .setDescription("Get a webhook delivery by its id.<br/>" +
+ "Require 'Administer System' permission.<br/>" +
+ "Note that additional information are returned by api/webhooks/delivery.")
+ .setResponseExample(Resources.getResource(this.getClass(), "example-delivery.json"))
+ .setInternal(true)
+ .setHandler(this);
+
+ action.createParam(PARAM_ID)
+ .setDescription("Id of delivery")
+ .setExampleValue(Uuids.UUID_EXAMPLE_06);
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ // fail-fast if not logged in
+ userSession.checkLoggedIn();
+
+ Data data = loadFromDatabase(request.mandatoryParam(PARAM_ID));
+ data.ensureAdminPermission(userSession);
+ data.writeTo(request, response);
+ }
+
+ private Data loadFromDatabase(String deliveryUuid) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ Optional<WebhookDeliveryDto> delivery = dbClient.webhookDeliveryDao().selectByUuid(dbSession, deliveryUuid);
+ checkFoundWithOptional(delivery, "Webhook delivery not found");
+ ComponentDto component = componentFinder.getByUuid(dbSession, delivery.get().getComponentUuid());
+ return new Data(component, delivery.get());
+ }
+ }
+
+ private static class Data {
+ private final ComponentDto component;
+ private final WebhookDeliveryDto deliveryDto;
+
+ Data(ComponentDto component, WebhookDeliveryDto delivery) {
+ this.deliveryDto = requireNonNull(delivery);
+ this.component = requireNonNull(component);
+ }
+
+ void ensureAdminPermission(UserSession userSession) {
+ userSession.checkComponentUuidPermission(UserRole.ADMIN, component.uuid());
+ }
+
+ void writeTo(Request request, Response response) {
+ Webhooks.DeliveryWsResponse.Builder responseBuilder = Webhooks.DeliveryWsResponse.newBuilder();
+ Webhooks.Delivery.Builder deliveryBuilder = Webhooks.Delivery.newBuilder();
+ copyDtoToProtobuf(component, deliveryDto, deliveryBuilder);
+ responseBuilder.setDelivery(deliveryBuilder);
+
+ writeProtobuf(responseBuilder.build(), request, response);
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.webhook.ws;
+
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.webhook.WebhookDeliveryDto;
+import org.sonar.db.webhook.WebhookDeliveryLiteDto;
+import org.sonarqube.ws.Webhooks;
+
+import static org.sonar.api.utils.DateUtils.formatDateTime;
+
+class WebhookWsSupport {
+ private WebhookWsSupport() {
+ // only statics
+ }
+
+ static Webhooks.Delivery.Builder copyDtoToProtobuf(ComponentDto component, WebhookDeliveryLiteDto dto, Webhooks.Delivery.Builder builder) {
+ builder
+ .clear()
+ .setId(dto.getUuid())
+ .setAt(formatDateTime(dto.getCreatedAt()))
+ .setName(dto.getName())
+ .setUrl(dto.getUrl())
+ .setSuccess(dto.isSuccess())
+ .setCeTaskId(dto.getCeTaskUuid())
+ .setComponentKey(component.getKey());
+ if (dto.getHttpStatus() != null) {
+ builder.setHttpStatus(dto.getHttpStatus());
+ }
+ if (dto.getDurationMs() != null) {
+ builder.setDurationMs(dto.getDurationMs());
+ }
+ return builder;
+ }
+
+ static Webhooks.Delivery.Builder copyDtoToProtobuf(ComponentDto component, WebhookDeliveryDto dto, Webhooks.Delivery.Builder builder) {
+ copyDtoToProtobuf(component, (WebhookDeliveryLiteDto) dto, builder);
+ builder.setPayload(dto.getPayload());
+ if (dto.getErrorStacktrace() != null) {
+ builder.setErrorStacktrace(dto.getErrorStacktrace());
+ }
+ return builder;
+ }
+}
protected void configureModule() {
add(
WebhooksWs.class,
+ WebhookDeliveryAction.class,
WebhookDeliveriesAction.class);
}
}
--- /dev/null
+{
+ "delivery": {
+ "id": "d1",
+ "componentKey": "my-project",
+ "ceTaskId": "task-1",
+ "name": "Jenkins",
+ "url": "http://jenkins",
+ "at": "2017-07-14T04:40:00+0200",
+ "success": true,
+ "httpStatus": 200,
+ "durationMs": 10,
+ "payload": "{\"status\"=\"SUCCESS\"}"
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.webhook.ws;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.webhook.WebhookDeliveryDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.Webhooks;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.component.ComponentTesting.newProjectDto;
+import static org.sonar.db.webhook.WebhookDbTesting.newWebhookDeliveryDto;
+import static org.sonar.test.JsonAssert.assertJson;
+
+public class WebhookDeliveryActionTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+
+ @Rule
+ public DbTester db = DbTester.create(System2.INSTANCE);
+
+ private DbClient dbClient = db.getDbClient();
+ private WsActionTester ws;
+ private ComponentDto project;
+
+ @Before
+ public void setUp() {
+ ComponentFinder componentFinder = new ComponentFinder(dbClient);
+ WebhookDeliveryAction underTest = new WebhookDeliveryAction(dbClient, userSession, componentFinder);
+ ws = new WsActionTester(underTest);
+ project = db.components().insertComponent(newProjectDto().setKey("my-project"));
+ }
+
+ @Test
+ public void test_definition() {
+ assertThat(ws.getDef().params()).extracting(WebService.Param::key).containsOnly("deliveryId");
+ assertThat(ws.getDef().isPost()).isFalse();
+ assertThat(ws.getDef().isInternal()).isTrue();
+ assertThat(ws.getDef().responseExampleAsString()).isNotEmpty();
+ }
+
+ @Test
+ public void throw_UnauthorizedException_if_anonymous() {
+ expectedException.expect(UnauthorizedException.class);
+
+ ws.newRequest().execute();
+ }
+
+ @Test
+ public void return_404_if_delivery_does_not_exist() throws Exception {
+ userSession.login();
+
+ expectedException.expect(NotFoundException.class);
+
+ ws.newRequest()
+ .setMediaType(MediaTypes.PROTOBUF)
+ .setParam("deliveryId", "does_not_exist")
+ .execute();
+ }
+
+ @Test
+ public void load_the_delivery_of_example() throws Exception {
+ WebhookDeliveryDto dto = newWebhookDeliveryDto()
+ .setUuid("d1")
+ .setComponentUuid(project.uuid())
+ .setCeTaskUuid("task-1")
+ .setName("Jenkins")
+ .setUrl("http://jenkins")
+ .setCreatedAt(1_500_000_000_000L)
+ .setSuccess(true)
+ .setDurationMs(10)
+ .setHttpStatus(200)
+ .setPayload("{\"status\"=\"SUCCESS\"}");
+ dbClient.webhookDeliveryDao().insert(db.getSession(), dto);
+ db.commit();
+ userSession.login().addProjectUuidPermissions(UserRole.ADMIN, project.uuid());
+
+ String json = ws.newRequest()
+ .setParam("deliveryId", dto.getUuid())
+ .execute()
+ .getInput();
+
+ assertJson(json).isSimilarTo(ws.getDef().responseExampleAsString());
+ }
+
+ @Test
+ public void return_delivery_that_failed_to_be_sent() throws Exception {
+ WebhookDeliveryDto dto = newWebhookDeliveryDto()
+ .setComponentUuid(project.uuid())
+ .setSuccess(false)
+ .setHttpStatus(null)
+ .setDurationMs(null)
+ .setErrorStacktrace("IOException -> can not connect");
+ dbClient.webhookDeliveryDao().insert(db.getSession(), dto);
+ db.commit();
+ userSession.login().addProjectUuidPermissions(UserRole.ADMIN, project.uuid());
+
+ Webhooks.DeliveryWsResponse response = Webhooks.DeliveryWsResponse.parseFrom(ws.newRequest()
+ .setMediaType(MediaTypes.PROTOBUF)
+ .setParam("deliveryId", dto.getUuid())
+ .execute()
+ .getInputStream());
+
+ Webhooks.Delivery actual = response.getDelivery();
+ assertThat(actual.hasHttpStatus()).isFalse();
+ assertThat(actual.hasDurationMs()).isFalse();
+ assertThat(actual.getErrorStacktrace()).isEqualTo(dto.getErrorStacktrace());
+ }
+
+ @Test
+ public void throw_ForbiddenException_if_not_admin_of_project() throws Exception {
+ WebhookDeliveryDto dto = newWebhookDeliveryDto()
+ .setComponentUuid(project.uuid());
+ dbClient.webhookDeliveryDao().insert(db.getSession(), dto);
+ db.commit();
+ userSession.login().addProjectUuidPermissions(UserRole.USER, project.uuid());
+
+ expectedException.expect(ForbiddenException.class);
+ expectedException.expectMessage("Insufficient privileges");
+
+ ws.newRequest()
+ .setParam("deliveryId", dto.getUuid())
+ .execute();
+ }
+}
public class WebhooksWsModuleTest {
+ private WebhooksWsModule underTest = new WebhooksWsModule();
+
@Test
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
- new WebhooksWsModule().configure(container);
- assertThat(container.size()).isEqualTo(2 + 2);
+ underTest.configure(container);
+ assertThat(container.size()).isEqualTo(2 + 3);
}
}
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.WsConnector;
+/**
+ * @since 6.2
+ */
public class WebhooksService extends BaseService {
public WebhooksService(WsConnector wsConnector) {
super(wsConnector, "api/webhooks");
}
+ public Webhooks.DeliveryWsResponse delivery(String deliveryId) {
+ GetRequest httpRequest = new GetRequest(path("delivery"))
+ .setParam("deliveryId", deliveryId);
+ return call(httpRequest, Webhooks.DeliveryWsResponse.parser());
+ }
+
/**
* @throws org.sonarqube.ws.client.HttpException if HTTP status code is not 2xx.
*/
repeated Delivery deliveries = 1;
}
+// WS api/webhooks/delivery
+message DeliveryWsResponse {
+ optional Delivery delivery = 1;
+}
+
message Delivery {
optional string id = 1;
optional string componentKey = 2;
optional bool success = 7;
optional int32 httpStatus = 8;
optional int32 durationMs = 9;
+ optional string payload = 10;
+ optional string errorStacktrace = 11;
}