-
Notifications
You must be signed in to change notification settings - Fork 215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Confluence as source plugin #5404
base: main
Are you sure you want to change the base?
Changes from 16 commits
ae65748
d20e6fa
98660a1
b358db7
6f40d65
378c41f
d9a98d0
04ac7c0
0e89bf0
66befd5
4fde3e9
6bd3451
f8e8d45
47ee223
15e0b4a
14a5870
6ae21b9
f207bcc
aaaf3fc
b68fd99
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
plugins { | ||
id 'java' | ||
} | ||
|
||
|
||
dependencies { | ||
|
||
implementation project(path: ':data-prepper-api') | ||
implementation project(path: ':data-prepper-plugins:saas-source-plugins:source-crawler') | ||
implementation project(path: ':data-prepper-plugins:common') | ||
|
||
implementation 'com.fasterxml.jackson.core:jackson-core' | ||
implementation 'com.fasterxml.jackson.core:jackson-databind' | ||
|
||
implementation 'io.micrometer:micrometer-core' | ||
implementation 'org.projectlombok:lombok:1.18.30' | ||
annotationProcessor 'org.projectlombok:lombok:1.18.30' | ||
implementation("org.springframework:spring-web:${libs.versions.spring.get()}") | ||
|
||
implementation(libs.spring.context) { | ||
exclude group: 'commons-logging', module: 'commons-logging' | ||
} | ||
|
||
testImplementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.4' | ||
testImplementation project(path: ':data-prepper-test-common') | ||
} | ||
|
||
test { | ||
useJUnitPlatform() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
*/ | ||
|
||
package org.opensearch.dataprepper.plugins.source.atlassian; | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import jakarta.validation.Valid; | ||
import lombok.Getter; | ||
import org.opensearch.dataprepper.plugins.source.atlassian.configuration.AuthenticationConfig; | ||
import org.opensearch.dataprepper.plugins.source.source_crawler.base.CrawlerSourceConfig; | ||
|
||
import java.util.List; | ||
|
||
@Getter | ||
public class AtlassianSourceConfig implements CrawlerSourceConfig { | ||
|
||
private static final int DEFAULT_BATCH_SIZE = 50; | ||
|
||
/** | ||
* Jira account url | ||
*/ | ||
@JsonProperty("hosts") | ||
protected List<String> hosts; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this a list of hosts? It seems the code only supports one host and that would be simpler for users:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a recommendation from Raj to support future expansion I guess. |
||
|
||
/** | ||
* Authentication Config to Access Jira | ||
*/ | ||
@JsonProperty("authentication") | ||
@Valid | ||
protected AuthenticationConfig authenticationConfig; | ||
|
||
/** | ||
* Batch size for fetching tickets | ||
*/ | ||
@JsonProperty("batch_size") | ||
protected int batchSize = DEFAULT_BATCH_SIZE; | ||
|
||
|
||
/** | ||
* Boolean property indicating end to end acknowledgments state | ||
*/ | ||
@JsonProperty("acknowledgments") | ||
private boolean acknowledgments = false; | ||
|
||
public String getAccountUrl() { | ||
return this.getHosts().get(0); | ||
} | ||
|
||
public String getAuthType() { | ||
return this.getAuthenticationConfig().getAuthType(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
*/ | ||
|
||
package org.opensearch.dataprepper.plugins.source.atlassian.configuration; | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import jakarta.validation.Valid; | ||
import jakarta.validation.constraints.AssertTrue; | ||
import lombok.Getter; | ||
|
||
import static org.opensearch.dataprepper.plugins.source.atlassian.utils.Constants.BASIC; | ||
import static org.opensearch.dataprepper.plugins.source.atlassian.utils.Constants.OAUTH2; | ||
|
||
|
||
@Getter | ||
public class AuthenticationConfig { | ||
@JsonProperty("basic") | ||
@Valid | ||
private BasicConfig basicConfig; | ||
|
||
@JsonProperty("oauth2") | ||
@Valid | ||
private Oauth2Config oauth2Config; | ||
|
||
@AssertTrue(message = "Authentication config should have either basic or oauth2") | ||
private boolean isValidAuthenticationConfig() { | ||
boolean hasBasic = basicConfig != null; | ||
boolean hasOauth = oauth2Config != null; | ||
return hasBasic ^ hasOauth; | ||
} | ||
|
||
public String getAuthType() { | ||
if (basicConfig != null) { | ||
return BASIC; | ||
} else { | ||
return OAUTH2; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
*/ | ||
|
||
package org.opensearch.dataprepper.plugins.source.atlassian.configuration; | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import jakarta.validation.constraints.AssertTrue; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
public class BasicConfig { | ||
@JsonProperty("username") | ||
private String username; | ||
|
||
@JsonProperty("password") | ||
private String password; | ||
|
||
@AssertTrue(message = "Username and Password are both required for Basic Auth") | ||
private boolean isBasicConfigValid() { | ||
return username != null && password != null; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
*/ | ||
|
||
package org.opensearch.dataprepper.plugins.source.atlassian.configuration; | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import jakarta.validation.constraints.AssertTrue; | ||
import lombok.Getter; | ||
import org.opensearch.dataprepper.model.plugin.PluginConfigVariable; | ||
|
||
@Getter | ||
public class Oauth2Config { | ||
@JsonProperty("client_id") | ||
private String clientId; | ||
|
||
@JsonProperty("client_secret") | ||
private String clientSecret; | ||
|
||
@JsonProperty("access_token") | ||
private PluginConfigVariable accessToken; | ||
|
||
@JsonProperty("refresh_token") | ||
private PluginConfigVariable refreshToken; | ||
|
||
@AssertTrue(message = "Client ID, Client Secret, Access Token, and Refresh Token are both required for Oauth2") | ||
private boolean isOauth2ConfigValid() { | ||
return clientId != null && clientSecret != null && accessToken != null && refreshToken != null; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
*/ | ||
|
||
package org.opensearch.dataprepper.plugins.source.atlassian.rest; | ||
|
||
import com.google.common.annotations.VisibleForTesting; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.opensearch.dataprepper.plugins.source.atlassian.rest.auth.AtlassianAuthConfig; | ||
import org.opensearch.dataprepper.plugins.source.source_crawler.exception.BadRequestException; | ||
import org.opensearch.dataprepper.plugins.source.source_crawler.exception.UnAuthorizedException; | ||
import org.opensearch.dataprepper.plugins.source.source_crawler.utils.AddressValidation; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.client.HttpClientErrorException; | ||
import org.springframework.web.client.RestTemplate; | ||
|
||
import java.net.URI; | ||
import java.util.List; | ||
|
||
import static org.opensearch.dataprepper.logging.DataPrepperMarkers.NOISY; | ||
import static org.opensearch.dataprepper.plugins.source.atlassian.utils.Constants.MAX_RETRIES; | ||
|
||
@Slf4j | ||
public class AtlassianRestClient { | ||
|
||
public static final List<Integer> RETRY_ATTEMPT_SLEEP_TIME = List.of(1, 2, 5, 10, 20, 40); | ||
private int sleepTimeMultiplier = 1000; | ||
private final RestTemplate restTemplate; | ||
private final AtlassianAuthConfig authConfig; | ||
|
||
public AtlassianRestClient(RestTemplate restTemplate, AtlassianAuthConfig authConfig) { | ||
this.restTemplate = restTemplate; | ||
this.authConfig = authConfig; | ||
} | ||
|
||
|
||
protected <T> ResponseEntity<T> invokeRestApi(URI uri, Class<T> responseType) throws BadRequestException { | ||
AddressValidation.validateInetAddress(AddressValidation.getInetAddress(uri.toString())); | ||
int retryCount = 0; | ||
while (retryCount < MAX_RETRIES) { | ||
try { | ||
return restTemplate.getForEntity(uri, responseType); | ||
} catch (HttpClientErrorException ex) { | ||
HttpStatus statusCode = ex.getStatusCode(); | ||
String statusMessage = ex.getMessage(); | ||
log.error("An exception has occurred while getting response from Jira search API {}", ex.getMessage()); | ||
if (statusCode == HttpStatus.FORBIDDEN) { | ||
throw new UnAuthorizedException(statusMessage); | ||
} else if (statusCode == HttpStatus.UNAUTHORIZED) { | ||
log.error(NOISY, "Token expired. We will try to renew the tokens now", ex); | ||
authConfig.renewCredentials(); | ||
} else if (statusCode == HttpStatus.TOO_MANY_REQUESTS) { | ||
log.error(NOISY, "Hitting API rate limit. Backing off with sleep timer.", ex); | ||
} | ||
try { | ||
Thread.sleep((long) RETRY_ATTEMPT_SLEEP_TIME.get(retryCount) * sleepTimeMultiplier); | ||
} catch (InterruptedException e) { | ||
throw new RuntimeException("Sleep in the retry attempt got interrupted", e); | ||
} | ||
} | ||
retryCount++; | ||
} | ||
String errorMessage = String.format("Exceeded max retry attempts. Failed to execute the Rest API call %s", uri); | ||
log.error(errorMessage); | ||
throw new RuntimeException(errorMessage); | ||
} | ||
|
||
@VisibleForTesting | ||
public void setSleepTimeMultiplier(int multiplier) { | ||
sleepTimeMultiplier = multiplier; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
*/ | ||
|
||
package org.opensearch.dataprepper.plugins.source.atlassian.rest; | ||
|
||
import org.opensearch.dataprepper.plugins.source.atlassian.AtlassianSourceConfig; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.HttpRequest; | ||
import org.springframework.http.client.ClientHttpRequestExecution; | ||
import org.springframework.http.client.ClientHttpRequestInterceptor; | ||
import org.springframework.http.client.ClientHttpResponse; | ||
|
||
import java.io.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Base64; | ||
|
||
|
||
public class BasicAuthInterceptor implements ClientHttpRequestInterceptor { | ||
private final String username; | ||
private final String password; | ||
|
||
public BasicAuthInterceptor(AtlassianSourceConfig config) { | ||
this.username = config.getAuthenticationConfig().getBasicConfig().getUsername(); | ||
this.password = config.getAuthenticationConfig().getBasicConfig().getPassword(); | ||
} | ||
|
||
@Override | ||
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { | ||
String auth = username + ":" + password; | ||
byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.US_ASCII)); | ||
String authHeader = "Basic " + new String(encodedAuth); | ||
request.getHeaders().set(HttpHeaders.AUTHORIZATION, authHeader); | ||
return execution.execute(request, body); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
*/ | ||
|
||
package org.opensearch.dataprepper.plugins.source.atlassian.rest; | ||
|
||
|
||
import org.opensearch.dataprepper.plugins.source.atlassian.AtlassianSourceConfig; | ||
import org.opensearch.dataprepper.plugins.source.atlassian.rest.auth.AtlassianAuthConfig; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.http.client.ClientHttpRequestInterceptor; | ||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; | ||
import org.springframework.web.client.RestTemplate; | ||
|
||
import static org.opensearch.dataprepper.plugins.source.atlassian.utils.Constants.OAUTH2; | ||
|
||
@Configuration | ||
public class CustomRestTemplateConfig { | ||
|
||
@Bean | ||
public RestTemplate basicAuthRestTemplate(AtlassianSourceConfig config, AtlassianAuthConfig authConfig) { | ||
RestTemplate restTemplate = new RestTemplate(); | ||
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory()); | ||
ClientHttpRequestInterceptor httpInterceptor; | ||
if (OAUTH2.equals(config.getAuthType())) { | ||
httpInterceptor = new OAuth2RequestInterceptor(authConfig); | ||
} else { | ||
httpInterceptor = new BasicAuthInterceptor(config); | ||
} | ||
restTemplate.getInterceptors().add(httpInterceptor); | ||
return restTemplate; | ||
} | ||
|
||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need this. It is inherited.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed