Skip to content

Commit 3303548

Browse files
committed
SCANJLIB-272 Cleanup URLs for better comparison
1 parent 8c7105d commit 3303548

File tree

5 files changed

+91
-37
lines changed

5 files changed

+91
-37
lines changed

lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapper.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ public class ScannerEngineBootstrapper {
8181

8282
private static final Logger LOG = LoggerFactory.getLogger(ScannerEngineBootstrapper.class);
8383

84-
8584
static final String SQ_VERSION_NEW_BOOTSTRAPPING = "10.6";
8685
static final String SQ_VERSION_TOKEN_AUTHENTICATION = "10.0";
8786

@@ -157,8 +156,7 @@ public ScannerEngineBootstrapResult bootstrap() {
157156
private ScannerEngineBootstrapResult bootstrapCloud(FileCache fileCache, Map<String, String> immutableProperties, HttpConfig httpConfig, ScannerEndpoint endpoint) {
158157
endpoint.getRegionLabel().ifPresentOrElse(
159158
region -> LOG.info("Communicating with SonarQube Cloud ({} region)", region),
160-
() -> LOG.info("Communicating with SonarQube Cloud")
161-
);
159+
() -> LOG.info("Communicating with SonarQube Cloud"));
162160
var scannerFacade = buildNewFacade(fileCache, immutableProperties, httpConfig,
163161
(launcher, adaptedProperties) -> NewScannerEngineFacade.forSonarQubeCloud(adaptedProperties, launcher));
164162
return new SuccessfulBootstrap(scannerFacade);
@@ -307,8 +305,11 @@ private static String getServerVersion(ScannerHttpClient scannerHttpClient) {
307305
}
308306

309307
private void initBootstrapDefaultValues(ScannerEndpoint endpoint) {
310-
setBootstrapPropertyIfNotAlreadySet(ScannerProperties.HOST_URL, endpoint.getWebEndpoint());
311-
setBootstrapPropertyIfNotAlreadySet(ScannerProperties.API_BASE_URL, endpoint.getApiEndpoint());
308+
setBootstrapProperty(ScannerProperties.HOST_URL, endpoint.getWebEndpoint());
309+
setBootstrapProperty(ScannerProperties.API_BASE_URL, endpoint.getApiEndpoint());
310+
if (endpoint.isSonarQubeCloud()) {
311+
setBootstrapProperty(ScannerProperties.SONARQUBE_CLOUD_URL, endpoint.getWebEndpoint());
312+
}
312313
if (!bootstrapProperties.containsKey(SCANNER_OS)) {
313314
setBootstrapProperty(SCANNER_OS, new OsResolver(system, new Paths2()).getOs().name().toLowerCase(Locale.ENGLISH));
314315
}
@@ -338,10 +339,4 @@ static Map<String, String> adaptSslPropertiesToScannerProperties(Map<String, Str
338339
return Map.copyOf(result);
339340
}
340341

341-
private void setBootstrapPropertyIfNotAlreadySet(String key, @Nullable String value) {
342-
if (!bootstrapProperties.containsKey(key) && value != null) {
343-
setBootstrapProperty(key, value);
344-
}
345-
}
346-
347342
}

lib/src/main/java/org/sonarsource/scanner/lib/internal/endpoint/OfficialSonarQubeCloudInstance.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import java.util.Arrays;
2323
import java.util.Locale;
24+
import java.util.Objects;
2425
import java.util.Optional;
2526
import java.util.Set;
2627
import java.util.stream.Collectors;
@@ -32,7 +33,6 @@ public enum OfficialSonarQubeCloudInstance {
3233
GLOBAL("https://sonarcloud.io", "https://api.sonarcloud.io", null),
3334
US("https://sonarqube.us", "https://api.sonarqube.us", "US");
3435

35-
3636
private final ScannerEndpoint endpoint;
3737

3838
OfficialSonarQubeCloudInstance(String webEndpoint, String apiEndpoint, @Nullable String regionLabel) {
@@ -68,10 +68,15 @@ public static Optional<OfficialSonarQubeCloudInstance> fromRegionCode(@Nullable
6868

6969
public static Optional<OfficialSonarQubeCloudInstance> fromWebEndpoint(String url) {
7070
return Arrays.stream(OfficialSonarQubeCloudInstance.values())
71-
.filter(r -> r.endpoint.getWebEndpoint().equals(url))
71+
.filter(r -> Objects.equals(removePrefix(r.endpoint.getWebEndpoint()), removePrefix(url)))
7272
.findFirst();
7373
}
7474

75+
// In a web browser, both http://sonarcloud.io or https://www.sonarcloud.io redirect to SonarQube Cloud, so apply the same logic here for the detection
76+
private static String removePrefix(String url) {
77+
return url.replaceFirst("^https?://(www.)?", "");
78+
}
79+
7580
public ScannerEndpoint getEndpoint() {
7681
return endpoint;
7782
}

lib/src/main/java/org/sonarsource/scanner/lib/internal/endpoint/ScannerEndpointResolver.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@
2222
import java.util.Map;
2323
import java.util.Optional;
2424
import org.apache.commons.lang3.StringUtils;
25+
import org.apache.commons.lang3.Strings;
2526
import org.sonarsource.scanner.lib.EnvironmentConfig;
2627
import org.sonarsource.scanner.lib.ScannerProperties;
2728
import org.sonarsource.scanner.lib.internal.MessageException;
2829

2930
import static java.lang.String.format;
3031
import static java.util.stream.Collectors.toList;
32+
import static org.apache.commons.lang3.StringUtils.trim;
3133

3234
public class ScannerEndpointResolver {
3335

@@ -73,8 +75,8 @@ private static ScannerEndpoint resolveCustomSonarQubeCloudEndpoint(Map<String, S
7375
throw new MessageException(format("Defining a custom '%s' without providing '%s' is not supported.", ScannerProperties.SONARQUBE_CLOUD_URL, ScannerProperties.API_BASE_URL));
7476
}
7577
return new ScannerEndpoint(
76-
properties.get(ScannerProperties.SONARQUBE_CLOUD_URL),
77-
properties.get(ScannerProperties.API_BASE_URL), true, null);
78+
cleanUrl(properties.get(ScannerProperties.SONARQUBE_CLOUD_URL)),
79+
cleanUrl(properties.get(ScannerProperties.API_BASE_URL)), true, null);
7880
}
7981

8082
private static MessageException inconsistentUrlAndRegion(String prop2) {
@@ -83,11 +85,11 @@ private static MessageException inconsistentUrlAndRegion(String prop2) {
8385

8486
private static ScannerEndpoint resolveEndpointFromSonarHostUrl(Map<String, String> properties) {
8587
return maybeResolveOfficialSonarQubeCloud(properties, ScannerProperties.HOST_URL)
86-
.orElse(new SonarQubeServer(properties.get(ScannerProperties.HOST_URL)));
88+
.orElse(new SonarQubeServer(cleanUrl(properties.get(ScannerProperties.HOST_URL))));
8789
}
8890

8991
private static Optional<ScannerEndpoint> maybeResolveOfficialSonarQubeCloud(Map<String, String> properties, String urlPropName) {
90-
var maybeCloudInstance = OfficialSonarQubeCloudInstance.fromWebEndpoint(properties.get(urlPropName));
92+
var maybeCloudInstance = OfficialSonarQubeCloudInstance.fromWebEndpoint(cleanUrl(properties.get(urlPropName)));
9193
var hasRegion = properties.containsKey(ScannerProperties.SONAR_REGION);
9294
if (maybeCloudInstance.isPresent()) {
9395
if (hasRegion && OfficialSonarQubeCloudInstance.fromRegionCode(properties.get(ScannerProperties.SONAR_REGION)).filter(maybeCloudInstance.get()::equals).isEmpty()) {
@@ -101,4 +103,12 @@ private static Optional<ScannerEndpoint> maybeResolveOfficialSonarQubeCloud(Map<
101103
return Optional.empty();
102104
}
103105

106+
private static String cleanUrl(String url) {
107+
String withoutTrailingSlash = trim(url);
108+
while (withoutTrailingSlash.endsWith("/")) {
109+
withoutTrailingSlash = Strings.CS.removeEnd(withoutTrailingSlash, "/");
110+
}
111+
return withoutTrailingSlash;
112+
}
113+
104114
}

lib/src/test/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapperTest.java

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,11 @@ void should_use_new_bootstrapping_on_sonarqube_cloud() throws Exception {
105105
try (var bootstrapResult = underTest.bootstrap()) {
106106
verify(scannerEngineLauncherFactory).createLauncher(eq(scannerHttpClient), any(FileCache.class), anyMap());
107107
assertThat(bootstrapResult.getEngineFacade().isSonarQubeCloud()).isTrue();
108-
assertThat(bootstrapResult.getEngineFacade().getBootstrapProperties()).containsEntry(ScannerProperties.HOST_URL, "https://sonarcloud.io");
108+
assertThat(bootstrapResult.getEngineFacade().getBootstrapProperties())
109+
.contains(
110+
entry(ScannerProperties.HOST_URL, "https://sonarcloud.io"),
111+
entry(ScannerProperties.API_BASE_URL, "https://api.sonarcloud.io"),
112+
entry(ScannerProperties.SONARQUBE_CLOUD_URL, "https://sonarcloud.io"));
109113
assertThat(logTester.logs(Level.INFO)).contains("Communicating with SonarQube Cloud");
110114
}
111115
}
@@ -115,19 +119,39 @@ void should_use_new_bootstrapping_on_sonarqube_cloud_us() throws Exception {
115119
try (var bootstrapResult = underTest.setBootstrapProperty(ScannerProperties.SONAR_REGION, "us").bootstrap()) {
116120
verify(scannerEngineLauncherFactory).createLauncher(eq(scannerHttpClient), any(FileCache.class), anyMap());
117121
assertThat(bootstrapResult.getEngineFacade().isSonarQubeCloud()).isTrue();
118-
assertThat(bootstrapResult.getEngineFacade().getBootstrapProperties()).containsEntry(ScannerProperties.HOST_URL, "https://sonarqube.us");
122+
assertThat(bootstrapResult.getEngineFacade().getBootstrapProperties()).contains(
123+
entry(ScannerProperties.HOST_URL, "https://sonarqube.us"),
124+
entry(ScannerProperties.API_BASE_URL, "https://api.sonarqube.us"),
125+
entry(ScannerProperties.SONARQUBE_CLOUD_URL, "https://sonarqube.us"));
119126
assertThat(logTester.logs(Level.INFO)).contains("Communicating with SonarQube Cloud (US region)");
120127
}
121128
}
122129

130+
@Test
131+
void should_use_cleaned_urls() throws Exception {
132+
try (var bootstrapResult = underTest.setBootstrapProperty(ScannerProperties.HOST_URL, "http://www.sonarcloud.io/").bootstrap()) {
133+
verify(scannerEngineLauncherFactory).createLauncher(eq(scannerHttpClient), any(FileCache.class), anyMap());
134+
assertThat(bootstrapResult.getEngineFacade().isSonarQubeCloud()).isTrue();
135+
assertThat(bootstrapResult.getEngineFacade().getBootstrapProperties())
136+
.contains(
137+
entry(ScannerProperties.HOST_URL, "https://sonarcloud.io"),
138+
entry(ScannerProperties.API_BASE_URL, "https://api.sonarcloud.io"),
139+
entry(ScannerProperties.SONARQUBE_CLOUD_URL, "https://sonarcloud.io"));
140+
assertThat(logTester.logs(Level.INFO)).contains("Communicating with SonarQube Cloud");
141+
}
142+
}
143+
123144
@Test
124145
void should_use_new_bootstrapping_with_sonarqube_10_6() throws Exception {
125146
when(scannerHttpClient.callRestApi("/analysis/version")).thenReturn(SQ_VERSION_NEW_BOOTSTRAPPING);
126-
try (var bootstrapResult = underTest.setBootstrapProperty(ScannerProperties.HOST_URL, "http://localhost").bootstrap()) {
147+
try (var bootstrapResult = underTest.setBootstrapProperty(ScannerProperties.HOST_URL, "http://localhost:1234/").bootstrap()) {
127148
verify(scannerEngineLauncherFactory).createLauncher(eq(scannerHttpClient), any(FileCache.class), anyMap());
128149
assertThat(bootstrapResult.getEngineFacade().isSonarQubeCloud()).isFalse();
129150
verifySonarQubeServerTypeLogged(SQ_VERSION_NEW_BOOTSTRAPPING);
130151
assertThat(bootstrapResult.getEngineFacade().getServerVersion()).isEqualTo(SQ_VERSION_NEW_BOOTSTRAPPING);
152+
assertThat(bootstrapResult.getEngineFacade().getBootstrapProperties()).contains(
153+
entry(ScannerProperties.HOST_URL, "http://localhost:1234"),
154+
entry(ScannerProperties.API_BASE_URL, "http://localhost:1234/api/v2"));
131155
assertThat(logTester.logs(Level.WARN)).isEmpty();
132156
}
133157
}
@@ -141,7 +165,8 @@ void should_report_apiv2_error_with_sq_10_6_even_if_older_version_ws_succeeded()
141165
}
142166

143167
assertThat(logTester.logs(Level.ERROR))
144-
.contains("Failed to query server version: GET http://myserver/api/v2/analysis/version failed with HTTP 401. Please check the property sonar.token or the environment variable SONAR_TOKEN.");
168+
.contains(
169+
"Failed to query server version: GET http://myserver/api/v2/analysis/version failed with HTTP 401. Please check the property sonar.token or the environment variable SONAR_TOKEN.");
145170
}
146171

147172
@Test
@@ -234,17 +259,21 @@ void should_show_help_on_proxy_auth_error() throws Exception {
234259

235260
ScannerEngineBootstrapper bootstrapper = new ScannerEngineBootstrapper("Gradle", "3.1", system, scannerHttpClient,
236261
launcherFactory, scannerEngineLauncherFactory);
237-
when(scannerHttpClient.callRestApi("/analysis/version")).thenThrow(new HttpException(URI.create("http://myserver/analysis/version").toURL(), 407, "Proxy Authentication Required", null));
238-
when(scannerHttpClient.callWebApi("/api/server/version")).thenThrow(new HttpException(URI.create("http://myserver/api/server/version").toURL(), 407, "Proxy Authentication Required", null));
262+
when(scannerHttpClient.callRestApi("/analysis/version"))
263+
.thenThrow(new HttpException(URI.create("http://myserver/analysis/version").toURL(), 407, "Proxy Authentication Required", null));
264+
when(scannerHttpClient.callWebApi("/api/server/version"))
265+
.thenThrow(new HttpException(URI.create("http://myserver/api/server/version").toURL(), 407, "Proxy Authentication Required", null));
239266

240267
logTester.setLevel(Level.DEBUG);
241268

242269
try (var result = bootstrapper.setBootstrapProperty(ScannerProperties.HOST_URL, "http://myserver").bootstrap()) {
243270
assertThat(result.isSuccessful()).isFalse();
244271
}
245272

246-
assertThat(logTester.logs(Level.ERROR)).contains("Failed to query server version: GET http://myserver/api/server/version failed with HTTP 407 Proxy Authentication Required. Please check the properties sonar.scanner.proxyUser and " +
247-
"sonar.scanner.proxyPassword.");
273+
assertThat(logTester.logs(Level.ERROR)).contains(
274+
"Failed to query server version: GET http://myserver/api/server/version failed with HTTP 407 Proxy Authentication Required. Please check the properties sonar.scanner.proxyUser and "
275+
+
276+
"sonar.scanner.proxyPassword.");
248277
assertThatNoServerTypeIsLogged();
249278
}
250279

@@ -275,7 +304,6 @@ void should_preserve_both_exceptions_when_checking_version() throws Exception {
275304
assertThatNoServerTypeIsLogged();
276305
}
277306

278-
279307
@ParameterizedTest
280308
@ValueSource(ints = {401, 403})
281309
void should_log_user_friendly_message_when_auth_error(int code) throws Exception {
@@ -292,7 +320,8 @@ void should_log_user_friendly_message_when_auth_error(int code) throws Exception
292320
assertThat(result.isSuccessful()).isFalse();
293321
}
294322

295-
assertThat(logTester.logs(Level.ERROR)).contains("Failed to query server version: GET http://myserver failed with HTTP " + code + " Unauthorized. Please check the property sonar.token or the environment variable SONAR_TOKEN" +
323+
assertThat(logTester.logs(Level.ERROR)).contains("Failed to query server version: GET http://myserver failed with HTTP " + code
324+
+ " Unauthorized. Please check the property sonar.token or the environment variable SONAR_TOKEN" +
296325
".");
297326
assertThatNoServerTypeIsLogged();
298327
}
@@ -507,8 +536,7 @@ private static Stream<Arguments> provideServerVersionAndTypeArgumentPairs() {
507536
Arguments.of("10.6", "SonarQube Server"),
508537
Arguments.of("2025.1.0.1234", "SonarQube Server"),
509538
Arguments.of("24.12", "SonarQube Community Build"),
510-
Arguments.of("25.1.0.1234", "SonarQube Community Build")
511-
);
539+
Arguments.of("25.1.0.1234", "SonarQube Community Build"));
512540
}
513541

514542
private void verifySonarQubeServerTypeLogged(String version) {

lib/src/test/java/org/sonarsource/scanner/lib/internal/endpoint/ScannerEndpointResolverTest.java

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
import java.util.Map;
2323
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.params.ParameterizedTest;
25+
import org.junit.jupiter.params.provider.ValueSource;
2426
import org.sonarsource.scanner.lib.ScannerProperties;
2527
import org.sonarsource.scanner.lib.internal.MessageException;
2628

@@ -38,9 +40,10 @@ void should_resolve_sonarqube_cloud_global_by_default() {
3840
assertThat(endpoint.getApiEndpoint()).isEqualTo("https://api.sonarcloud.io");
3941
}
4042

41-
@Test
42-
void should_recognize_sonarqube_cloud_endpoint_passed_through_host_url() {
43-
var props = Map.of(ScannerProperties.HOST_URL, "https://sonarcloud.io");
43+
@ParameterizedTest
44+
@ValueSource(strings = {"https://sonarcloud.io", "http://sonarcloud.io", "https://sonarcloud.io/", " https://sonarcloud.io ", "https://www.sonarcloud.io/"})
45+
void should_recognize_sonarqube_cloud_endpoint_passed_through_host_url(String hostUrl) {
46+
var props = Map.of(ScannerProperties.HOST_URL, hostUrl);
4447

4548
var endpoint = ScannerEndpointResolver.resolveEndpoint(props);
4649

@@ -49,9 +52,10 @@ void should_recognize_sonarqube_cloud_endpoint_passed_through_host_url() {
4952
assertThat(endpoint.getApiEndpoint()).isEqualTo("https://api.sonarcloud.io");
5053
}
5154

52-
@Test
53-
void should_recognize_sonarqube_cloud_us_endpoint_passed_through_host_url() {
54-
var props = Map.of(ScannerProperties.HOST_URL, "https://sonarqube.us");
55+
@ParameterizedTest
56+
@ValueSource(strings = {"https://sonarqube.us", "https://sonarqube.us/", " https://sonarqube.us ", "https://www.sonarqube.us/"})
57+
void should_recognize_sonarqube_cloud_us_endpoint_passed_through_host_url(String hostUrl) {
58+
var props = Map.of(ScannerProperties.HOST_URL, hostUrl);
5559

5660
var endpoint = ScannerEndpointResolver.resolveEndpoint(props);
5761

@@ -72,8 +76,20 @@ void should_recognize_sonarqube_server_endpoint_with_path() {
7276
}
7377

7478
@Test
75-
void should_recognize_sonarqube_cloud_endpoint_passed_through_cloud_url() {
76-
var props = Map.of(ScannerProperties.SONARQUBE_CLOUD_URL, "https://sonarcloud.io");
79+
void should_recognize_sonarqube_server_endpoint_with_path_ending_with_slash() {
80+
var props = Map.of(ScannerProperties.HOST_URL, "https://next.sonarqube.com/sonarqube/");
81+
82+
var endpoint = ScannerEndpointResolver.resolveEndpoint(props);
83+
84+
assertThat(endpoint.isSonarQubeCloud()).isFalse();
85+
assertThat(endpoint.getWebEndpoint()).isEqualTo("https://next.sonarqube.com/sonarqube");
86+
assertThat(endpoint.getApiEndpoint()).isEqualTo("https://next.sonarqube.com/sonarqube/api/v2");
87+
}
88+
89+
@ParameterizedTest
90+
@ValueSource(strings = {"https://sonarcloud.io", "https://sonarcloud.io/", " https://sonarcloud.io ", "https://www.sonarcloud.io/"})
91+
void should_recognize_sonarqube_cloud_endpoint_passed_through_cloud_url(String cloudUrl) {
92+
var props = Map.of(ScannerProperties.SONARQUBE_CLOUD_URL, cloudUrl);
7793

7894
var endpoint = ScannerEndpointResolver.resolveEndpoint(props);
7995

0 commit comments

Comments
 (0)