GithubBinding.GsonApp getApp(GithubAppConfiguration githubAppConfiguration);
+ /**
+ * Retrieve the installation id for the given accountName.
+ * @throws IllegalArgumentException if one of the arguments is invalid (for example, wrong private key)
+ */
+ Optional<Long> getInstallationId(GithubAppConfiguration githubAppConfiguration, String repositorySlug);
+
/**
* Lists all the organizations accessible to the access token provided.
*/
}
}
+ @Override
+ public Optional<Long> getInstallationId(GithubAppConfiguration githubAppConfiguration, String repositorySlug) {
+ AppToken appToken = appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey());
+ String endpoint = String.format("/repos/%s/installation", repositorySlug);
+ return get(githubAppConfiguration.getApiEndpoint(), appToken, endpoint, GithubBinding.GsonInstallation.class)
+ .map(GithubBinding.GsonInstallation::getId);
+ }
+
@Override
public Organizations listOrganizations(String appUrl, AccessToken accessToken, int page, int pageSize) {
checkPageArgs(page, pageSize);
assertThatCode(() -> underTest.checkAppPermissions(githubAppConfiguration)).isNull();
}
+ @Test
+ public void getInstallationId_returns_installation_id_of_given_account() throws IOException {
+ AppToken appToken = new AppToken(APP_JWT_TOKEN);
+ when(appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey())).thenReturn(appToken);
+ when(httpClient.get(appUrl, appToken, "/repos/torvalds/linux/installation"))
+ .thenReturn(new OkGetResponse("{" +
+ " \"id\": 2," +
+ " \"account\": {" +
+ " \"login\": \"torvalds\"" +
+ " }" +
+ "}"));
+
+ assertThat(underTest.getInstallationId(githubAppConfiguration, "torvalds/linux")).hasValue(2L);
+ }
+
+ @Test
+ public void getInstallationId_throws_IAE_if_fail_to_create_app_token() {
+ when(appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey())).thenThrow(IllegalArgumentException.class);
+
+ assertThatThrownBy(() -> underTest.getInstallationId(githubAppConfiguration, "torvalds"))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ public void getInstallationId_return_empty_if_no_installation_found_for_githubAccount() throws IOException {
+ AppToken appToken = new AppToken(APP_JWT_TOKEN);
+ when(appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey())).thenReturn(appToken);
+ when(httpClient.get(appUrl, appToken, "/repos/torvalds/linux/installation"))
+ .thenReturn(new ErrorGetResponse(404, null));
+
+ assertThat(underTest.getInstallationId(githubAppConfiguration, "torvalds")).isEmpty();
+ }
+
@Test
@UseDataProvider("githubServers")
public void createUserAccessToken_returns_empty_if_access_token_cant_be_created(String apiUrl, String appUrl) throws IOException {
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.almsettings.ws;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.Priority;
+import org.apache.commons.lang.NotImplementedException;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+@ServerSide
+@Priority(1)
+public class DelegatingDevOpsPlatformService implements DevOpsPlatformService {
+
+ private final Set<DevOpsPlatformService> delegates;
+
+ public DelegatingDevOpsPlatformService(Set<DevOpsPlatformService> delegates) {
+ this.delegates = delegates;
+ }
+
+ @Override
+ public ALM getDevOpsPlatform() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Optional<DevOpsProjectDescriptor> getDevOpsProjectDescriptor(Map<String, String> characteristics) {
+ return delegates.stream()
+ .flatMap(delegate -> delegate.getDevOpsProjectDescriptor(characteristics).stream())
+ .findFirst();
+ }
+
+ @Override
+ public Optional<AlmSettingDto> getValidAlmSettingDto(DbSession dbSession, DevOpsProjectDescriptor devOpsProjectDescriptor) {
+ return findDelegate(devOpsProjectDescriptor.alm())
+ .flatMap(delegate -> delegate.getValidAlmSettingDto(dbSession, devOpsProjectDescriptor));
+ }
+
+ private Optional<DevOpsPlatformService> findDelegate(ALM alm) {
+ return delegates.stream()
+ .filter(delegate -> delegate.getDevOpsPlatform().equals(alm))
+ .findFirst();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.almsettings.ws;
+
+import java.util.Map;
+import java.util.Optional;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+public interface DevOpsPlatformService {
+
+ ALM getDevOpsPlatform();
+
+ Optional<DevOpsProjectDescriptor> getDevOpsProjectDescriptor(Map<String, String> characteristics);
+
+ Optional<AlmSettingDto> getValidAlmSettingDto(DbSession dbSession, DevOpsProjectDescriptor devOpsProjectDescriptor);
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.almsettings.ws;
+
+import org.sonar.db.alm.setting.ALM;
+
+public record DevOpsProjectDescriptor(ALM alm, String url, String projectIdentifier) {
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.almsettings.ws;
+
+import java.util.Map;
+import java.util.Optional;
+import org.sonar.alm.client.github.GithubApplicationClient;
+import org.sonar.alm.client.github.GithubGlobalSettingsValidator;
+import org.sonar.alm.client.github.config.GithubAppConfiguration;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDao;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+@ServerSide
+public class GitHubDevOpsPlatformService implements DevOpsPlatformService {
+
+ public static final String DEVOPS_PLATFORM_URL = "devOpsPlatformUrl";
+ public static final String DEVOPS_PLATFORM_PROJECT_IDENTIFIER = "devOpsPlatformProjectIdentifier";
+
+ private final AlmSettingDao almSettingDao;
+ private final GithubGlobalSettingsValidator githubGlobalSettingsValidator;
+ private final GithubApplicationClient githubApplicationClient;
+
+ public GitHubDevOpsPlatformService(AlmSettingDao almSettingDao, GithubGlobalSettingsValidator githubGlobalSettingsValidator,
+ GithubApplicationClient githubApplicationClient) {
+ this.almSettingDao = almSettingDao;
+ this.githubGlobalSettingsValidator = githubGlobalSettingsValidator;
+ this.githubApplicationClient = githubApplicationClient;
+ }
+
+ @Override
+ public ALM getDevOpsPlatform() {
+ return ALM.GITHUB;
+ }
+
+ @Override
+ public Optional<DevOpsProjectDescriptor> getDevOpsProjectDescriptor(Map<String, String> characteristics) {
+ String githubApiUrl = characteristics.get(DEVOPS_PLATFORM_URL);
+ String githubRepository = characteristics.get(DEVOPS_PLATFORM_PROJECT_IDENTIFIER);
+ if (githubApiUrl != null && githubRepository != null) {
+ return Optional.of(new DevOpsProjectDescriptor(ALM.GITHUB, githubApiUrl, githubRepository));
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional<AlmSettingDto> getValidAlmSettingDto(DbSession dbSession, DevOpsProjectDescriptor devOpsProjectDescriptor) {
+ return almSettingDao.selectByAlm(dbSession, getDevOpsPlatform()).stream()
+ .filter(almSettingDto -> devOpsProjectDescriptor.url().equals(almSettingDto.getUrl()))
+ .filter(almSettingDto -> hasAccessToRepo(almSettingDto, devOpsProjectDescriptor.projectIdentifier()))
+ .findFirst();
+ }
+
+ private boolean hasAccessToRepo(AlmSettingDto almSettingDto, String repo) {
+ GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto);
+ return githubApplicationClient.getInstallationId(githubAppConfiguration, repo).isPresent();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.almsettings.ws;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import org.apache.commons.lang.NotImplementedException;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+import static java.util.Collections.emptySet;
+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.when;
+
+public class DelegatingDevOpsPlatformServiceTest {
+
+ private static final DelegatingDevOpsPlatformService NO_DEVOPS_PLATFORMS = new DelegatingDevOpsPlatformService(emptySet());
+ private static final DelegatingDevOpsPlatformService MULTIPLE_DEVOPS_PLATFORMS = new DelegatingDevOpsPlatformService(
+ Set.of(mockGitHubDevOpsPlatformService(), mockAzureDevOpsPlatformService()));
+
+ @Mock
+ private DbSession dbSession;
+
+ @Test
+ public void getDevOpsPlatform_shouldThrow() {
+ assertThatThrownBy(NO_DEVOPS_PLATFORMS::getDevOpsPlatform)
+ .isInstanceOf(NotImplementedException.class);
+ }
+
+ @Test
+ public void getDevOpsProjectDescriptor_whenNoDelegates_shouldReturnOptionalEmpty() {
+ Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = NO_DEVOPS_PLATFORMS.getDevOpsProjectDescriptor(Map.of());
+
+ assertThat(devOpsProjectDescriptor).isEmpty();
+ }
+
+ @Test
+ public void getDevOpsProjectDescriptor_whenDelegates_shouldReturnDelegateResponse() {
+ Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = MULTIPLE_DEVOPS_PLATFORMS.getDevOpsProjectDescriptor(Map.of(
+ "githubUrl", "githubUrl"
+ ));
+
+ assertThat(devOpsProjectDescriptor)
+ .isPresent()
+ .get().usingRecursiveComparison().isEqualTo(new DevOpsProjectDescriptor(ALM.GITHUB, "githubUrl", "githubRepo"));
+ }
+
+ @Test
+ public void getValidAlmSettingDto_whenNoDelegates_shouldReturnOptionalEmpty() {
+ Optional<AlmSettingDto> almSettingDto = NO_DEVOPS_PLATFORMS.getValidAlmSettingDto(dbSession, mock(DevOpsProjectDescriptor.class));
+
+ assertThat(almSettingDto).isEmpty();
+ }
+
+ @Test
+ public void getValidAlmSettingDto_whenDelegates_shouldReturnDelegateResponse() {
+ Optional<AlmSettingDto> almSettingDto = MULTIPLE_DEVOPS_PLATFORMS.getValidAlmSettingDto(dbSession, new DevOpsProjectDescriptor(ALM.GITHUB, "githubUrl", "githubRepo"));
+
+ assertThat(almSettingDto)
+ .isPresent()
+ .get()
+ .usingRecursiveComparison().isEqualTo(new AlmSettingDto().setAlm(ALM.GITHUB));
+ }
+
+ private static DevOpsPlatformService mockGitHubDevOpsPlatformService() {
+ DevOpsPlatformService mockDevOpsPlatformService = mock();
+ when(mockDevOpsPlatformService.getDevOpsPlatform()).thenReturn(ALM.GITHUB);
+ when(mockDevOpsPlatformService.getDevOpsProjectDescriptor(Map.of("githubUrl", "githubUrl")))
+ .thenReturn(Optional.of(new DevOpsProjectDescriptor(ALM.GITHUB, "githubUrl", "githubRepo")));
+ when(mockDevOpsPlatformService.getValidAlmSettingDto(any(), any()))
+ .thenReturn(Optional.of(new AlmSettingDto().setAlm(ALM.GITHUB)));
+ return mockDevOpsPlatformService;
+ }
+
+ private static DevOpsPlatformService mockAzureDevOpsPlatformService() {
+ DevOpsPlatformService mockDevOpsPlatformService = mock();
+ when(mockDevOpsPlatformService.getDevOpsPlatform()).thenReturn(ALM.AZURE_DEVOPS);
+ when(mockDevOpsPlatformService.getDevOpsProjectDescriptor(Map.of("azureUrl", "azureUrl")))
+ .thenReturn(Optional.of(new DevOpsProjectDescriptor(ALM.AZURE_DEVOPS, "azureUrl", "azureProject")));
+ when(mockDevOpsPlatformService.getValidAlmSettingDto(any(), any()))
+ .thenReturn(Optional.of(new AlmSettingDto().setAlm(ALM.AZURE_DEVOPS)));
+ return mockDevOpsPlatformService;
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.almsettings.ws;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonar.alm.client.github.GithubApplicationClient;
+import org.sonar.alm.client.github.GithubGlobalSettingsValidator;
+import org.sonar.alm.client.github.config.GithubAppConfiguration;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDao;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+import static java.util.Collections.emptyList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService.DEVOPS_PLATFORM_PROJECT_IDENTIFIER;
+import static org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService.DEVOPS_PLATFORM_URL;
+
+
+@RunWith(MockitoJUnitRunner.class)
+public class GitHubDevOpsPlatformServiceTest {
+
+ private static final DevOpsProjectDescriptor GITHUB_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, "url", "repo");
+
+ @Mock
+ private DbSession dbSession;
+ @Mock
+ private AlmSettingDao almSettingDao;
+ @Mock
+ private GithubGlobalSettingsValidator githubGlobalSettingsValidator;
+ @Mock
+ private GithubApplicationClient githubApplicationClient;
+ @InjectMocks
+ private GitHubDevOpsPlatformService gitHubDevOpsPlatformService;
+
+ @Test
+ public void getDevOpsPlatform_shouldReturnGitHub() {
+ assertThat(gitHubDevOpsPlatformService.getDevOpsPlatform())
+ .isEqualTo(ALM.GITHUB);
+ }
+
+ @Test
+ public void getDevOpsProjectDescriptor_whenNoCharacteristics_shouldReturnEmpty() {
+ Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = gitHubDevOpsPlatformService.getDevOpsProjectDescriptor(Map.of());
+
+ assertThat(devOpsProjectDescriptor).isEmpty();
+ }
+
+ @Test
+ public void getDevOpsProjectDescriptor_whenValidCharacteristics_shouldReturn() {
+ Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = gitHubDevOpsPlatformService.getDevOpsProjectDescriptor(Map.of(
+ DEVOPS_PLATFORM_URL, GITHUB_PROJECT_DESCRIPTOR.url(),
+ DEVOPS_PLATFORM_PROJECT_IDENTIFIER, GITHUB_PROJECT_DESCRIPTOR.projectIdentifier()
+ ));
+
+ assertThat(devOpsProjectDescriptor)
+ .isPresent()
+ .get().usingRecursiveComparison().isEqualTo(GITHUB_PROJECT_DESCRIPTOR);
+ }
+
+ @Test
+ public void getValidAlmSettingDto_whenNoAlmSetting_shouldReturnEmpty() {
+ when(almSettingDao.selectByAlm(dbSession, ALM.GITHUB)).thenReturn(emptyList());
+
+ Optional<AlmSettingDto> almSettingDto = gitHubDevOpsPlatformService.getValidAlmSettingDto(dbSession, GITHUB_PROJECT_DESCRIPTOR);
+
+ assertThat(almSettingDto).isEmpty();
+ }
+
+ @Test
+ public void getValidAlmSettingDto_whenMultipleAlmSetting_shouldReturnTheRightOne() {
+ AlmSettingDto mockGitHubAlmSettingDtoNoAccess = mockGitHubAlmSettingDto(false);
+ AlmSettingDto mockGitHubAlmSettingDtoAccess = mockGitHubAlmSettingDto(true);
+ when(almSettingDao.selectByAlm(dbSession, ALM.GITHUB)).thenReturn(List.of(mockGitHubAlmSettingDtoNoAccess, mockGitHubAlmSettingDtoAccess));
+
+ Optional<AlmSettingDto> almSettingDto = gitHubDevOpsPlatformService.getValidAlmSettingDto(dbSession, GITHUB_PROJECT_DESCRIPTOR);
+
+ assertThat(almSettingDto)
+ .isPresent()
+ .get().isEqualTo(mockGitHubAlmSettingDtoAccess);
+ }
+
+ private AlmSettingDto mockGitHubAlmSettingDto(boolean repoAccess) {
+ AlmSettingDto mockAlmSettingDto = mock();
+ when(mockAlmSettingDto.getUrl()).thenReturn(GITHUB_PROJECT_DESCRIPTOR.url());
+ GithubAppConfiguration mockGithubAppConfiguration = mock(GithubAppConfiguration.class);
+ when(githubGlobalSettingsValidator.validate(mockAlmSettingDto)).thenReturn(mockGithubAppConfiguration);
+ when(githubApplicationClient.getInstallationId(eq(mockGithubAppConfiguration), any())).thenReturn(repoAccess ? Optional.of(1L) : Optional.empty());
+ return mockAlmSettingDto;
+ }
+
+}
import org.sonar.server.almintegration.ws.github.GithubProvisioningWs;
import org.sonar.server.almsettings.MultipleAlmFeature;
import org.sonar.server.almsettings.ws.AlmSettingsWsModule;
+import org.sonar.server.almsettings.ws.DelegatingDevOpsPlatformService;
+import org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService;
import org.sonar.server.authentication.AuthenticationModule;
import org.sonar.server.authentication.DefaultAdminCredentialsVerifierImpl;
import org.sonar.server.authentication.DefaultAdminCredentialsVerifierNotificationHandler;
DefaultBranchNameResolver.class,
DefaultDocumentationLinkGenerator.class,
DelegatingManagedServices.class,
+ DelegatingDevOpsPlatformService.class,
// batch
new BatchWsModule(),
GithubApplicationHttpClientImpl.class,
GithubProvisioningConfigValidator.class,
GithubProvisioningWs.class,
+ GitHubDevOpsPlatformService.class,
BitbucketCloudRestClientConfiguration.class,
BitbucketServerRestClient.class,
GitlabHttpClient.class,