Skip to content

Commit

Permalink
SONAR-9871 move webhook code to use it in web
Browse files Browse the repository at this point in the history
as well as we use it in CE report processing
  • Loading branch information
sns-seb committed Oct 17, 2017
1 parent 020273d commit 3c8d83b
Show file tree
Hide file tree
Showing 25 changed files with 418 additions and 220 deletions.
Expand Up @@ -117,11 +117,13 @@
import org.sonar.server.computation.task.projectanalysis.source.SourceLinesRepositoryImpl; import org.sonar.server.computation.task.projectanalysis.source.SourceLinesRepositoryImpl;
import org.sonar.server.computation.task.projectanalysis.step.ReportComputationSteps; import org.sonar.server.computation.task.projectanalysis.step.ReportComputationSteps;
import org.sonar.server.computation.task.projectanalysis.step.SmallChangesetQualityGateSpecialCase; import org.sonar.server.computation.task.projectanalysis.step.SmallChangesetQualityGateSpecialCase;
import org.sonar.server.computation.task.projectanalysis.webhook.WebhookModule; import org.sonar.server.computation.task.projectanalysis.webhook.WebhookPayloadFactoryImpl;
import org.sonar.server.computation.task.projectanalysis.webhook.WebhookPostTask;
import org.sonar.server.computation.task.step.ComputationStepExecutor; import org.sonar.server.computation.task.step.ComputationStepExecutor;
import org.sonar.server.computation.task.step.ComputationSteps; import org.sonar.server.computation.task.step.ComputationSteps;
import org.sonar.server.computation.taskprocessor.MutableTaskResultHolderImpl; import org.sonar.server.computation.taskprocessor.MutableTaskResultHolderImpl;
import org.sonar.server.view.index.ViewIndex; import org.sonar.server.view.index.ViewIndex;
import org.sonar.server.webhook.WebhookModule;


public final class ProjectAnalysisTaskContainerPopulator implements ContainerPopulator<TaskContainer> { public final class ProjectAnalysisTaskContainerPopulator implements ContainerPopulator<TaskContainer> {
private static final ReportAnalysisComponentProvider[] NO_REPORT_ANALYSIS_COMPONENT_PROVIDERS = new ReportAnalysisComponentProvider[0]; private static final ReportAnalysisComponentProvider[] NO_REPORT_ANALYSIS_COMPONENT_PROVIDERS = new ReportAnalysisComponentProvider[0];
Expand Down Expand Up @@ -271,7 +273,9 @@ private static List<Object> componentClasses() {
SmallChangesetQualityGateSpecialCase.class, SmallChangesetQualityGateSpecialCase.class,


// webhooks // webhooks
WebhookModule.class); WebhookModule.class,
WebhookPayloadFactoryImpl.class,
WebhookPostTask.class);
} }


} }
Expand Up @@ -20,6 +20,7 @@
package org.sonar.server.computation.task.projectanalysis.webhook; package org.sonar.server.computation.task.projectanalysis.webhook;


import org.sonar.api.ce.posttask.PostProjectAnalysisTask; import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
import org.sonar.server.webhook.WebhookPayload;


@FunctionalInterface @FunctionalInterface
public interface WebhookPayloadFactory { public interface WebhookPayloadFactory {
Expand Down
Expand Up @@ -35,6 +35,7 @@
import org.sonar.api.platform.Server; import org.sonar.api.platform.Server;
import org.sonar.api.utils.System2; import org.sonar.api.utils.System2;
import org.sonar.api.utils.text.JsonWriter; import org.sonar.api.utils.text.JsonWriter;
import org.sonar.server.webhook.WebhookPayload;


import static java.lang.String.format; import static java.lang.String.format;
import static org.sonar.core.config.WebhookProperties.ANALYSIS_PROPERTY_PREFIX; import static org.sonar.core.config.WebhookProperties.ANALYSIS_PROPERTY_PREFIX;
Expand Down
Expand Up @@ -19,82 +19,31 @@
*/ */
package org.sonar.server.computation.task.projectanalysis.webhook; package org.sonar.server.computation.task.projectanalysis.webhook;


import com.google.common.collect.Iterables;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.sonar.api.ce.posttask.PostProjectAnalysisTask; import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
import org.sonar.api.config.Configuration; import org.sonar.api.config.Configuration;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.config.WebhookProperties;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.server.computation.task.projectanalysis.component.ConfigurationRepository; import org.sonar.server.computation.task.projectanalysis.component.ConfigurationRepository;

import org.sonar.server.webhook.WebHooks;
import static java.lang.String.format;
import static org.sonar.core.config.WebhookProperties.MAX_WEBHOOKS_PER_TYPE;


public class WebhookPostTask implements PostProjectAnalysisTask { public class WebhookPostTask implements PostProjectAnalysisTask {


private static final Logger LOGGER = Loggers.get(WebhookPostTask.class);

private final ConfigurationRepository configRepository; private final ConfigurationRepository configRepository;
private final WebhookPayloadFactory payloadFactory; private final WebhookPayloadFactory payloadFactory;
private final WebhookCaller caller; private final WebHooks webHooks;
private final WebhookDeliveryStorage deliveryStorage;


public WebhookPostTask(ConfigurationRepository configRepository, WebhookPayloadFactory payloadFactory, public WebhookPostTask(ConfigurationRepository configRepository, WebhookPayloadFactory payloadFactory, WebHooks webHooks) {
WebhookCaller caller, WebhookDeliveryStorage deliveryStorage) {
this.configRepository = configRepository; this.configRepository = configRepository;
this.payloadFactory = payloadFactory; this.payloadFactory = payloadFactory;
this.caller = caller; this.webHooks = webHooks;
this.deliveryStorage = deliveryStorage;
} }


@Override @Override
public void finished(ProjectAnalysis analysis) { public void finished(ProjectAnalysis analysis) {
Configuration config = configRepository.getConfiguration(); Configuration config = configRepository.getConfiguration();


Iterable<String> webhookProps = Iterables.concat( webHooks.sendProjectAnalysisUpdate(
getWebhookProperties(config, WebhookProperties.GLOBAL_KEY), config,
getWebhookProperties(config, WebhookProperties.PROJECT_KEY)); new WebHooks.Analysis(analysis.getProject().getUuid(), analysis.getCeTask().getId()),
if (!Iterables.isEmpty(webhookProps)) { () -> payloadFactory.create(analysis));
process(config, analysis, webhookProps);
deliveryStorage.purge(analysis.getProject().getUuid());
}
}

private static List<String> getWebhookProperties(Configuration config, String propertyKey) {
String[] webhookIds = config.getStringArray(propertyKey);
return Arrays.stream(webhookIds)
.map(webhookId -> format("%s.%s", propertyKey, webhookId))
.limit(MAX_WEBHOOKS_PER_TYPE)
.collect(MoreCollectors.toList(webhookIds.length));
} }


private void process(Configuration config, ProjectAnalysis analysis, Iterable<String> webhookProperties) {
WebhookPayload payload = payloadFactory.create(analysis);
for (String webhookProp : webhookProperties) {
String name = config.get(format("%s.%s", webhookProp, WebhookProperties.NAME_FIELD)).orElse(null);
String url = config.get(format("%s.%s", webhookProp, WebhookProperties.URL_FIELD)).orElse(null);
// as webhooks are defined as property sets, we can't ensure validity of fields on creation.
if (name != null && url != null) {
Webhook webhook = new Webhook(analysis.getProject().getUuid(), analysis.getCeTask().getId(), name, url);
WebhookDelivery delivery = caller.call(webhook, payload);
log(delivery);
deliveryStorage.persist(delivery);
}
}
}

private static void log(WebhookDelivery delivery) {
Optional<String> error = delivery.getErrorMessage();
if (error.isPresent()) {
LOGGER.debug("Failed to send webhook '{}' | url={} | message={}",
delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), error.get());
} else {
LOGGER.debug("Sent webhook '{}' | url={} | time={}ms | status={}",
delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), delivery.getDurationInMs().orElse(-1), delivery.getHttpStatus().orElse(-1));
}
}
} }
Expand Up @@ -221,6 +221,7 @@
import org.sonar.server.view.index.ViewIndex; import org.sonar.server.view.index.ViewIndex;
import org.sonar.server.view.index.ViewIndexDefinition; import org.sonar.server.view.index.ViewIndexDefinition;
import org.sonar.server.view.index.ViewIndexer; import org.sonar.server.view.index.ViewIndexer;
import org.sonar.server.webhook.WebhookModule;
import org.sonar.server.webhook.ws.WebhooksWsModule; import org.sonar.server.webhook.ws.WebhooksWsModule;
import org.sonar.server.ws.DeprecatedPropertiesWsFilter; import org.sonar.server.ws.DeprecatedPropertiesWsFilter;
import org.sonar.server.ws.WebServiceEngine; import org.sonar.server.ws.WebServiceEngine;
Expand Down Expand Up @@ -536,6 +537,7 @@ protected void configureLevel() {
RootWsModule.class, RootWsModule.class,


// webhooks // webhooks
WebhookModule.class,
WebhooksWsModule.class, WebhooksWsModule.class,


// Http Request ID // Http Request ID
Expand Down
@@ -0,0 +1,65 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info 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;

import java.util.Objects;
import java.util.function.Supplier;
import org.sonar.api.config.Configuration;

import static java.util.Objects.requireNonNull;

public interface WebHooks {
void sendProjectAnalysisUpdate(Configuration configuration, Analysis analysis, Supplier<WebhookPayload> payloadSupplier);

final class Analysis {
private final String projectUuid;
private final String ceTaskUuid;

public Analysis(String projectUuid, String ceTaskUuid) {
this.projectUuid = requireNonNull(projectUuid, "projectUuid can't be null");
this.ceTaskUuid = requireNonNull(ceTaskUuid, "ceTaskUuid can't be null");
}

public String getProjectUuid() {
return projectUuid;
}

public String getCeTaskUuid() {
return ceTaskUuid;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Analysis analysis = (Analysis) o;
return projectUuid.equals(analysis.projectUuid) && ceTaskUuid.equals(analysis.ceTaskUuid);
}

@Override
public int hashCode() {
return Objects.hash(projectUuid, ceTaskUuid);
}
}
}
@@ -0,0 +1,101 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info 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;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.sonar.api.config.Configuration;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.config.WebhookProperties;
import org.sonar.core.util.stream.MoreCollectors;

import static java.lang.String.format;
import static org.sonar.core.config.WebhookProperties.MAX_WEBHOOKS_PER_TYPE;

public class WebHooksImpl implements WebHooks {

private static final Logger LOGGER = Loggers.get(WebHooksImpl.class);
private static final String WEBHOOK_PROPERTY_FORMAT = "%s.%s";

private final WebhookCaller caller;
private final WebhookDeliveryStorage deliveryStorage;

public WebHooksImpl(WebhookCaller caller, WebhookDeliveryStorage deliveryStorage) {
this.caller = caller;
this.deliveryStorage = deliveryStorage;
}

@Override
public void sendProjectAnalysisUpdate(Configuration configuration, Analysis analysis, Supplier<WebhookPayload> payloadSupplier) {
List<Webhook> webhooks = loadWebhooks(analysis, configuration);
if (webhooks.isEmpty()) {
return;
}

WebhookPayload payload = payloadSupplier.get();
webhooks.forEach(webhook -> {
WebhookDelivery delivery = caller.call(webhook, payload);
log(delivery);
deliveryStorage.persist(delivery);
});
deliveryStorage.purge(analysis.getProjectUuid());
}

private List<Webhook> loadWebhooks(Analysis analysis, Configuration config) {
return Stream.concat(
getWebhookProperties(config, WebhookProperties.GLOBAL_KEY).stream(),
getWebhookProperties(config, WebhookProperties.PROJECT_KEY).stream())
.map(
webHookProperty -> {
String name = config.get(format(WEBHOOK_PROPERTY_FORMAT, webHookProperty, WebhookProperties.NAME_FIELD)).orElse(null);
String url = config.get(format(WEBHOOK_PROPERTY_FORMAT, webHookProperty, WebhookProperties.URL_FIELD)).orElse(null);
if (name != null && url != null) {
return new Webhook(analysis.getProjectUuid(), analysis.getCeTaskUuid(), name, url);
}
return null;
})
.filter(Objects::nonNull)
.collect(MoreCollectors.toList());
}

private static List<String> getWebhookProperties(Configuration config, String propertyKey) {
String[] webhookIds = config.getStringArray(propertyKey);
return Arrays.stream(webhookIds)
.map(webhookId -> format(WEBHOOK_PROPERTY_FORMAT, propertyKey, webhookId))
.limit(MAX_WEBHOOKS_PER_TYPE)
.collect(MoreCollectors.toList(webhookIds.length));
}

private static void log(WebhookDelivery delivery) {
Optional<String> error = delivery.getErrorMessage();
if (error.isPresent()) {
LOGGER.debug("Failed to send webhook '{}' | url={} | message={}",
delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), error.get());
} else {
LOGGER.debug("Sent webhook '{}' | url={} | time={}ms | status={}",
delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), delivery.getDurationInMs().orElse(-1), delivery.getHttpStatus().orElse(-1));
}
}
}
Expand Up @@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
package org.sonar.server.computation.task.projectanalysis.webhook; package org.sonar.server.webhook;


import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;


Expand Down
Expand Up @@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
package org.sonar.server.computation.task.projectanalysis.webhook; package org.sonar.server.webhook;


public interface WebhookCaller { public interface WebhookCaller {


Expand Down
Expand Up @@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
package org.sonar.server.computation.task.projectanalysis.webhook; package org.sonar.server.webhook;


import java.io.IOException; import java.io.IOException;
import okhttp3.Credentials; import okhttp3.Credentials;
Expand Down
Expand Up @@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
package org.sonar.server.computation.task.projectanalysis.webhook; package org.sonar.server.webhook;


import java.util.Optional; import java.util.Optional;
import javax.annotation.Nullable; import javax.annotation.Nullable;
Expand Down
Expand Up @@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
package org.sonar.server.computation.task.projectanalysis.webhook; package org.sonar.server.webhook;


import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import org.sonar.api.ce.ComputeEngineSide; import org.sonar.api.ce.ComputeEngineSide;
Expand Down
Expand Up @@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
package org.sonar.server.computation.task.projectanalysis.webhook; package org.sonar.server.webhook;


import org.sonar.core.platform.Module; import org.sonar.core.platform.Module;


Expand All @@ -27,7 +27,6 @@ protected void configureModule() {
add( add(
WebhookCallerImpl.class, WebhookCallerImpl.class,
WebhookDeliveryStorage.class, WebhookDeliveryStorage.class,
WebhookPayloadFactoryImpl.class, WebHooksImpl.class);
WebhookPostTask.class);
} }
} }
Expand Up @@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
package org.sonar.server.computation.task.projectanalysis.webhook; package org.sonar.server.webhook;


import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;


Expand Down
Expand Up @@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
@ParametersAreNonnullByDefault @ParametersAreNonnullByDefault
package org.sonar.server.webhook.ws; package org.sonar.server.webhook;


import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault;


0 comments on commit 3c8d83b

Please sign in to comment.