'private/it-branch/it-tests/src/test/java/com/sonarsource/branch/it/suite/pr/gitlab/**/*.java',
'private/core-extension-gitlab-vulnerability-report/src/main/**/*.java')
-gitlab_provisioning_task_template: &GITLAB_PROVISIONING_TASK_TEMPLATE
- only_if: >-
+gitlab_provisioning_task_template: &IS_GITLAB_PROVISIONING_TASK_NEEDED >-
$CIRRUS_BRANCH == $BRANCH_MAIN || $CIRRUS_BRANCH =~ $BRANCH_PATTERN_MAINTENANCE || $CIRRUS_BRANCH == $BRANCH_NIGHTLY ||
changesInclude('private/core-extension-gitlab-provisioning/**/*.java')
qa_gitlab_provisioning_task:
<<: *DEFAULT_TEMPLATE
<<: *BUILD_DEPENDANT_TASK_TEMPLATE
- <<: *GITLAB_PROVISIONING_TASK_TEMPLATE
<<: *JAR_CACHE_TEMPLATE
<<: *GRADLE_CACHE_TEMPLATE
+ only_if: *IS_GITLAB_PROVISIONING_TASK_NEEDED
env:
QA_CATEGORY: GITLAB_PROVISIONING
matrix:
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
+import com.google.gson.reflect.TypeToken;
+import java.util.Collection;
public class GsonId {
+ private static final TypeToken<Collection<GsonId>> COLLECTION_TYPE_TOKEN = new TypeToken<>() {
+ };
@SerializedName("id")
private final long id;
return gson.fromJson(json, GsonId.class);
}
+ public static Collection<GsonId> parseCollection(String json) {
+ Gson gson = new Gson();
+ return gson.fromJson(json, COLLECTION_TYPE_TOKEN);
+ }
+
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 java.util.Collection;
+import org.junit.Test;
+
+import static java.util.stream.Collectors.toSet;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class GsonIdTest {
+
+ @Test
+ public void parseOne_whenSingleId_deserializesCorrectly() {
+ GsonId gsonId = GsonId.parseOne("""
+ {
+ "blu": "blue",
+ "id": "123",
+ "bla": "bli"
+ }
+ """);
+
+ assertThat(gsonId.getId()).isEqualTo(123);
+ }
+
+ @Test
+ public void parseCollection_whenMultipleIds_deserializeCorrectly() {
+ Collection<GsonId> gsonId = GsonId.parseCollection("""
+ [
+ {
+ "blu": "blue",
+ "id": "123",
+ "bla": "bli"
+ },
+
+ {
+ "id": "456",
+ "bla": "bli"
+ }
+ ]
+ """);
+
+ assertThat(gsonId.stream().map(GsonId::getId).collect(toSet())).containsExactlyInAnyOrder(123L, 456L);
+ }
+
+}
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.management.ManagedInstanceService;
+import org.sonar.server.setting.ThreadLocalSettings;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@Mock
private GitlabGlobalSettingsValidator gitlabGlobalSettingsValidator;
+ @Mock
+ private ThreadLocalSettings threadLocalSettings;
+
private GitlabConfigurationService gitlabConfigurationService;
@Before
gitlabConfigurationService = new GitlabConfigurationService(
dbTester.getDbClient(),
managedInstanceService,
- gitlabGlobalSettingsValidator);
+ gitlabGlobalSettingsValidator,
+ threadLocalSettings);
}
@Test
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.management.ManagedInstanceService;
+import org.sonar.server.setting.ThreadLocalSettings;
import static java.lang.String.format;
import static org.apache.commons.lang.StringUtils.isNotBlank;
private final DbClient dbClient;
private final ManagedInstanceService managedInstanceService;
private final GitlabGlobalSettingsValidator gitlabGlobalSettingsValidator;
+ private final ThreadLocalSettings threadLocalSettings;
public GitlabConfigurationService(DbClient dbClient,
- ManagedInstanceService managedInstanceService, GitlabGlobalSettingsValidator gitlabGlobalSettingsValidator) {
+ ManagedInstanceService managedInstanceService, GitlabGlobalSettingsValidator gitlabGlobalSettingsValidator, ThreadLocalSettings threadLocalSettings) {
this.dbClient = dbClient;
this.managedInstanceService = managedInstanceService;
this.gitlabGlobalSettingsValidator = gitlabGlobalSettingsValidator;
+ this.threadLocalSettings = threadLocalSettings;
}
public GitlabConfiguration updateConfiguration(UpdateGitlabConfigurationRequest updateRequest) {
value
.map(definedValue -> new PropertyDto().setKey(propertyName).setValue(definedValue))
.applyIfDefined(property -> dbClient.propertiesDao().saveProperty(dbSession, property));
+ threadLocalSettings.setProperty(propertyName, value.orElse(null));
}
private void deleteExternalGroupsWhenDisablingAutoProvisioning(
import static org.sonarqube.ws.WsUtils.isNullOrEmpty;
public abstract class BaseService {
+ protected static final String APPLICATION_MERGE_PATCH_JSON = "application/merge-patch+json";
private final WsConnector wsConnector;
protected final String controller;
}
}
+ protected String path() {
+ return controller;
+ }
+
protected String path(String action) {
return String.format("%s/%s", controller, action);
}
import org.sonarqube.ws.client.favorites.FavoritesService;
import org.sonarqube.ws.client.github.provisioning.permissions.GithubPermissionsService;
import org.sonarqube.ws.client.githubprovisioning.GithubProvisioningService;
+import org.sonarqube.ws.client.gitlab.configuration.GitlabConfigurationService;
+import org.sonarqube.ws.client.gitlab.synchronization.run.GitlabSynchronizationRunService;
import org.sonarqube.ws.client.governancereports.GovernanceReportsService;
import org.sonarqube.ws.client.hotspots.HotspotsService;
import org.sonarqube.ws.client.issues.IssuesService;
private final SonarLintServerPushService sonarLintPushService;
private final GithubProvisioningService githubProvisioningService;
private final GithubPermissionsService githubPermissionsService;
+ private final GitlabConfigurationService gitlabConfigurationService;
+
+ private final GitlabSynchronizationRunService gitlabSynchronizationRunService;
DefaultWsClient(WsConnector wsConnector) {
this.wsConnector = wsConnector;
this.regulatoryReportsService = new RegulatoryReportsService(wsConnector);
this.githubProvisioningService = new GithubProvisioningService(wsConnector);
this.githubPermissionsService = new GithubPermissionsService(wsConnector);
+ this.gitlabConfigurationService = new GitlabConfigurationService(wsConnector);
+ this.gitlabSynchronizationRunService = new GitlabSynchronizationRunService(wsConnector);
}
@Override
return githubPermissionsService;
}
+ @Override
+ public GitlabConfigurationService gitlabConfigurationService() {
+ return gitlabConfigurationService;
+ }
+
+ @Override
+ public GitlabSynchronizationRunService gitlabSynchronizationRunService() {
+ return gitlabSynchronizationRunService;
+ }
+
@Override
public GovernanceReportsService governanceReports() {
return governanceReportsService;
public static final int DEFAULT_CONNECT_TIMEOUT_MILLISECONDS = 30_000;
public static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = 60_000;
- private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+ private static final String JSON = "application/json; charset=utf-8";
/**
* Base URL with trailing slash, for instance "https://localhost/sonarqube/".
RequestBody body;
Map<String, Part> parts = request.getParts();
if (request.hasBody()) {
- body = RequestBody.create(JSON, request.getBody());
+ MediaType contentType = MediaType.parse(request.getContentType().orElse(JSON));
+ body = RequestBody.create(contentType, request.getBody());
} else if (parts.isEmpty()) {
// parameters are defined in the body (application/x-www-form-urlencoded)
FormBody.Builder formBody = new FormBody.Builder();
import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Optional;
import java.util.function.Function;
import okhttp3.Request;
import okhttp3.RequestBody;
public abstract class RequestWithPayload<T extends RequestWithPayload<T>> extends BaseRequest<RequestWithPayload<T>> {
private String body;
+ private String contentType = null;
private final Map<String, Part> parts = new LinkedHashMap<>();
protected RequestWithPayload(String path) {
return this.body != null;
}
+ public T setContentType(String contentType) {
+ this.contentType = contentType;
+ return (T) this;
+ }
+
+ public Optional<String> getContentType() {
+ return Optional.ofNullable(contentType);
+ }
+
public T setPart(String name, Part part) {
this.parts.put(name, part);
return (T) this;
import org.sonarqube.ws.client.favorites.FavoritesService;
import org.sonarqube.ws.client.github.provisioning.permissions.GithubPermissionsService;
import org.sonarqube.ws.client.githubprovisioning.GithubProvisioningService;
+import org.sonarqube.ws.client.gitlab.configuration.GitlabConfigurationService;
+import org.sonarqube.ws.client.gitlab.synchronization.run.GitlabSynchronizationRunService;
import org.sonarqube.ws.client.governancereports.GovernanceReportsService;
import org.sonarqube.ws.client.hotspots.HotspotsService;
import org.sonarqube.ws.client.issues.IssuesService;
GithubPermissionsService githubPermissionsService();
+ GitlabConfigurationService gitlabConfigurationService();
+
+ GitlabSynchronizationRunService gitlabSynchronizationRunService();
+
GovernanceReportsService governanceReports();
HotspotsService hotspots();
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.sonarqube.ws.client.gitlab.configuration;
+
+public record GitlabConfiguration(boolean enabled, String applicationId, String url, String secret, boolean synchronizeGroups,
+ String provisioningType, boolean allowUsersToSignUp, String provisioningToken,
+ String singleProvisioningGroup) {
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.sonarqube.ws.client.gitlab.configuration;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import org.sonarqube.ws.client.BaseService;
+import org.sonarqube.ws.client.PatchRequest;
+import org.sonarqube.ws.client.PostRequest;
+import org.sonarqube.ws.client.WsConnector;
+import org.sonarqube.ws.client.WsResponse;
+
+public class GitlabConfigurationService extends BaseService {
+
+
+ public GitlabConfigurationService(WsConnector wsConnector) {
+ super(wsConnector, "api/v2/dop-translation/gitlab-configurations");
+ }
+
+ public String saveGitlabConfiguration(GitlabConfiguration gitlabConfiguration) {
+ String body = String.format("""
+ {
+ "enabled": "%s",
+ "applicationId": "%s",
+ "url": "%s",
+ "secret": "%s",
+ "synchronizeGroups": "%s",
+ "provisioningType": "%s",
+ "allowUsersToSignUp": "%s",
+ "provisioningToken": "%s",
+ "provisioningGroups": ["%s"]
+ }
+ """, gitlabConfiguration.enabled(), gitlabConfiguration.applicationId(), gitlabConfiguration.url(), gitlabConfiguration.secret(),
+ gitlabConfiguration.synchronizeGroups(), gitlabConfiguration.provisioningType(), gitlabConfiguration.allowUsersToSignUp(),
+ gitlabConfiguration.provisioningToken(), gitlabConfiguration.singleProvisioningGroup());
+ WsResponse response = call(
+ new PostRequest(path()).setBody(body)
+ );
+ return new Gson().fromJson(response.content(), JsonObject.class).get("id").getAsString();
+ }
+
+ public void enableAutoProvisioning(String configId) {
+ setProvisioningMode(configId, "AUTO_PROVISIONING");
+ }
+
+ public void disableAutoProvisioning(String configId) {
+ setProvisioningMode(configId, "JIT");
+ }
+
+ private void setProvisioningMode(String configId, String provisioningMode) {
+ String body = String.format("""
+ {
+ "provisioningType": "%s"
+ }
+ """, provisioningMode);
+ call(
+ new PatchRequest(path(configId)).setBody(body).setContentType(APPLICATION_MERGE_PATCH_JSON)
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonarqube.ws.client.gitlab.configuration;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.sonarqube.ws.client.gitlab.synchronization.run;
+
+import org.sonarqube.ws.client.BaseService;
+import org.sonarqube.ws.client.PostRequest;
+import org.sonarqube.ws.client.WsConnector;
+
+public class GitlabSynchronizationRunService extends BaseService {
+
+ public GitlabSynchronizationRunService(WsConnector wsConnector) {
+ super(wsConnector, "api/v2/dop-translation/gitlab-synchronization-runs");
+ }
+
+ public void triggerRun() {
+ call(
+ new PostRequest(path())
+ );
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonarqube.ws.client.gitlab.synchronization.run;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.sonarqube.ws.client.gitlab.configuration;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonarqube.ws.client.WsConnector;
+
+import static org.assertj.core.api.Assertions.assertThatNoException;
+import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GitlabConfigurationServiceTest {
+
+ @Mock(answer = RETURNS_DEEP_STUBS)
+ private WsConnector wsConnector;
+
+ @InjectMocks
+ private GitlabConfigurationService gitlabConfigurationService;
+
+ @Test
+ public void saveGitlabConfiguration_shouldNotFail() {
+ when(wsConnector.call(any()).failIfNotSuccessful().content()).thenReturn("{\"id\": \"configId\"}");
+ assertThatNoException().isThrownBy(() -> gitlabConfigurationService.saveGitlabConfiguration(new GitlabConfiguration(true, "appId", "url", "secret",
+ true, "JIT", false, "token", "group")));
+ }
+
+ @Test
+ public void enableAutoProvisioning_shouldNotFail() {
+ assertThatNoException().isThrownBy(() -> gitlabConfigurationService.enableAutoProvisioning("configId"));
+ }
+
+ @Test
+ public void disableAutoProvisioning_shouldNotFail() {
+ assertThatNoException().isThrownBy(() -> gitlabConfigurationService.disableAutoProvisioning("configId"));
+ }
+
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.sonarqube.ws.client.gitlab.synchronization.run;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonarqube.ws.client.WsConnector;
+
+import static org.assertj.core.api.Assertions.assertThatNoException;
+import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GitlabSynchronizationRunServiceTest {
+
+ @Mock(answer = RETURNS_DEEP_STUBS)
+ private WsConnector wsConnector;
+
+ @InjectMocks
+ private GitlabSynchronizationRunService gitlabSynchronizationRunService;
+
+ @Test
+ public void triggerRun_whenTriggered_shouldNotFail() {
+ assertThatNoException().isThrownBy(() ->gitlabSynchronizationRunService.triggerRun());
+ }
+
+}