]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15769 added metrics for integration with devops platforms
authorLukasz Jarocki <lukasz.jarocki@sonarsource.com>
Wed, 8 Dec 2021 08:35:06 +0000 (09:35 +0100)
committerLukasz Jarocki <lukasz.jarocki@sonarsource.com>
Mon, 13 Dec 2021 14:22:35 +0000 (15:22 +0100)
30 files changed:
build.gradle
server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/AzureDevOpsValidator.java [new file with mode: 0644]
server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudValidator.java [new file with mode: 0644]
server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/BitbucketServerSettingsValidator.java [new file with mode: 0644]
server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubGlobalSettingsValidator.java [new file with mode: 0644]
server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabGlobalSettingsValidator.java [new file with mode: 0644]
server/sonar-alm-client/src/test/java/org/sonar/alm/client/azure/AzureDevOpsValidatorTest.java [new file with mode: 0644]
server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudValidatorTest.java [new file with mode: 0644]
server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucketserver/BitbucketServerSettingsValidatorTest.java [new file with mode: 0644]
server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubGlobalSettingsValidatorTest.java [new file with mode: 0644]
server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabGlobalSettingsValidatorTest.java [new file with mode: 0644]
server/sonar-alm-client/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker [new file with mode: 0644]
server/sonar-webserver-monitoring/build.gradle [new file with mode: 0644]
server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ServerMonitoringMetrics.java [new file with mode: 0644]
server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/devops/DevOpsPlatformsMetricsCollector.java [new file with mode: 0644]
server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ServerMonitoringMetricsTest.java [new file with mode: 0644]
server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/devops/DevOpsPlatformsMetricsCollectorTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/validator/BitbucketServerSettingsValidator.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/validator/GithubGlobalSettingsValidator.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/validator/GitlabGlobalSettingsValidator.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/AlmIntegrationsWSModule.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/ValidateAction.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/validator/BitbucketServerSettingsValidatorTest.java [deleted file]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/validator/GithubGlobalSettingsValidatorTest.java [deleted file]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/validator/GitlabGlobalSettingsValidatorTest.java [deleted file]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/ValidateActionTest.java
server/sonar-webserver/build.gradle
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java
settings.gradle

index 6e8237db23467dfa616d56f50bf371d92c382e61..3317c91db1816909287a27a4e9b845663345aec0 100644 (file)
@@ -307,6 +307,8 @@ subprojects {
       dependency('com.googlecode.json-simple:json-simple:1.1.1') {
         exclude 'junit:junit'
       }
+      dependency 'io.prometheus:simpleclient:0.12.0'
+      dependency 'io.prometheus:simpleclient_servlet:0.12.0'
       dependency 'com.google.code.findbugs:jsr305:3.0.2'
       dependency 'com.google.code.gson:gson:2.8.9'
       dependency('com.google.guava:guava:28.2-jre') {
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/AzureDevOpsValidator.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/AzureDevOpsValidator.java
new file mode 100644 (file)
index 0000000..d021b6c
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.alm.client.azure;
+
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.alm.setting.AlmSettingDto;
+import org.sonar.api.config.internal.Settings;
+
+import static java.util.Objects.requireNonNull;
+
+@ServerSide
+public class AzureDevOpsValidator {
+
+  private final AzureDevOpsHttpClient azureDevOpsHttpClient;
+  private final Settings settings;
+
+  public AzureDevOpsValidator(AzureDevOpsHttpClient azureDevOpsHttpClient, Settings settings) {
+    this.azureDevOpsHttpClient = azureDevOpsHttpClient;
+    this.settings = settings;
+  }
+
+  public void validate(AlmSettingDto dto) {
+    try {
+      azureDevOpsHttpClient.checkPAT(requireNonNull(dto.getUrl()),
+        requireNonNull(dto.getDecryptedPersonalAccessToken(settings.getEncryption())));
+    } catch (IllegalArgumentException e) {
+      throw new IllegalArgumentException("Invalid Azure URL or Personal Access Token", e);
+    }
+  }
+}
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudValidator.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudValidator.java
new file mode 100644 (file)
index 0000000..09fca29
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.alm.client.bitbucket.bitbucketcloud;
+
+import org.sonar.api.config.internal.Settings;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+import static java.util.Objects.requireNonNull;
+
+@ServerSide
+public class BitbucketCloudValidator {
+
+  private final BitbucketCloudRestClient bitbucketCloudRestClient;
+  private final Settings settings;
+
+  public BitbucketCloudValidator(BitbucketCloudRestClient bitbucketCloudRestClient, Settings settings) {
+    this.bitbucketCloudRestClient = bitbucketCloudRestClient;
+    this.settings = settings;
+  }
+
+  public void validate(AlmSettingDto dto) {
+    String clientId = requireNonNull(dto.getClientId());
+    String appId = requireNonNull(dto.getAppId());
+    String decryptedClientSecret = requireNonNull(dto.getDecryptedClientSecret(settings.getEncryption()));
+    bitbucketCloudRestClient.validate(clientId, decryptedClientSecret, appId);
+  }
+
+}
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/BitbucketServerSettingsValidator.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/BitbucketServerSettingsValidator.java
new file mode 100644 (file)
index 0000000..c4d0656
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.alm.client.bitbucketserver;
+
+import org.sonar.api.config.internal.Encryption;
+import org.sonar.api.config.internal.Settings;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+@ServerSide
+public class BitbucketServerSettingsValidator {
+  private final BitbucketServerRestClient bitbucketServerRestClient;
+  private final Encryption encryption;
+
+  public BitbucketServerSettingsValidator(BitbucketServerRestClient bitbucketServerRestClient, Settings settings) {
+    this.bitbucketServerRestClient = bitbucketServerRestClient;
+    this.encryption = settings.getEncryption();
+  }
+
+  public void validate(AlmSettingDto almSettingDto) {
+    String bitbucketUrl = almSettingDto.getUrl();
+    String bitbucketToken = almSettingDto.getDecryptedPersonalAccessToken(encryption);
+    if (bitbucketUrl == null || bitbucketToken == null) {
+      throw new IllegalArgumentException("Your global Bitbucket Server configuration is incomplete.");
+    }
+
+    bitbucketServerRestClient.validateUrl(bitbucketUrl);
+    bitbucketServerRestClient.validateToken(bitbucketUrl, bitbucketToken);
+    bitbucketServerRestClient.validateReadPermission(bitbucketUrl, bitbucketToken);
+  }
+}
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubGlobalSettingsValidator.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubGlobalSettingsValidator.java
new file mode 100644 (file)
index 0000000..3542e7d
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.alm.client.github;
+
+import java.util.Optional;
+import org.sonar.alm.client.github.config.GithubAppConfiguration;
+import org.sonar.api.config.internal.Encryption;
+import org.sonar.api.config.internal.Settings;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+import static org.apache.commons.lang.StringUtils.isBlank;
+
+@ServerSide
+public class GithubGlobalSettingsValidator {
+
+  private final Encryption encryption;
+  private final GithubApplicationClient githubApplicationClient;
+
+  public GithubGlobalSettingsValidator(GithubApplicationClientImpl githubApplicationClient, Settings settings) {
+    this.encryption = settings.getEncryption();
+    this.githubApplicationClient = githubApplicationClient;
+  }
+
+  public GithubAppConfiguration validate(AlmSettingDto settings) {
+    long appId;
+    try {
+      appId = Long.parseLong(Optional.ofNullable(settings.getAppId()).orElseThrow(() -> new IllegalArgumentException("Missing appId")));
+    } catch (NumberFormatException e) {
+      throw new IllegalArgumentException("Invalid appId; " + e.getMessage());
+    }
+    if (isBlank(settings.getClientId())) {
+      throw new IllegalArgumentException("Missing Client Id");
+    }
+    if (isBlank(settings.getDecryptedClientSecret(encryption))) {
+      throw new IllegalArgumentException("Missing Client Secret");
+    }
+    GithubAppConfiguration configuration = new GithubAppConfiguration(appId, settings.getDecryptedPrivateKey(encryption),
+      settings.getUrl());
+
+    githubApplicationClient.checkApiEndpoint(configuration);
+    githubApplicationClient.checkAppPermissions(configuration);
+
+    return configuration;
+  }
+}
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabGlobalSettingsValidator.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabGlobalSettingsValidator.java
new file mode 100644 (file)
index 0000000..4d06956
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.alm.client.gitlab;
+
+import org.sonar.api.config.internal.Encryption;
+import org.sonar.api.config.internal.Settings;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+@ServerSide
+public class GitlabGlobalSettingsValidator {
+
+  private final Encryption encryption;
+  private final GitlabHttpClient gitlabHttpClient;
+
+  public GitlabGlobalSettingsValidator(GitlabHttpClient gitlabHttpClient, Settings settings) {
+    this.encryption = settings.getEncryption();
+    this.gitlabHttpClient = gitlabHttpClient;
+  }
+
+  public void validate(AlmSettingDto almSettingDto) {
+    String gitlabUrl = almSettingDto.getUrl();
+    String accessToken = almSettingDto.getDecryptedPersonalAccessToken(encryption);
+
+    if (gitlabUrl == null || accessToken == null) {
+      throw new IllegalArgumentException("Your Gitlab global configuration is incomplete.");
+    }
+
+    gitlabHttpClient.checkUrl(gitlabUrl);
+    gitlabHttpClient.checkToken(gitlabUrl, accessToken);
+    gitlabHttpClient.checkReadPermission(gitlabUrl, accessToken);
+    gitlabHttpClient.checkWritePermission(gitlabUrl, accessToken);
+  }
+
+}
diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/azure/AzureDevOpsValidatorTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/azure/AzureDevOpsValidatorTest.java
new file mode 100644 (file)
index 0000000..842624f
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.alm.client.azure;
+
+import org.junit.Test;
+import org.sonar.api.config.internal.Settings;
+import org.sonar.db.alm.setting.AlmSettingDto;
+import org.sonarqube.ws.AlmSettings;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class AzureDevOpsValidatorTest {
+
+  private final AzureDevOpsHttpClient azureDevOpsHttpClient = mock(AzureDevOpsHttpClient.class);
+  private final Settings settings = mock(Settings.class);
+  private final AzureDevOpsValidator underTest = new AzureDevOpsValidator(azureDevOpsHttpClient, settings);
+
+  @Test
+  public void validate_givenHttpClientThrowingException_throwException() {
+    AlmSettingDto dto = createMockDto();
+
+    doThrow(new IllegalArgumentException()).when(azureDevOpsHttpClient).checkPAT(any(), any());
+
+    assertThatExceptionOfType(IllegalArgumentException.class)
+      .isThrownBy(() -> underTest.validate(dto))
+      .withMessage("Invalid Azure URL or Personal Access Token");
+
+  }
+
+  @Test(expected = Test.None.class /* no exception expected */)
+  public void validate_givenHttpClientNotThrowingException_doesNotThrowException() {
+    AlmSettingDto dto = createMockDto();
+
+    underTest.validate(dto);
+  }
+
+  private AlmSettingDto createMockDto() {
+    AlmSettingDto dto = mock(AlmSettingDto.class);
+    when(dto.getUrl()).thenReturn("http://azure-devops-url.url");
+    when(dto.getDecryptedPersonalAccessToken(any())).thenReturn("decrypted-token");
+    return dto;
+  }
+}
diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudValidatorTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudValidatorTest.java
new file mode 100644 (file)
index 0000000..f450b70
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.alm.client.bitbucket.bitbucketcloud;
+
+import org.junit.Test;
+import org.sonar.api.config.internal.Settings;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class BitbucketCloudValidatorTest {
+
+  private final BitbucketCloudRestClient bitbucketCloudRestClient = mock(BitbucketCloudRestClient.class);
+  private final Settings settings = mock(Settings.class);
+
+  private final BitbucketCloudValidator underTest = new BitbucketCloudValidator(bitbucketCloudRestClient, settings);
+
+  private static final String EXAMPLE_APP_ID = "123";
+
+  @Test
+  public void validate_forwardsExceptionFromRestClient() {
+    AlmSettingDto dto = mock(AlmSettingDto.class);
+    when(dto.getAppId()).thenReturn(EXAMPLE_APP_ID);
+    when(dto.getClientId()).thenReturn("clientId");
+    when(dto.getDecryptedClientSecret(any())).thenReturn("secret");
+
+    doThrow(new IllegalArgumentException("Exception from bitbucket cloud rest client"))
+      .when(bitbucketCloudRestClient).validate(any(), any(), any());
+
+    assertThatExceptionOfType(IllegalArgumentException.class)
+      .isThrownBy(() -> underTest.validate(dto))
+      .withMessage("Exception from bitbucket cloud rest client");
+  }
+
+  @Test
+  public void validate_callsValidate() {
+    AlmSettingDto dto = mock(AlmSettingDto.class);
+    when(dto.getAppId()).thenReturn(EXAMPLE_APP_ID);
+    when(dto.getClientId()).thenReturn("clientId");
+    when(dto.getDecryptedClientSecret(any())).thenReturn("secret");
+
+    underTest.validate(dto);
+
+    verify(bitbucketCloudRestClient, times(1)).validate("clientId", "secret", EXAMPLE_APP_ID);
+  }
+}
diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucketserver/BitbucketServerSettingsValidatorTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucketserver/BitbucketServerSettingsValidatorTest.java
new file mode 100644 (file)
index 0000000..a30709b
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.alm.client.bitbucketserver;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.sonar.api.config.internal.Encryption;
+import org.sonar.api.config.internal.Settings;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class BitbucketServerSettingsValidatorTest {
+  private static final Encryption encryption = mock(Encryption.class);
+  private static final Settings settings = mock(Settings.class);
+
+  private final BitbucketServerRestClient bitbucketServerRestClient = mock(BitbucketServerRestClient.class);
+  private final BitbucketServerSettingsValidator underTest = new BitbucketServerSettingsValidator(bitbucketServerRestClient, settings);
+
+  @BeforeClass
+  public static void setUp() {
+    when(settings.getEncryption()).thenReturn(encryption);
+  }
+
+  @Test
+  public void validate_success() {
+    AlmSettingDto almSettingDto = createNewBitbucketDto("http://abc.com", "abc");
+    when(encryption.isEncrypted(any())).thenReturn(false);
+
+    underTest.validate(almSettingDto);
+
+    verify(bitbucketServerRestClient, times(1)).validateUrl("http://abc.com");
+    verify(bitbucketServerRestClient, times(1)).validateToken("http://abc.com", "abc");
+    verify(bitbucketServerRestClient, times(1)).validateReadPermission("http://abc.com", "abc");
+  }
+
+  @Test
+  public void validate_success_with_encrypted_token() {
+    String encryptedToken = "abc";
+    String decryptedToken = "decrypted-token";
+    AlmSettingDto almSettingDto = createNewBitbucketDto("http://abc.com", encryptedToken);
+    when(encryption.isEncrypted(encryptedToken)).thenReturn(true);
+    when(encryption.decrypt(encryptedToken)).thenReturn(decryptedToken);
+
+    underTest.validate(almSettingDto);
+
+    verify(bitbucketServerRestClient, times(1)).validateUrl("http://abc.com");
+    verify(bitbucketServerRestClient, times(1)).validateToken("http://abc.com", decryptedToken);
+    verify(bitbucketServerRestClient, times(1)).validateReadPermission("http://abc.com", decryptedToken);
+  }
+
+  @Test
+  public void validate_failure_on_incomplete_configuration() {
+    AlmSettingDto almSettingDto = createNewBitbucketDto(null, "abc");
+
+    assertThatThrownBy(() -> underTest.validate(almSettingDto))
+      .isInstanceOf(IllegalArgumentException.class);
+  }
+
+  @Test
+  public void validate_failure_on_bitbucket_server_api_error() {
+    doThrow(new IllegalArgumentException("error")).when(bitbucketServerRestClient).validateUrl(anyString());
+    AlmSettingDto almSettingDto = createNewBitbucketDto("http://abc.com", "abc");
+
+    assertThatThrownBy(() -> underTest.validate(almSettingDto))
+      .isInstanceOf(IllegalArgumentException.class);
+  }
+
+  private AlmSettingDto createNewBitbucketDto(String url, String pat) {
+    AlmSettingDto dto = new AlmSettingDto();
+    dto.setAlm(ALM.BITBUCKET);
+    dto.setUrl(url);
+    dto.setPersonalAccessToken(pat);
+    return dto;
+  }
+}
diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubGlobalSettingsValidatorTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubGlobalSettingsValidatorTest.java
new file mode 100644 (file)
index 0000000..317cfdc
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.alm.client.github;
+
+import javax.annotation.Nullable;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.sonar.alm.client.github.config.GithubAppConfiguration;
+import org.sonar.api.config.internal.Encryption;
+import org.sonar.api.config.internal.Settings;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class GithubGlobalSettingsValidatorTest {
+  private static final Encryption encryption = mock(Encryption.class);
+  private static final Settings settings = mock(Settings.class);
+
+  private static final String EXAMPLE_APP_ID = "123";
+  private static final String EXAMPLE_PRIVATE_KEY = "private_key";
+
+  private final GithubApplicationClientImpl appClient = mock(GithubApplicationClientImpl.class);
+  private final GithubGlobalSettingsValidator underTest = new GithubGlobalSettingsValidator(appClient, settings);
+
+  @BeforeClass
+  public static void setUp() {
+    when(settings.getEncryption()).thenReturn(encryption);
+  }
+
+  @Test
+  public void github_global_settings_validation() {
+    AlmSettingDto almSettingDto = createNewGithubDto("clientId", "clientSecret", EXAMPLE_APP_ID, EXAMPLE_PRIVATE_KEY);
+
+    when(encryption.isEncrypted(any())).thenReturn(false);
+
+    GithubAppConfiguration configuration = underTest.validate(almSettingDto);
+
+    ArgumentCaptor<GithubAppConfiguration> configurationArgumentCaptor = ArgumentCaptor.forClass(GithubAppConfiguration.class);
+    verify(appClient).checkApiEndpoint(configurationArgumentCaptor.capture());
+    verify(appClient).checkAppPermissions(configurationArgumentCaptor.capture());
+    assertThat(configuration.getId()).isEqualTo(configurationArgumentCaptor.getAllValues().get(0).getId());
+    assertThat(configuration.getId()).isEqualTo(configurationArgumentCaptor.getAllValues().get(1).getId());
+  }
+
+  @Test
+  public void github_global_settings_validation_with_encrypted_key() {
+    String encryptedKey = "encrypted-key";
+    String decryptedKey = "decrypted-key";
+    AlmSettingDto almSettingDto = createNewGithubDto("clientId", "clientSecret", EXAMPLE_APP_ID, encryptedKey);
+
+    when(encryption.isEncrypted(encryptedKey)).thenReturn(true);
+    when(encryption.decrypt(encryptedKey)).thenReturn(decryptedKey);
+
+    GithubAppConfiguration configuration = underTest.validate(almSettingDto);
+
+    ArgumentCaptor<GithubAppConfiguration> configurationArgumentCaptor = ArgumentCaptor.forClass(GithubAppConfiguration.class);
+    verify(appClient).checkApiEndpoint(configurationArgumentCaptor.capture());
+    verify(appClient).checkAppPermissions(configurationArgumentCaptor.capture());
+    assertThat(configuration.getId()).isEqualTo(configurationArgumentCaptor.getAllValues().get(0).getId());
+    assertThat(decryptedKey).isEqualTo(configurationArgumentCaptor.getAllValues().get(0).getPrivateKey());
+    assertThat(configuration.getId()).isEqualTo(configurationArgumentCaptor.getAllValues().get(1).getId());
+    assertThat(decryptedKey).isEqualTo(configurationArgumentCaptor.getAllValues().get(1).getPrivateKey());
+  }
+
+  @Test
+  public void github_validation_checks_invalid_appId() {
+    AlmSettingDto almSettingDto = createNewGithubDto("clientId", "clientSecret", "abc", null);
+
+    assertThatThrownBy(() -> underTest.validate(almSettingDto))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Invalid appId; For input string: \"abc\"");
+  }
+
+  @Test
+  public void github_validation_checks_missing_appId() {
+    AlmSettingDto almSettingDto = new AlmSettingDto();
+    almSettingDto.setAppId(null);
+
+    assertThatThrownBy(() -> underTest.validate(almSettingDto))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Missing appId");
+  }
+
+  @Test
+  public void github_validation_checks_missing_clientId() {
+    AlmSettingDto almSettingDto = createNewGithubDto(null, null, EXAMPLE_APP_ID, null);
+
+    assertThatThrownBy(() -> underTest.validate(almSettingDto))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Missing Client Id");
+  }
+
+  @Test
+  public void github_validation_checks_missing_clientSecret() {
+    AlmSettingDto almSettingDto = createNewGithubDto("clientId", null, EXAMPLE_APP_ID, null);
+
+    assertThatThrownBy(() -> underTest.validate(almSettingDto))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Missing Client Secret");
+
+  }
+
+  private AlmSettingDto createNewGithubDto(@Nullable String clientId, @Nullable String clientSecret,
+    @Nullable String appId, @Nullable String privateKey) {
+    AlmSettingDto dto = new AlmSettingDto();
+    dto.setAlm(ALM.GITHUB);
+    dto.setUrl("http://github-example-url.com");
+    dto.setClientId(clientId);
+    dto.setClientSecret(clientSecret);
+    dto.setAppId(appId);
+    dto.setPrivateKey(privateKey);
+    return dto;
+  }
+}
diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabGlobalSettingsValidatorTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabGlobalSettingsValidatorTest.java
new file mode 100644 (file)
index 0000000..3b03325
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.alm.client.gitlab;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator;
+import org.sonar.alm.client.gitlab.GitlabHttpClient;
+import org.sonar.api.config.internal.Encryption;
+import org.sonar.api.config.internal.Settings;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class GitlabGlobalSettingsValidatorTest {
+  private static final Encryption encryption = mock(Encryption.class);
+  private static final Settings settings = mock(Settings.class);
+
+  private final GitlabHttpClient gitlabHttpClient = mock(GitlabHttpClient.class);
+
+  private final GitlabGlobalSettingsValidator underTest = new GitlabGlobalSettingsValidator(gitlabHttpClient, settings);
+
+  @BeforeClass
+  public static void setUp() {
+    when(settings.getEncryption()).thenReturn(encryption);
+  }
+
+  @Test
+  public void validate_success() {
+    String token = "personal-access-token";
+    AlmSettingDto almSettingDto = new AlmSettingDto()
+      .setUrl("https://gitlab.com/api")
+      .setPersonalAccessToken("personal-access-token");
+    when(encryption.isEncrypted(token)).thenReturn(false);
+
+    underTest.validate(almSettingDto);
+    verify(gitlabHttpClient, times(1)).checkUrl(almSettingDto.getUrl());
+    verify(gitlabHttpClient, times(1)).checkToken(almSettingDto.getUrl(), almSettingDto.getDecryptedPersonalAccessToken(encryption));
+    verify(gitlabHttpClient, times(1)).checkReadPermission(almSettingDto.getUrl(), almSettingDto.getDecryptedPersonalAccessToken(encryption));
+    verify(gitlabHttpClient, times(1)).checkWritePermission(almSettingDto.getUrl(), almSettingDto.getDecryptedPersonalAccessToken(encryption));
+  }
+
+  @Test
+  public void validate_success_with_encrypted_token() {
+    String encryptedToken = "personal-access-token";
+    String decryptedToken = "decrypted-token";
+    AlmSettingDto almSettingDto = new AlmSettingDto()
+      .setUrl("https://gitlab.com/api")
+      .setPersonalAccessToken(encryptedToken);
+    when(encryption.isEncrypted(encryptedToken)).thenReturn(true);
+    when(encryption.decrypt(encryptedToken)).thenReturn(decryptedToken);
+
+    underTest.validate(almSettingDto);
+
+    verify(gitlabHttpClient, times(1)).checkUrl(almSettingDto.getUrl());
+    verify(gitlabHttpClient, times(1)).checkToken(almSettingDto.getUrl(), decryptedToken);
+    verify(gitlabHttpClient, times(1)).checkReadPermission(almSettingDto.getUrl(), decryptedToken);
+    verify(gitlabHttpClient, times(1)).checkWritePermission(almSettingDto.getUrl(), decryptedToken);
+  }
+
+  @Test
+  public void validate_fail_url_not_set() {
+    AlmSettingDto almSettingDto = new AlmSettingDto()
+      .setUrl(null)
+      .setPersonalAccessToken("personal-access-token");
+
+    assertThatThrownBy(() -> underTest.validate(almSettingDto))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Your Gitlab global configuration is incomplete.");
+  }
+
+  @Test
+  public void validate_fail_pat_not_set() {
+    AlmSettingDto almSettingDto = new AlmSettingDto()
+      .setUrl("https://gitlab.com/api")
+      .setPersonalAccessToken(null);
+
+    assertThatThrownBy(() -> underTest.validate(almSettingDto))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Your Gitlab global configuration is incomplete.");
+  }
+
+}
diff --git a/server/sonar-alm-client/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/server/sonar-alm-client/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644 (file)
index 0000000..1f0955d
--- /dev/null
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/server/sonar-webserver-monitoring/build.gradle b/server/sonar-webserver-monitoring/build.gradle
new file mode 100644 (file)
index 0000000..854ae9a
--- /dev/null
@@ -0,0 +1,13 @@
+description = 'SonarQube :: Monitoring'
+
+dependencies {
+    compile project(path: ':sonar-plugin-api', configuration: 'shadow')
+    compile project(':server:sonar-webserver-api')
+    compile project(':server:sonar-alm-client')
+    compile 'io.prometheus:simpleclient'
+
+    testCompile 'junit:junit'
+    testCompile 'org.assertj:assertj-core'
+    testCompile 'org.mockito:mockito-core'
+
+}
diff --git a/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ServerMonitoringMetrics.java b/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ServerMonitoringMetrics.java
new file mode 100644 (file)
index 0000000..1fb4c46
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.monitoring;
+
+import io.prometheus.client.Gauge;
+import org.sonar.api.server.ServerSide;
+
+@ServerSide
+public class ServerMonitoringMetrics {
+
+  private final Gauge githubConfigOk;
+  private final Gauge gitlabConfigOk;
+  private final Gauge bitbucketConfigOk;
+  private final Gauge azureConfigOk;
+
+  public ServerMonitoringMetrics() {
+    githubConfigOk = Gauge.build()
+      .name("github_config_ok")
+      .help("Tells whether SonarQube instance has configured GitHub integration and its status is green. 0 for green, 1 otherwise .")
+      .register();
+
+    gitlabConfigOk = Gauge.build()
+      .name("gitlab_config_ok")
+      .help("Tells whether SonarQube instance has configured GitLab integration and its status is green. 0 for green, 1 otherwise .")
+      .register();
+
+    bitbucketConfigOk = Gauge.build()
+      .name("bitbucket_config_ok")
+      .help("Tells whether SonarQube instance has configured BitBucket integration and its status is green. 0 for green, 1 otherwise .")
+      .register();
+
+    azureConfigOk = Gauge.build()
+      .name("azure_config_ok")
+      .help("Tells whether SonarQube instance has configured Azure integration and its status is green. 0 for green, 1 otherwise .")
+      .register();
+  }
+
+  public void setGithubStatusToGreen() {
+    githubConfigOk.set(0);
+  }
+
+  public void setGithubStatusToRed() {
+    githubConfigOk.set(1);
+  }
+
+  public void setGitlabStatusToGreen() {
+    gitlabConfigOk.set(0);
+  }
+
+  public void setGitlabStatusToRed() {
+    gitlabConfigOk.set(1);
+  }
+
+  public void setAzureStatusToGreen() {
+    azureConfigOk.set(0);
+  }
+
+  public void setAzureStatusToRed() {
+    azureConfigOk.set(1);
+  }
+
+  public void setBitbucketStatusToGreen() {
+    bitbucketConfigOk.set(0);
+  }
+
+  public void setBitbucketStatusToRed() {
+    bitbucketConfigOk.set(1);
+  }
+}
diff --git a/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/devops/DevOpsPlatformsMetricsCollector.java b/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/devops/DevOpsPlatformsMetricsCollector.java
new file mode 100644 (file)
index 0000000..fcee736
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.monitoring.devops;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.stream.Collectors;
+import org.picocontainer.Startable;
+import org.sonar.alm.client.azure.AzureDevOpsValidator;
+import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudValidator;
+import org.sonar.alm.client.bitbucketserver.BitbucketServerSettingsValidator;
+import org.sonar.alm.client.github.GithubGlobalSettingsValidator;
+import org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDto;
+import org.sonar.server.monitoring.ServerMonitoringMetrics;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+@ServerSide
+public class DevOpsPlatformsMetricsCollector implements Startable {
+
+  private static final String DELAY_IN_MILISECONDS_PROPERTY = "sonar.server.monitoring.devops.initial.delay";
+  private static final String PERIOD_IN_MILISECONDS_PROPERTY = "sonar.server.monitoring.devops.period";
+
+  private final Configuration config;
+
+  private final BitbucketServerSettingsValidator bitbucketServerValidator;
+  private final GithubGlobalSettingsValidator githubValidator;
+  private final GitlabGlobalSettingsValidator gitlabValidator;
+  private final BitbucketCloudValidator bitbucketCloudValidator;
+  private final AzureDevOpsValidator azureDevOpsValidator;
+
+  private final DbClient dbClient;
+  private final ServerMonitoringMetrics metrics;
+
+  private ScheduledExecutorService scheduledExecutorService;
+
+  public DevOpsPlatformsMetricsCollector(ServerMonitoringMetrics metrics, DbClient dbClient,
+    BitbucketServerSettingsValidator bitbucketServerValidator, GithubGlobalSettingsValidator githubValidator,
+    GitlabGlobalSettingsValidator gitlabValidator, BitbucketCloudValidator bitbucketCloudValidator,
+    AzureDevOpsValidator azureDevOpsValidator, Configuration config) {
+    this.bitbucketCloudValidator = bitbucketCloudValidator;
+    this.bitbucketServerValidator = bitbucketServerValidator;
+    this.githubValidator = githubValidator;
+    this.azureDevOpsValidator = azureDevOpsValidator;
+    this.gitlabValidator = gitlabValidator;
+    this.metrics = metrics;
+    this.dbClient = dbClient;
+    this.config = config;
+  }
+
+  @Override
+  public void start() {
+    this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(
+      new ThreadFactoryBuilder()
+      .setDaemon(true)
+      .setNameFormat(getClass().getCanonicalName() + "-thread-%d")
+      .build());
+    long delayInMilliseconds = config.getLong(DELAY_IN_MILISECONDS_PROPERTY).orElse(10_000L);
+    long periodInMilliseconds = config.getLong(PERIOD_IN_MILISECONDS_PROPERTY).orElse(300_000L);
+    scheduledExecutorService.scheduleWithFixedDelay(createTask(), delayInMilliseconds, periodInMilliseconds, MILLISECONDS);
+  }
+
+  @Override
+  public void stop() {
+    scheduledExecutorService.shutdown();
+  }
+
+  @VisibleForTesting
+  Runnable createTask() {
+    return () -> {
+      try (DbSession dbSession = dbClient.openSession(false)) {
+        List<AlmSettingDto> almSettingDtos = dbClient.almSettingDao().selectAll(dbSession);
+        validateBitbucket(getALMsDTOs(almSettingDtos, ALM.BITBUCKET));
+        validateBitbucketCloud(getALMsDTOs(almSettingDtos, ALM.BITBUCKET_CLOUD));
+        validateGithub(getALMsDTOs(almSettingDtos, ALM.GITHUB));
+        validateGitlab(getALMsDTOs(almSettingDtos, ALM.GITLAB));
+        validateAzure(getALMsDTOs(almSettingDtos, ALM.AZURE_DEVOPS));
+      }
+    };
+  }
+
+  private static List<AlmSettingDto> getALMsDTOs(List<AlmSettingDto> almSettingDtos, ALM alm) {
+    return almSettingDtos.stream().filter(dto -> dto.getAlm() == alm).collect(Collectors.toList());
+  }
+
+  private void validateGithub(List<AlmSettingDto> almSettingDtos) {
+    try {
+      for (AlmSettingDto dto : almSettingDtos) {
+        githubValidator.validate(dto);
+      }
+      metrics.setGithubStatusToGreen();
+    } catch (RuntimeException e) {
+      metrics.setGithubStatusToRed();
+    }
+  }
+
+  private void validateBitbucket(List<AlmSettingDto> almSettingDtos) {
+    try {
+      for (AlmSettingDto dto : almSettingDtos) {
+        bitbucketServerValidator.validate(dto);
+      }
+      metrics.setBitbucketStatusToGreen();
+    } catch (Exception e) {
+      metrics.setBitbucketStatusToRed();
+    }
+  }
+
+  private void validateBitbucketCloud(List<AlmSettingDto> almSettingDtos) {
+    try {
+      for (AlmSettingDto dto : almSettingDtos) {
+        bitbucketCloudValidator.validate(dto);
+      }
+      metrics.setBitbucketStatusToGreen();
+    } catch (Exception e) {
+      metrics.setBitbucketStatusToRed();
+    }
+  }
+
+  private void validateGitlab(List<AlmSettingDto> almSettingDtos) {
+    try {
+      for (AlmSettingDto dto : almSettingDtos) {
+        gitlabValidator.validate(dto);
+      }
+      metrics.setGitlabStatusToGreen();
+    } catch (Exception e) {
+      metrics.setGitlabStatusToRed();
+    }
+  }
+
+  private void validateAzure(List<AlmSettingDto> almSettingDtos) {
+    try {
+      for (AlmSettingDto dto : almSettingDtos) {
+        azureDevOpsValidator.validate(dto);
+      }
+      metrics.setAzureStatusToGreen();
+    } catch (Exception e) {
+      metrics.setAzureStatusToRed();
+    }
+  }
+}
diff --git a/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ServerMonitoringMetricsTest.java b/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ServerMonitoringMetricsTest.java
new file mode 100644 (file)
index 0000000..0849f04
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.monitoring;
+
+import io.prometheus.client.Collector;
+import io.prometheus.client.CollectorRegistry;
+import java.util.Collections;
+import java.util.Enumeration;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+//@Execution(SAME_THREAD) for JUnit5
+public class ServerMonitoringMetricsTest {
+
+  @Before
+  public void before() {
+    CollectorRegistry.defaultRegistry.clear();
+  }
+
+  @Test
+  public void creatingClassShouldAddMetricsToRegistry() {
+    assertThat(sizeOfDefaultRegistry()).isNotPositive();
+
+    new ServerMonitoringMetrics();
+
+    assertThat(sizeOfDefaultRegistry()).isPositive();
+  }
+
+  @Test
+  public void setters_setGreenStatusForMetricsInTheMetricsRegistry() {
+    ServerMonitoringMetrics metrics = new ServerMonitoringMetrics();
+
+    metrics.setGithubStatusToGreen();
+    metrics.setGitlabStatusToGreen();
+    metrics.setAzureStatusToGreen();
+    metrics.setBitbucketStatusToGreen();
+
+    assertThat(CollectorRegistry.defaultRegistry.getSampleValue("github_config_ok")).isZero();
+    assertThat(CollectorRegistry.defaultRegistry.getSampleValue("gitlab_config_ok")).isZero();
+    assertThat(CollectorRegistry.defaultRegistry.getSampleValue("bitbucket_config_ok")).isZero();
+    assertThat(CollectorRegistry.defaultRegistry.getSampleValue("azure_config_ok")).isZero();
+  }
+
+  @Test
+  public void setters_setRedStatusForMetricsInTheMetricsRegistry() {
+    ServerMonitoringMetrics metrics = new ServerMonitoringMetrics();
+
+    metrics.setGithubStatusToRed();
+    metrics.setGitlabStatusToRed();
+    metrics.setAzureStatusToRed();
+    metrics.setBitbucketStatusToRed();
+
+    assertThat(CollectorRegistry.defaultRegistry.getSampleValue("github_config_ok")).isEqualTo(1);
+    assertThat(CollectorRegistry.defaultRegistry.getSampleValue("gitlab_config_ok")).isEqualTo(1);
+    assertThat(CollectorRegistry.defaultRegistry.getSampleValue("bitbucket_config_ok")).isEqualTo(1);
+    assertThat(CollectorRegistry.defaultRegistry.getSampleValue("azure_config_ok")).isEqualTo(1);
+  }
+
+  private int sizeOfDefaultRegistry() {
+    Enumeration<Collector.MetricFamilySamples> metrics = CollectorRegistry.defaultRegistry.metricFamilySamples();
+    return Collections.list(metrics).size();
+  }
+}
diff --git a/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/devops/DevOpsPlatformsMetricsCollectorTest.java b/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/devops/DevOpsPlatformsMetricsCollectorTest.java
new file mode 100644 (file)
index 0000000..d464825
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.monitoring.devops;
+
+import org.sonar.api.config.Configuration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.alm.client.azure.AzureDevOpsValidator;
+import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudValidator;
+import org.sonar.alm.client.bitbucketserver.BitbucketServerSettingsValidator;
+import org.sonar.alm.client.github.GithubGlobalSettingsValidator;
+import org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator;
+import org.sonar.db.DbClient;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDao;
+import org.sonar.db.alm.setting.AlmSettingDto;
+import org.sonar.server.monitoring.ServerMonitoringMetrics;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+public class DevOpsPlatformsMetricsCollectorTest {
+
+  private final ServerMonitoringMetrics serverMonitoringMetrics = mock(ServerMonitoringMetrics.class);
+  private final DbClient dbClient = mock(DbClient.class);
+  private final BitbucketServerSettingsValidator bitbucketServerValidator = mock(BitbucketServerSettingsValidator.class);
+  private final GithubGlobalSettingsValidator githubValidator = mock(GithubGlobalSettingsValidator.class);
+  private final GitlabGlobalSettingsValidator gitlabValidator = mock(GitlabGlobalSettingsValidator.class);
+  private final BitbucketCloudValidator bitbucketCloudValidator = mock(BitbucketCloudValidator.class);
+  private final AzureDevOpsValidator azureDevOpsValidator = mock(AzureDevOpsValidator.class);
+  private final Configuration config = mock(Configuration.class);
+
+  private DevOpsPlatformsMetricsCollector collector;
+
+  @Before
+  public void before() {
+    collector = new DevOpsPlatformsMetricsCollector(serverMonitoringMetrics,
+      dbClient, bitbucketServerValidator, githubValidator, gitlabValidator, bitbucketCloudValidator,
+      azureDevOpsValidator, config);
+  }
+
+  @Test
+  public void start_startsNewDeamonThread() {
+    collector.start();
+
+    Optional<Thread> newDeamonThread = findNewDeamonThread();
+
+    assertThat(newDeamonThread).isPresent();
+    assertThat(newDeamonThread.get().isDaemon()).isTrue();
+  }
+
+  @Test
+  public void createTask_givenOneConfigForEachALM_allValidatorsAreCalled() {
+    AlmSettingDao dao = mock(AlmSettingDao.class);
+    List<AlmSettingDto> almSettingDtos = createAlmSettingDtos();
+    when(dao.selectAll(any())).thenReturn(almSettingDtos);
+    when(dbClient.almSettingDao()).thenReturn(dao);
+
+    collector.createTask().run();
+
+    verify(bitbucketCloudValidator, times(1)).validate(findDto(ALM.BITBUCKET_CLOUD, almSettingDtos));
+    verify(bitbucketServerValidator, times(1)).validate(findDto(ALM.BITBUCKET, almSettingDtos));
+    verify(azureDevOpsValidator, times(1)).validate(findDto(ALM.AZURE_DEVOPS, almSettingDtos));
+    verify(gitlabValidator, times(1)).validate(findDto(ALM.GITLAB, almSettingDtos));
+    verify(githubValidator, times(1)).validate(findDto(ALM.GITHUB, almSettingDtos));
+  }
+
+  @Test
+  public void createTask_givenOnlyGitHubConfigured_validateOnlyGithub() {
+    AlmSettingDao dao = mock(AlmSettingDao.class);
+    AlmSettingDto githubDto = new AlmSettingDto();
+    githubDto.setAlm(ALM.GITHUB);
+    when(dao.selectAll(any())).thenReturn(List.of(githubDto));
+    when(dbClient.almSettingDao()).thenReturn(dao);
+
+    collector.createTask().run();
+
+    verifyNoInteractions(bitbucketCloudValidator);
+    verifyNoInteractions(bitbucketServerValidator);
+    verifyNoInteractions(azureDevOpsValidator);
+    verifyNoInteractions(gitlabValidator);
+
+    verify(githubValidator, times(1)).validate(githubDto);
+  }
+
+  @Test
+  public void createTask_givenAllValidationsFailing_setAllMetricsStatusesToFalse() {
+    AlmSettingDao dao = mock(AlmSettingDao.class);
+    List<AlmSettingDto> almSettingDtos = createAlmSettingDtos();
+    when(dao.selectAll(any())).thenReturn(almSettingDtos);
+    when(dbClient.almSettingDao()).thenReturn(dao);
+
+    doThrow(new RuntimeException()).when(bitbucketCloudValidator).validate(any());
+    doThrow(new RuntimeException()).when(bitbucketServerValidator).validate(any());
+    doThrow(new RuntimeException()).when(azureDevOpsValidator).validate(any());
+    doThrow(new RuntimeException()).when(gitlabValidator).validate(any());
+    doThrow(new RuntimeException()).when(githubValidator).validate(any());
+
+    collector.createTask().run();
+
+    verify(serverMonitoringMetrics, times(2)).setBitbucketStatusToRed(); //2 validators for Bitbucket
+    verify(serverMonitoringMetrics, times(1)).setAzureStatusToRed();
+    verify(serverMonitoringMetrics, times(1)).setGitlabStatusToRed();
+    verify(serverMonitoringMetrics, times(1)).setGithubStatusToRed();
+  }
+
+  @Test
+  public void createTask_givenAllValidationsArePassing_setAllMetricsStatusesToTrue() {
+    AlmSettingDao dao = mock(AlmSettingDao.class);
+    List<AlmSettingDto> almSettingDtos = createAlmSettingDtos();
+    when(dao.selectAll(any())).thenReturn(almSettingDtos);
+    when(dbClient.almSettingDao()).thenReturn(dao);
+
+    collector.createTask().run();
+
+    verify(serverMonitoringMetrics, times(2)).setBitbucketStatusToGreen(); //2 validators for Bitbucket
+    verify(serverMonitoringMetrics, times(1)).setAzureStatusToGreen();
+    verify(serverMonitoringMetrics, times(1)).setGitlabStatusToGreen();
+    verify(serverMonitoringMetrics, times(1)).setGithubStatusToGreen();
+  }
+
+  @Test
+  public void createTask_givenFirstGithubValidationNotPassingAndSecondPassing_setGitHubValidationToTrue() {
+    AlmSettingDao dao = mock(AlmSettingDao.class);
+    List<AlmSettingDto> almSettingDtos = createAlmSettingDtos();
+    when(dao.selectAll(any())).thenReturn(almSettingDtos);
+    when(dbClient.almSettingDao()).thenReturn(dao);
+
+    when(githubValidator.validate(any()))
+      .thenThrow(new RuntimeException())
+      .thenReturn(null);
+
+    collector.createTask().run();
+
+    verify(serverMonitoringMetrics, times(1)).setGithubStatusToRed();
+    verify(serverMonitoringMetrics, times(0)).setGithubStatusToGreen();
+  }
+
+  private AlmSettingDto findDto(ALM alm, List<AlmSettingDto> almSettingDtos) {
+    return almSettingDtos.stream().filter(d -> d.getAlm() == alm).findFirst().get();
+  }
+
+  private List<AlmSettingDto> createAlmSettingDtos() {
+    List<AlmSettingDto> dtos = new ArrayList<>();
+    for(ALM alm : ALM.values()) {
+      AlmSettingDto almSettingDto = new AlmSettingDto();
+      almSettingDto.setAlm(alm);
+      dtos.add(almSettingDto);
+    }
+    return dtos;
+  }
+
+  private Optional<Thread> findNewDeamonThread() {
+    Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
+    String threadPartialName = DevOpsPlatformsMetricsCollector.class.getName();
+    return threadSet.stream().filter(t -> t.getName().contains(threadPartialName)).findFirst();
+  }
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/validator/BitbucketServerSettingsValidator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/validator/BitbucketServerSettingsValidator.java
deleted file mode 100644 (file)
index b0e6e63..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.almintegration.validator;
-
-import org.sonar.alm.client.bitbucketserver.BitbucketServerRestClient;
-import org.sonar.api.config.internal.Encryption;
-import org.sonar.api.config.internal.Settings;
-import org.sonar.api.server.ServerSide;
-import org.sonar.db.alm.setting.AlmSettingDto;
-
-@ServerSide
-public class BitbucketServerSettingsValidator {
-  private final BitbucketServerRestClient bitbucketServerRestClient;
-  private final Encryption encryption;
-
-  public BitbucketServerSettingsValidator(BitbucketServerRestClient bitbucketServerRestClient, Settings settings) {
-    this.bitbucketServerRestClient = bitbucketServerRestClient;
-    this.encryption = settings.getEncryption();
-  }
-
-  public void validate(AlmSettingDto almSettingDto) {
-    String bitbucketUrl = almSettingDto.getUrl();
-    String bitbucketToken = almSettingDto.getDecryptedPersonalAccessToken(encryption);
-    if (bitbucketUrl == null || bitbucketToken == null) {
-      throw new IllegalArgumentException("Your global Bitbucket Server configuration is incomplete.");
-    }
-
-    bitbucketServerRestClient.validateUrl(bitbucketUrl);
-    bitbucketServerRestClient.validateToken(bitbucketUrl, bitbucketToken);
-    bitbucketServerRestClient.validateReadPermission(bitbucketUrl, bitbucketToken);
-  }
-}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/validator/GithubGlobalSettingsValidator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/validator/GithubGlobalSettingsValidator.java
deleted file mode 100644 (file)
index 56298ce..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.almintegration.validator;
-
-import java.util.Optional;
-import org.sonar.alm.client.github.GithubApplicationClient;
-import org.sonar.alm.client.github.GithubApplicationClientImpl;
-import org.sonar.alm.client.github.config.GithubAppConfiguration;
-import org.sonar.api.config.internal.Encryption;
-import org.sonar.api.config.internal.Settings;
-import org.sonar.api.server.ServerSide;
-import org.sonar.db.alm.setting.AlmSettingDto;
-
-import static org.apache.commons.lang.StringUtils.isBlank;
-
-@ServerSide
-public class GithubGlobalSettingsValidator {
-
-  private final Encryption encryption;
-  private final GithubApplicationClient githubApplicationClient;
-
-  public GithubGlobalSettingsValidator(GithubApplicationClientImpl githubApplicationClient, Settings settings) {
-    this.encryption = settings.getEncryption();
-    this.githubApplicationClient = githubApplicationClient;
-  }
-
-  public GithubAppConfiguration validate(AlmSettingDto settings) {
-    long appId;
-    try {
-      appId = Long.parseLong(Optional.ofNullable(settings.getAppId()).orElseThrow(() -> new IllegalArgumentException("Missing appId")));
-    } catch (NumberFormatException e) {
-      throw new IllegalArgumentException("Invalid appId; " + e.getMessage());
-    }
-    if (isBlank(settings.getClientId())) {
-      throw new IllegalArgumentException("Missing Client Id");
-    }
-    if (isBlank(settings.getDecryptedClientSecret(encryption))) {
-      throw new IllegalArgumentException("Missing Client Secret");
-    }
-    GithubAppConfiguration configuration = new GithubAppConfiguration(appId, settings.getDecryptedPrivateKey(encryption),
-      settings.getUrl());
-
-    githubApplicationClient.checkApiEndpoint(configuration);
-    githubApplicationClient.checkAppPermissions(configuration);
-
-    return configuration;
-  }
-}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/validator/GitlabGlobalSettingsValidator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/validator/GitlabGlobalSettingsValidator.java
deleted file mode 100644 (file)
index d6758b4..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.almintegration.validator;
-
-import org.sonar.alm.client.gitlab.GitlabHttpClient;
-import org.sonar.api.config.internal.Encryption;
-import org.sonar.api.config.internal.Settings;
-import org.sonar.api.server.ServerSide;
-import org.sonar.db.alm.setting.AlmSettingDto;
-
-@ServerSide
-public class GitlabGlobalSettingsValidator {
-
-  private final Encryption encryption;
-  private final GitlabHttpClient gitlabHttpClient;
-
-  public GitlabGlobalSettingsValidator(GitlabHttpClient gitlabHttpClient, Settings settings) {
-    this.encryption = settings.getEncryption();
-    this.gitlabHttpClient = gitlabHttpClient;
-  }
-
-  public void validate(AlmSettingDto almSettingDto) {
-    String gitlabUrl = almSettingDto.getUrl();
-    String accessToken = almSettingDto.getDecryptedPersonalAccessToken(encryption);
-
-    if (gitlabUrl == null || accessToken == null) {
-      throw new IllegalArgumentException("Your Gitlab global configuration is incomplete.");
-    }
-
-    gitlabHttpClient.checkUrl(gitlabUrl);
-    gitlabHttpClient.checkToken(gitlabUrl, accessToken);
-    gitlabHttpClient.checkReadPermission(gitlabUrl, accessToken);
-    gitlabHttpClient.checkWritePermission(gitlabUrl, accessToken);
-  }
-
-}
index 0ab3065bc5993a776c2193bbbb7cd52a1504fdf3..528b78e98bd02043aa618fbeab6780db059909e9 100644 (file)
@@ -20,9 +20,6 @@
 package org.sonar.server.almintegration.ws;
 
 import org.sonar.core.platform.Module;
-import org.sonar.server.almintegration.validator.BitbucketServerSettingsValidator;
-import org.sonar.server.almintegration.validator.GithubGlobalSettingsValidator;
-import org.sonar.server.almintegration.validator.GitlabGlobalSettingsValidator;
 import org.sonar.server.almintegration.ws.azure.ImportAzureProjectAction;
 import org.sonar.server.almintegration.ws.azure.ListAzureProjectsAction;
 import org.sonar.server.almintegration.ws.azure.SearchAzureReposAction;
@@ -50,14 +47,11 @@ public class AlmIntegrationsWSModule extends Module {
       SearchBitbucketServerReposAction.class,
       SearchBitbucketCloudReposAction.class,
       GetGithubClientIdAction.class,
-      GithubGlobalSettingsValidator.class,
-      BitbucketServerSettingsValidator.class,
       ImportGithubProjectAction.class,
       ListGithubOrganizationsAction.class,
       ListGithubRepositoriesAction.class,
       ImportGitLabProjectAction.class,
       SearchGitlabReposAction.class,
-      GitlabGlobalSettingsValidator.class,
       ImportAzureProjectAction.class,
       ListAzureProjectsAction.class,
       SearchAzureReposAction.class,
index 8195eb94a0e9c313007a89e90337d44e2bbbcde0..4fb5b98e39b51d638a636e8ff35ef0f19ceee871 100644 (file)
@@ -20,7 +20,9 @@
 package org.sonar.server.almsettings.ws;
 
 import org.sonar.alm.client.azure.AzureDevOpsHttpClient;
+import org.sonar.alm.client.azure.AzureDevOpsValidator;
 import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient;
+import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudValidator;
 import org.sonar.api.config.internal.Encryption;
 import org.sonar.api.config.internal.Settings;
 import org.sonar.api.server.ws.Request;
@@ -29,9 +31,9 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.alm.setting.AlmSettingDto;
-import org.sonar.server.almintegration.validator.BitbucketServerSettingsValidator;
-import org.sonar.server.almintegration.validator.GithubGlobalSettingsValidator;
-import org.sonar.server.almintegration.validator.GitlabGlobalSettingsValidator;
+import org.sonar.alm.client.bitbucketserver.BitbucketServerSettingsValidator;
+import org.sonar.alm.client.github.GithubGlobalSettingsValidator;
+import org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator;
 import org.sonar.server.user.UserSession;
 
 public class ValidateAction implements AlmSettingsWsAction {
@@ -46,7 +48,8 @@ public class ValidateAction implements AlmSettingsWsAction {
   private final GitlabGlobalSettingsValidator gitlabSettingsValidator;
   private final GithubGlobalSettingsValidator githubGlobalSettingsValidator;
   private final BitbucketServerSettingsValidator bitbucketServerSettingsValidator;
-  private final BitbucketCloudRestClient bitbucketCloudRestClient;
+  private final BitbucketCloudValidator bitbucketCloudValidator;
+  private final AzureDevOpsValidator azureDevOpsValidator;
 
   public ValidateAction(DbClient dbClient,
     Settings settings,
@@ -56,7 +59,8 @@ public class ValidateAction implements AlmSettingsWsAction {
     GithubGlobalSettingsValidator githubGlobalSettingsValidator,
     GitlabGlobalSettingsValidator gitlabSettingsValidator,
     BitbucketServerSettingsValidator bitbucketServerSettingsValidator,
-    BitbucketCloudRestClient bitbucketCloudRestClient) {
+    BitbucketCloudValidator bitbucketCloudValidator,
+    AzureDevOpsValidator azureDevOpsValidator) {
     this.dbClient = dbClient;
     this.encryption = settings.getEncryption();
     this.userSession = userSession;
@@ -65,7 +69,8 @@ public class ValidateAction implements AlmSettingsWsAction {
     this.githubGlobalSettingsValidator = githubGlobalSettingsValidator;
     this.gitlabSettingsValidator = gitlabSettingsValidator;
     this.bitbucketServerSettingsValidator = bitbucketServerSettingsValidator;
-    this.bitbucketCloudRestClient = bitbucketCloudRestClient;
+    this.bitbucketCloudValidator = bitbucketCloudValidator;
+    this.azureDevOpsValidator = azureDevOpsValidator;
   }
 
   @Override
@@ -106,24 +111,12 @@ public class ValidateAction implements AlmSettingsWsAction {
           bitbucketServerSettingsValidator.validate(almSettingDto);
           break;
         case BITBUCKET_CLOUD:
-          validateBitbucketCloud(almSettingDto);
+          bitbucketCloudValidator.validate(almSettingDto);
           break;
         case AZURE_DEVOPS:
-          validateAzure(almSettingDto);
+          azureDevOpsValidator.validate(almSettingDto);
           break;
       }
     }
   }
-
-  private void validateAzure(AlmSettingDto almSettingDto) {
-    try {
-      azureDevOpsHttpClient.checkPAT(almSettingDto.getUrl(), almSettingDto.getDecryptedPersonalAccessToken(encryption));
-    } catch (IllegalArgumentException e) {
-      throw new IllegalArgumentException("Invalid Azure URL or Personal Access Token", e);
-    }
-  }
-
-  private void validateBitbucketCloud(AlmSettingDto almSettingDto) {
-    bitbucketCloudRestClient.validate(almSettingDto.getClientId(), almSettingDto.getDecryptedClientSecret(encryption), almSettingDto.getAppId());
-  }
 }
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/validator/BitbucketServerSettingsValidatorTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/validator/BitbucketServerSettingsValidatorTest.java
deleted file mode 100644 (file)
index e864487..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.almintegration.validator;
-
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.sonar.alm.client.bitbucketserver.BitbucketServerRestClient;
-import org.sonar.api.config.internal.Encryption;
-import org.sonar.api.config.internal.Settings;
-import org.sonar.db.alm.setting.AlmSettingDto;
-
-import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.almsettings.AlmSettingsTesting.newBitbucketAlmSettingDto;
-
-public class BitbucketServerSettingsValidatorTest {
-  private static final Encryption encryption = mock(Encryption.class);
-  private static final Settings settings = mock(Settings.class);
-
-  private final BitbucketServerRestClient bitbucketServerRestClient = mock(BitbucketServerRestClient.class);
-  private final BitbucketServerSettingsValidator underTest = new BitbucketServerSettingsValidator(bitbucketServerRestClient, settings);
-
-  @BeforeClass
-  public static void setUp() {
-    when(settings.getEncryption()).thenReturn(encryption);
-  }
-
-  @Test
-  public void validate_success() {
-    AlmSettingDto almSettingDto = newBitbucketAlmSettingDto()
-      .setUrl("http://abc.com")
-      .setPersonalAccessToken("abc");
-    when(encryption.isEncrypted(any())).thenReturn(false);
-
-    underTest.validate(almSettingDto);
-
-    verify(bitbucketServerRestClient, times(1)).validateUrl("http://abc.com");
-    verify(bitbucketServerRestClient, times(1)).validateToken("http://abc.com", "abc");
-    verify(bitbucketServerRestClient, times(1)).validateReadPermission("http://abc.com", "abc");
-  }
-
-  @Test
-  public void validate_success_with_encrypted_token() {
-    String encryptedToken = "abc";
-    String decryptedToken = "decrypted-token";
-    AlmSettingDto almSettingDto = newBitbucketAlmSettingDto()
-      .setUrl("http://abc.com")
-      .setPersonalAccessToken(encryptedToken);
-    when(encryption.isEncrypted(encryptedToken)).thenReturn(true);
-    when(encryption.decrypt(encryptedToken)).thenReturn(decryptedToken);
-
-    underTest.validate(almSettingDto);
-
-    verify(bitbucketServerRestClient, times(1)).validateUrl("http://abc.com");
-    verify(bitbucketServerRestClient, times(1)).validateToken("http://abc.com", decryptedToken);
-    verify(bitbucketServerRestClient, times(1)).validateReadPermission("http://abc.com", decryptedToken);
-  }
-
-  @Test
-  public void validate_failure_on_incomplete_configuration() {
-    AlmSettingDto almSettingDto = newBitbucketAlmSettingDto()
-      .setUrl(null)
-      .setPersonalAccessToken("abc");
-
-    assertThatThrownBy(() -> underTest.validate(almSettingDto))
-      .isInstanceOf(IllegalArgumentException.class);
-  }
-
-  @Test
-  public void validate_failure_on_bitbucket_server_api_error() {
-    doThrow(new IllegalArgumentException("error")).when(bitbucketServerRestClient).validateUrl(anyString());
-    AlmSettingDto almSettingDto = newBitbucketAlmSettingDto()
-      .setUrl("http://abc.com")
-      .setPersonalAccessToken("abc");
-
-    assertThatThrownBy(() -> underTest.validate(almSettingDto))
-      .isInstanceOf(IllegalArgumentException.class);
-  }
-}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/validator/GithubGlobalSettingsValidatorTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/validator/GithubGlobalSettingsValidatorTest.java
deleted file mode 100644 (file)
index 5c3d274..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.almintegration.validator;
-
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.sonar.alm.client.github.GithubApplicationClientImpl;
-import org.sonar.alm.client.github.config.GithubAppConfiguration;
-import org.sonar.api.config.internal.Encryption;
-import org.sonar.api.config.internal.Settings;
-import org.sonar.db.alm.setting.AlmSettingDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.almsettings.AlmSettingsTesting.newGithubAlmSettingDto;
-
-public class GithubGlobalSettingsValidatorTest {
-  private static final Encryption encryption = mock(Encryption.class);
-  private static final Settings settings = mock(Settings.class);
-
-  private final GithubApplicationClientImpl appClient = mock(GithubApplicationClientImpl.class);
-  private final GithubGlobalSettingsValidator underTest = new GithubGlobalSettingsValidator(appClient, settings);
-
-  @BeforeClass
-  public static void setUp() {
-    when(settings.getEncryption()).thenReturn(encryption);
-  }
-
-  @Test
-  public void github_global_settings_validation() {
-    AlmSettingDto almSettingDto = newGithubAlmSettingDto()
-      .setClientId("clientId")
-      .setClientSecret("clientSecret");
-    when(encryption.isEncrypted(any())).thenReturn(false);
-
-    GithubAppConfiguration configuration = underTest.validate(almSettingDto);
-
-    ArgumentCaptor<GithubAppConfiguration> configurationArgumentCaptor = ArgumentCaptor.forClass(GithubAppConfiguration.class);
-    verify(appClient).checkApiEndpoint(configurationArgumentCaptor.capture());
-    verify(appClient).checkAppPermissions(configurationArgumentCaptor.capture());
-    assertThat(configuration.getId()).isEqualTo(configurationArgumentCaptor.getAllValues().get(0).getId());
-    assertThat(configuration.getId()).isEqualTo(configurationArgumentCaptor.getAllValues().get(1).getId());
-  }
-
-  @Test
-  public void github_global_settings_validation_with_encrypted_key() {
-    String encryptedKey = "encrypted-key";
-    String decryptedKey = "decrypted-key";
-    AlmSettingDto almSettingDto = newGithubAlmSettingDto()
-      .setClientId("clientId")
-      .setPrivateKey(encryptedKey)
-      .setClientSecret("clientSecret");
-    when(encryption.isEncrypted(encryptedKey)).thenReturn(true);
-    when(encryption.decrypt(encryptedKey)).thenReturn(decryptedKey);
-
-    GithubAppConfiguration configuration = underTest.validate(almSettingDto);
-
-    ArgumentCaptor<GithubAppConfiguration> configurationArgumentCaptor = ArgumentCaptor.forClass(GithubAppConfiguration.class);
-    verify(appClient).checkApiEndpoint(configurationArgumentCaptor.capture());
-    verify(appClient).checkAppPermissions(configurationArgumentCaptor.capture());
-    assertThat(configuration.getId()).isEqualTo(configurationArgumentCaptor.getAllValues().get(0).getId());
-    assertThat(decryptedKey).isEqualTo(configurationArgumentCaptor.getAllValues().get(0).getPrivateKey());
-    assertThat(configuration.getId()).isEqualTo(configurationArgumentCaptor.getAllValues().get(1).getId());
-    assertThat(decryptedKey).isEqualTo(configurationArgumentCaptor.getAllValues().get(1).getPrivateKey());
-  }
-
-  @Test
-  public void github_validation_checks_invalid_appId() {
-    AlmSettingDto almSettingDto = newGithubAlmSettingDto()
-      .setAppId("abc")
-      .setClientId("clientId")
-      .setClientSecret("clientSecret");
-
-    assertThatThrownBy(() -> underTest.validate(almSettingDto))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("Invalid appId; For input string: \"abc\"");
-  }
-
-  @Test
-  public void github_validation_checks_missing_appId() {
-    AlmSettingDto almSettingDto = newGithubAlmSettingDto().setAppId(null);
-
-    assertThatThrownBy(() -> underTest.validate(almSettingDto))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("Missing appId");
-  }
-
-  @Test
-  public void github_validation_checks_missing_clientId() {
-    AlmSettingDto almSettingDto = newGithubAlmSettingDto().setClientId(null);
-
-    assertThatThrownBy(() -> underTest.validate(almSettingDto))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("Missing Client Id");
-  }
-
-  @Test
-  public void github_validation_checks_missing_clientSecret() {
-    AlmSettingDto almSettingDto = newGithubAlmSettingDto().setClientSecret(null);
-
-    assertThatThrownBy(() -> underTest.validate(almSettingDto))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("Missing Client Secret");
-
-  }
-}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/validator/GitlabGlobalSettingsValidatorTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/validator/GitlabGlobalSettingsValidatorTest.java
deleted file mode 100644 (file)
index 186b671..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.almintegration.validator;
-
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.sonar.alm.client.gitlab.GitlabHttpClient;
-import org.sonar.api.config.internal.Encryption;
-import org.sonar.api.config.internal.Settings;
-import org.sonar.db.alm.setting.AlmSettingDto;
-
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class GitlabGlobalSettingsValidatorTest {
-  private static final Encryption encryption = mock(Encryption.class);
-  private static final Settings settings = mock(Settings.class);
-
-  private final GitlabHttpClient gitlabHttpClient = mock(GitlabHttpClient.class);
-
-  private final GitlabGlobalSettingsValidator underTest = new GitlabGlobalSettingsValidator(gitlabHttpClient, settings);
-
-  @BeforeClass
-  public static void setUp() {
-    when(settings.getEncryption()).thenReturn(encryption);
-  }
-
-  @Test
-  public void validate_success() {
-    String token = "personal-access-token";
-    AlmSettingDto almSettingDto = new AlmSettingDto()
-      .setUrl("https://gitlab.com/api")
-      .setPersonalAccessToken("personal-access-token");
-    when(encryption.isEncrypted(token)).thenReturn(false);
-
-    underTest.validate(almSettingDto);
-    verify(gitlabHttpClient, times(1)).checkUrl(almSettingDto.getUrl());
-    verify(gitlabHttpClient, times(1)).checkToken(almSettingDto.getUrl(), almSettingDto.getDecryptedPersonalAccessToken(encryption));
-    verify(gitlabHttpClient, times(1)).checkReadPermission(almSettingDto.getUrl(), almSettingDto.getDecryptedPersonalAccessToken(encryption));
-    verify(gitlabHttpClient, times(1)).checkWritePermission(almSettingDto.getUrl(), almSettingDto.getDecryptedPersonalAccessToken(encryption));
-  }
-
-  @Test
-  public void validate_success_with_encrypted_token() {
-    String encryptedToken = "personal-access-token";
-    String decryptedToken = "decrypted-token";
-    AlmSettingDto almSettingDto = new AlmSettingDto()
-      .setUrl("https://gitlab.com/api")
-      .setPersonalAccessToken(encryptedToken);
-    when(encryption.isEncrypted(encryptedToken)).thenReturn(true);
-    when(encryption.decrypt(encryptedToken)).thenReturn(decryptedToken);
-
-    underTest.validate(almSettingDto);
-
-    verify(gitlabHttpClient, times(1)).checkUrl(almSettingDto.getUrl());
-    verify(gitlabHttpClient, times(1)).checkToken(almSettingDto.getUrl(), decryptedToken);
-    verify(gitlabHttpClient, times(1)).checkReadPermission(almSettingDto.getUrl(), decryptedToken);
-    verify(gitlabHttpClient, times(1)).checkWritePermission(almSettingDto.getUrl(), decryptedToken);
-  }
-
-  @Test
-  public void validate_fail_url_not_set() {
-    AlmSettingDto almSettingDto = new AlmSettingDto()
-      .setUrl(null)
-      .setPersonalAccessToken("personal-access-token");
-
-    assertThatThrownBy(() -> underTest.validate(almSettingDto))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("Your Gitlab global configuration is incomplete.");
-  }
-
-  @Test
-  public void validate_fail_pat_not_set() {
-    AlmSettingDto almSettingDto = new AlmSettingDto()
-      .setUrl("https://gitlab.com/api")
-      .setPersonalAccessToken(null);
-
-    assertThatThrownBy(() -> underTest.validate(almSettingDto))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("Your Gitlab global configuration is incomplete.");
-  }
-
-}
index 97da7ad21fb97ed4bc186db1abfec02312c841e1..985ad92c9c85b7ca6602a4979d7756d17c5eef03 100644 (file)
@@ -24,7 +24,9 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.sonar.alm.client.azure.AzureDevOpsHttpClient;
+import org.sonar.alm.client.azure.AzureDevOpsValidator;
 import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient;
+import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudValidator;
 import org.sonar.api.config.internal.Encryption;
 import org.sonar.api.config.internal.Settings;
 import org.sonar.api.resources.ResourceTypes;
@@ -33,9 +35,9 @@ import org.sonar.db.DbTester;
 import org.sonar.db.alm.setting.ALM;
 import org.sonar.db.alm.setting.AlmSettingDto;
 import org.sonar.db.user.UserDto;
-import org.sonar.server.almintegration.validator.BitbucketServerSettingsValidator;
-import org.sonar.server.almintegration.validator.GithubGlobalSettingsValidator;
-import org.sonar.server.almintegration.validator.GitlabGlobalSettingsValidator;
+import org.sonar.alm.client.bitbucketserver.BitbucketServerSettingsValidator;
+import org.sonar.alm.client.github.GithubGlobalSettingsValidator;
+import org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator;
 import org.sonar.server.almsettings.MultipleAlmFeatureProvider;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.exceptions.ForbiddenException;
@@ -66,13 +68,15 @@ public class ValidateActionTest {
   private final ComponentFinder componentFinder = new ComponentFinder(db.getDbClient(), mock(ResourceTypes.class));
   private final AlmSettingsSupport almSettingsSupport = new AlmSettingsSupport(db.getDbClient(), userSession, componentFinder, multipleAlmFeatureProvider);
   private final AzureDevOpsHttpClient azureDevOpsHttpClient = mock(AzureDevOpsHttpClient.class);
+  private final BitbucketCloudRestClient bitbucketCloudRestClient = mock(BitbucketCloudRestClient.class);
   private final GitlabGlobalSettingsValidator gitlabSettingsValidator = mock(GitlabGlobalSettingsValidator.class);
   private final GithubGlobalSettingsValidator githubGlobalSettingsValidator = mock(GithubGlobalSettingsValidator.class);
   private final BitbucketServerSettingsValidator bitbucketServerSettingsValidator = mock(BitbucketServerSettingsValidator.class);
-  private final BitbucketCloudRestClient bitbucketCloudRestClient = mock(BitbucketCloudRestClient.class);
+  private final BitbucketCloudValidator bitbucketCloudValidator = new BitbucketCloudValidator(bitbucketCloudRestClient, settings);
+  private final AzureDevOpsValidator azureDevOpsValidator = new AzureDevOpsValidator(azureDevOpsHttpClient, settings);
   private final WsActionTester ws = new WsActionTester(
     new ValidateAction(db.getDbClient(), settings, userSession, almSettingsSupport, azureDevOpsHttpClient, githubGlobalSettingsValidator,
-      gitlabSettingsValidator, bitbucketServerSettingsValidator, bitbucketCloudRestClient));
+      gitlabSettingsValidator, bitbucketServerSettingsValidator, bitbucketCloudValidator, azureDevOpsValidator));
 
   @BeforeClass
   public static void setUp() {
index 89cf5af83735245e7057e19cff8f0296181aa89f..ea2fcd9cf6ce4728d7321d101dcf95cda8d7847f 100644 (file)
@@ -21,6 +21,7 @@ dependencies {
   compile project(':server:sonar-process')
   compile project(':server:sonar-webserver-core')
   compile project(':server:sonar-webserver-webapi')
+  compile project(':server:sonar-webserver-monitoring')
 
   compileOnly 'com.google.code.findbugs:jsr305'
 
index a0b22729d1bf55ab622e6580f11d49b3666af02c..fe0c229346c9257216be91d1c916a185957b2361 100644 (file)
@@ -22,11 +22,16 @@ package org.sonar.server.platform.platformlevel;
 import java.util.List;
 import org.sonar.alm.client.TimeoutConfigurationImpl;
 import org.sonar.alm.client.azure.AzureDevOpsHttpClient;
+import org.sonar.alm.client.azure.AzureDevOpsValidator;
 import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient;
+import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudValidator;
 import org.sonar.alm.client.bitbucketserver.BitbucketServerRestClient;
+import org.sonar.alm.client.bitbucketserver.BitbucketServerSettingsValidator;
 import org.sonar.alm.client.github.GithubApplicationClientImpl;
 import org.sonar.alm.client.github.GithubApplicationHttpClientImpl;
+import org.sonar.alm.client.github.GithubGlobalSettingsValidator;
 import org.sonar.alm.client.github.security.GithubAppSecurityImpl;
+import org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator;
 import org.sonar.alm.client.gitlab.GitlabHttpClient;
 import org.sonar.api.profiles.XMLProfileParser;
 import org.sonar.api.profiles.XMLProfileSerializer;
@@ -122,6 +127,8 @@ import org.sonar.server.metric.MetricFinder;
 import org.sonar.server.metric.UnanalyzedLanguageMetrics;
 import org.sonar.server.metric.ws.MetricsWsModule;
 import org.sonar.server.monitoring.MonitoringWsModule;
+import org.sonar.server.monitoring.devops.DevOpsPlatformsMetricsCollector;
+import org.sonar.server.monitoring.ServerMonitoringMetrics;
 import org.sonar.server.newcodeperiod.ws.NewCodePeriodsWsModule;
 import org.sonar.server.notification.NotificationModule;
 import org.sonar.server.notification.ws.NotificationWsModule;
@@ -508,6 +515,11 @@ public class PlatformLevel4 extends PlatformLevel {
       GitlabHttpClient.class,
       AzureDevOpsHttpClient.class,
       AlmIntegrationsWSModule.class,
+      BitbucketCloudValidator.class,
+      BitbucketServerSettingsValidator.class,
+      GithubGlobalSettingsValidator.class,
+      GitlabGlobalSettingsValidator.class,
+      AzureDevOpsValidator.class,
 
       // ALM settings
       AlmSettingsWsModule.class,
@@ -564,6 +576,10 @@ public class PlatformLevel4 extends PlatformLevel {
       TelemetryDaemon.class,
       TelemetryClient.class,
 
+      // monitoring
+      ServerMonitoringMetrics.class,
+      DevOpsPlatformsMetricsCollector.class,
+
       PluginsRiskConsentFilter.class
 
     );
index fb0bea230f4a02aac09bcde83fa191aa810685d6..311b34c3cff6bb3f211bc1f922ccdab211aba6c9 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.platform.platformlevel;
 
 import org.sonar.server.authentication.SafeModeUserSession;
+import org.sonar.server.monitoring.ServerMonitoringMetrics;
 import org.sonar.server.platform.ServerImpl;
 import org.sonar.server.platform.db.migration.AutoDbMigration;
 import org.sonar.server.platform.db.migration.DatabaseMigrationImpl;
@@ -57,7 +58,10 @@ public class PlatformLevelSafeMode extends PlatformLevel {
       // WS engine
       SafeModeUserSession.class,
       WebServiceEngine.class,
-      WebServiceFilter.class);
+      WebServiceFilter.class,
+
+      // Monitoring
+      ServerMonitoringMetrics.class);
     addIfStartupLeader(
       DatabaseMigrationImpl.class,
       MigrationEngineModule.class,
index fb4ee883d9698ecf260c290971accaf3d7d46734..b4719ca33867b7489c3012779a80b74bdff57d6c 100644 (file)
@@ -39,6 +39,7 @@ include 'server:sonar-webserver-es'
 include 'server:sonar-webserver-webapi'
 include 'server:sonar-webserver-ws'
 include 'server:sonar-alm-client'
+include 'server:sonar-webserver-monitoring'
 
 include 'sonar-application'
 include 'sonar-check-api'