Skip to content

Commit 3428153

Browse files
authored
EPMRPP-114079 || add SQL script to restore GitHub integration (#4)
* EPMRPP-114079 || add SQL script to restore GitHub integration from backup and update GitHubExtension to initialize schema
1 parent 92cb289 commit 3428153

5 files changed

Lines changed: 284 additions & 5 deletions

File tree

src/main/java/com/epam/reportportal/extension/github/GitHubExtension.java

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.epam.reportportal.base.infrastructure.commons.ContentTypeResolver;
2828
import com.epam.reportportal.base.infrastructure.persistence.binary.UserBinaryDataService;
2929
import com.epam.reportportal.base.infrastructure.persistence.dao.IntegrationRepository;
30+
import com.epam.reportportal.base.infrastructure.persistence.dao.IntegrationTypeRepository;
3031
import com.epam.reportportal.base.infrastructure.persistence.dao.ProjectRepository;
3132
import com.epam.reportportal.base.infrastructure.persistence.dao.UserRepository;
3233
import com.epam.reportportal.base.infrastructure.persistence.entity.enums.IntegrationAuthFlowEnum;
@@ -36,20 +37,27 @@
3637
import com.epam.reportportal.extension.IntegrationGroupEnum;
3738
import com.epam.reportportal.extension.PluginCommand;
3839
import com.epam.reportportal.extension.github.command.SynchronizeGithubUserCommand;
40+
import com.epam.reportportal.extension.github.event.listener.PluginLoadedEventListener;
3941
import com.epam.reportportal.extension.github.oauth.GitHubOAuthProvider;
4042
import com.epam.reportportal.extension.github.service.GitHubIntegrationStrategy;
4143
import com.epam.reportportal.extension.github.service.GitHubRequiredParamNamesProvider;
4244
import com.epam.reportportal.extension.github.utils.MemoizingSupplier;
4345
import jakarta.annotation.PostConstruct;
46+
import java.io.IOException;
4447
import java.util.ArrayList;
4548
import java.util.HashMap;
4649
import java.util.Map;
4750
import java.util.Optional;
4851
import java.util.function.Supplier;
52+
import javax.sql.DataSource;
4953
import lombok.extern.slf4j.Slf4j;
5054
import org.jasypt.util.text.BasicTextEncryptor;
5155
import org.pf4j.Extension;
56+
import org.springframework.beans.factory.DisposableBean;
5257
import org.springframework.beans.factory.annotation.Autowired;
58+
import org.springframework.context.ApplicationContext;
59+
import org.springframework.context.event.ApplicationEventMulticaster;
60+
import org.springframework.context.support.AbstractApplicationContext;
5361
import org.springframework.security.authentication.AuthenticationProvider;
5462
import org.springframework.security.core.Authentication;
5563

@@ -58,11 +66,12 @@
5866
*/
5967
@Extension
6068
@Slf4j
61-
public class GitHubExtension implements AuthExtension {
69+
public class GitHubExtension implements AuthExtension, DisposableBean {
6270

6371
public static final String SSO_LOGIN_PATH = "/oauth/login";
64-
public static final String SCHEMA_SCRIPTS_DIR = "schema";
72+
public static final String SCHEMA_SCRIPTS_DIR = "resources/schema";
6573

74+
private static final String PLUGIN_ID = "github";
6675
private static final String PLUGIN_NAME = "GitHub OAuth Plugin";
6776
private static final String DOCUMENTATION_LINK = "https://reportportal.io/docs/plugins/authorization/GitHubAuthorization";
6877
private static final String DOCUMENTATION_LINK_FIELD = "documentationLink";
@@ -80,6 +89,12 @@ public boolean supports(Class<?> authentication) {
8089
}
8190
};
8291

92+
@Autowired
93+
private ApplicationContext applicationContext;
94+
95+
@Autowired
96+
private IntegrationTypeRepository integrationTypeRepository;
97+
8398
@Autowired
8499
private UserRepository userRepository;
85100

@@ -107,21 +122,29 @@ public boolean supports(Class<?> authentication) {
107122
@Autowired
108123
private BasicTextEncryptor encryptor;
109124

125+
@Autowired
126+
private DataSource dataSource;
127+
110128
private GitHubUserReplicator replicator;
111129
private GitHubOAuthProvider oauthProvider;
112130
private Map<String, CommonPluginCommand<?>> commonCommands;
113131

114132
private Supplier<GitHubIntegrationStrategy> gitHubIntegrationStrategySupplier;
133+
private Supplier<PluginLoadedEventListener> pluginLoadedListenerSupplier;
115134

116135

117136
@PostConstruct
118-
public void init() {
137+
public void init() throws IOException {
119138
log.info("Initializing GitHub OAuth extension");
120139
this.gitHubIntegrationStrategySupplier = new MemoizingSupplier<>(
121140
() -> new GitHubIntegrationStrategy(integrationRepository,
122141
new UpdateAuthRequestValidator(new GitHubRequiredParamNamesProvider()), integrationDuplicateValidator,
123142
encryptor));
124143

144+
this.pluginLoadedListenerSupplier = new MemoizingSupplier<>(
145+
() -> new PluginLoadedEventListener(PLUGIN_ID, integrationTypeRepository, integrationRepository,
146+
integrationType -> integrationType, dataSource));
147+
125148
replicator = new GitHubUserReplicator(
126149
userRepository, projectRepository, personalProjectService,
127150
userBinaryDataService, contentTypeResolver, userEventPublisher
@@ -130,8 +153,24 @@ public void init() {
130153
SynchronizeGithubUserCommand syncCommand = new SynchronizeGithubUserCommand(replicator);
131154
commonCommands = Map.of(syncCommand.getName(), syncCommand);
132155

133-
/* initApplicationListeners();
134-
initSchema();*/
156+
initListeners();
157+
}
158+
159+
private void initListeners() {
160+
ApplicationEventMulticaster applicationEventMulticaster = applicationContext.getBean(
161+
AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME,
162+
ApplicationEventMulticaster.class
163+
);
164+
applicationEventMulticaster.addApplicationListener(pluginLoadedListenerSupplier.get());
165+
}
166+
167+
@Override
168+
public void destroy() {
169+
ApplicationEventMulticaster applicationEventMulticaster = applicationContext.getBean(
170+
AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME,
171+
ApplicationEventMulticaster.class
172+
);
173+
applicationEventMulticaster.removeApplicationListener(pluginLoadedListenerSupplier.get());
135174
}
136175

137176
@Override
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2026 EPAM Systems
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.epam.reportportal.extension.github.event.listener;
18+
19+
import static com.epam.reportportal.extension.github.GitHubExtension.SCHEMA_SCRIPTS_DIR;
20+
21+
import com.epam.reportportal.base.core.events.domain.PluginUploadedEvent;
22+
import com.epam.reportportal.base.infrastructure.persistence.dao.IntegrationRepository;
23+
import com.epam.reportportal.base.infrastructure.persistence.dao.IntegrationTypeRepository;
24+
import com.epam.reportportal.extension.github.info.PluginInfoProvider;
25+
import java.io.InputStream;
26+
import java.nio.charset.StandardCharsets;
27+
import java.util.Arrays;
28+
import java.util.Comparator;
29+
import java.util.Objects;
30+
import javax.sql.DataSource;
31+
import lombok.SneakyThrows;
32+
import lombok.extern.slf4j.Slf4j;
33+
import org.springframework.context.ApplicationListener;
34+
import org.springframework.core.io.Resource;
35+
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
36+
import org.springframework.jdbc.core.JdbcTemplate;
37+
38+
/**
39+
* @author Andrei Piankouski
40+
*/
41+
@Slf4j
42+
public class PluginLoadedEventListener implements ApplicationListener<PluginUploadedEvent> {
43+
44+
private final String pluginId;
45+
private final IntegrationTypeRepository integrationTypeRepository;
46+
private final IntegrationRepository integrationRepository;
47+
private final PluginInfoProvider pluginInfoProvider;
48+
private final DataSource dataSource;
49+
50+
public PluginLoadedEventListener(String pluginId,
51+
IntegrationTypeRepository integrationTypeRepository,
52+
IntegrationRepository integrationRepository, PluginInfoProvider pluginInfoProvider, DataSource dataSource) {
53+
this.pluginId = pluginId;
54+
this.integrationTypeRepository = integrationTypeRepository;
55+
this.integrationRepository = integrationRepository;
56+
this.pluginInfoProvider = pluginInfoProvider;
57+
this.dataSource = dataSource;
58+
}
59+
60+
@Override
61+
public void onApplicationEvent(PluginUploadedEvent event) {
62+
if (!supports(event)) {
63+
return;
64+
}
65+
initSchema();
66+
}
67+
68+
private boolean supports(PluginUploadedEvent event) {
69+
return Objects.nonNull(event.getPluginActivityResource())
70+
&& pluginId.equals(event.getPluginActivityResource().getName());
71+
}
72+
73+
@SneakyThrows
74+
public void initSchema() {
75+
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(getClass().getClassLoader());
76+
Resource[] resources = resolver.getResources("classpath:" + SCHEMA_SCRIPTS_DIR + "/*.sql");
77+
log.debug("GitHub schema init: found {} script(s)", resources.length);
78+
if (resources.length == 0) {
79+
return;
80+
}
81+
Arrays.sort(resources, Comparator.comparing(Resource::getFilename));
82+
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
83+
for (Resource r : resources) {
84+
log.info("GitHub schema init: executing {}", r.getFilename());
85+
try (InputStream is = r.getInputStream()) {
86+
String sql = new String(is.readAllBytes(), StandardCharsets.UTF_8);
87+
jdbcTemplate.execute(sql);
88+
}
89+
}
90+
log.info("GitHub schema init: completed");
91+
}
92+
93+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2026 EPAM Systems
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.epam.reportportal.extension.github.info;
18+
19+
import com.epam.reportportal.base.infrastructure.persistence.entity.integration.IntegrationType;
20+
21+
/**
22+
* @author <a href="mailto:ivan_budayeu@epam.com">Ivan Budayeu</a>
23+
*/
24+
public interface PluginInfoProvider {
25+
26+
IntegrationType provide(IntegrationType integrationType);
27+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2026 EPAM Systems
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.epam.reportportal.extension.github.info.impl;
18+
19+
import static java.util.Optional.ofNullable;
20+
21+
import com.epam.reportportal.base.infrastructure.persistence.entity.integration.IntegrationType;
22+
import com.epam.reportportal.base.infrastructure.rules.exception.ErrorType;
23+
import com.epam.reportportal.base.infrastructure.rules.exception.ReportPortalException;
24+
import com.epam.reportportal.extension.github.info.PluginInfoProvider;
25+
import java.io.IOException;
26+
import java.io.InputStream;
27+
import java.nio.file.Files;
28+
import java.nio.file.Paths;
29+
import java.util.HashMap;
30+
import java.util.Map;
31+
import java.util.Properties;
32+
33+
/**
34+
* @author <a href="mailto:ivan_budayeu@epam.com">Ivan Budayeu</a>
35+
*/
36+
public class PluginInfoProviderImpl implements PluginInfoProvider {
37+
38+
private static final String BINARY_DATA_KEY = "binaryData";
39+
private static final String DESCRIPTION_KEY = "description";
40+
private static final String METADATA_KEY = "metadata";
41+
42+
private static final String PLUGIN_DESCRIPTION =
43+
"The integration provides an exchange of information between ReportPortal and the Jira Cloud, such as posting issues and linking issues, getting updates on their statuses.";
44+
45+
static final Map<String, Object> PLUGIN_METADATA = Map.of(
46+
"embedded", false,
47+
"multiple", false
48+
);
49+
50+
private final String resourcesDir;
51+
private final String propertyFile;
52+
53+
public PluginInfoProviderImpl(String resourcesDir, String propertyFile) {
54+
this.resourcesDir = resourcesDir;
55+
this.propertyFile = propertyFile;
56+
}
57+
58+
@Override
59+
public IntegrationType provide(IntegrationType integrationType) {
60+
loadBinaryDataInfo(integrationType);
61+
updateDescription(integrationType);
62+
updateMetadata(integrationType);
63+
return integrationType;
64+
}
65+
66+
private void loadBinaryDataInfo(IntegrationType integrationType) {
67+
Map<String, Object> details = integrationType.getDetails().getDetails();
68+
if (ofNullable(details.get(BINARY_DATA_KEY)).isEmpty()) {
69+
try (InputStream propertiesStream = Files.newInputStream(
70+
Paths.get(resourcesDir, propertyFile))) {
71+
Properties binaryDataProperties = new Properties();
72+
binaryDataProperties.load(propertiesStream);
73+
Map<String, String> binaryDataInfo = binaryDataProperties.entrySet().stream().collect(
74+
HashMap::new, (map, entry) -> map.put(String.valueOf(entry.getKey()),
75+
String.valueOf(entry.getValue())
76+
), HashMap::putAll);
77+
details.put(BINARY_DATA_KEY, binaryDataInfo);
78+
} catch (IOException ex) {
79+
throw new ReportPortalException(ErrorType.UNABLE_TO_LOAD_BINARY_DATA, ex.getMessage());
80+
}
81+
}
82+
}
83+
84+
private void updateDescription(IntegrationType integrationType) {
85+
Map<String, Object> details = integrationType.getDetails().getDetails();
86+
details.put(DESCRIPTION_KEY, PLUGIN_DESCRIPTION);
87+
}
88+
89+
private void updateMetadata(IntegrationType integrationType) {
90+
Map<String, Object> details = integrationType.getDetails().getDetails();
91+
details.put(METADATA_KEY, PLUGIN_METADATA);
92+
}
93+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
DO
2+
'
3+
BEGIN
4+
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = ''integration_backup'') THEN
5+
6+
-- restore saml integration from backup
7+
INSERT INTO integration (name, type, params, creator, creation_date, enabled)
8+
SELECT ib.name,
9+
(SELECT it.id FROM integration_type it WHERE it.auth_flow = ''OAUTH'' AND it.group_type = ''AUTH'' AND it.plugin_type = ''EXTENSION''),
10+
ib.params,
11+
''SYSTEM'',
12+
now(),
13+
true
14+
FROM integration_backup ib
15+
WHERE ib.auth_type = ''github''
16+
ON CONFLICT (id) DO NOTHING;
17+
18+
-- delete backup record
19+
DELETE from integration_backup ib WHERE ib.auth_type = ''github'';
20+
21+
-- drop table if empty
22+
IF NOT EXISTS (SELECT 1 FROM integration_backup) THEN
23+
DROP TABLE integration_backup;
24+
END IF;
25+
END IF;
26+
END;
27+
';

0 commit comments

Comments
 (0)