Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jboss.pnc.facade.providers.SCMRepositoryProviderImpl Maven / Gradle / Ivy
/**
* JBoss, Home of Professional Open Source.
* Copyright 2014-2022 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.pnc.facade.providers;
import org.jboss.pnc.auth.KeycloakServiceClient;
import org.jboss.pnc.bpm.BpmEventType;
import org.jboss.pnc.bpm.Connector;
import org.jboss.pnc.bpm.model.RepositoryCreationProcess;
import org.jboss.pnc.bpm.task.RepositoryCreationTask;
import org.jboss.pnc.common.Configuration;
import org.jboss.pnc.common.concurrent.Sequence;
import org.jboss.pnc.common.json.ConfigurationParseException;
import org.jboss.pnc.common.json.GlobalModuleGroup;
import org.jboss.pnc.common.json.moduleconfig.BpmModuleConfig;
import org.jboss.pnc.common.json.moduleconfig.ScmModuleConfig;
import org.jboss.pnc.common.json.moduleprovider.PncConfigProvider;
import org.jboss.pnc.common.net.GitSCPUrl;
import org.jboss.pnc.common.scm.ScmException;
import org.jboss.pnc.common.scm.ScmUrlGeneratorProvider;
import org.jboss.pnc.common.util.StringUtils;
import org.jboss.pnc.common.util.UrlUtils;
import org.jboss.pnc.dto.BuildConfiguration;
import org.jboss.pnc.dto.SCMRepository;
import org.jboss.pnc.dto.notification.RepositoryCreationFailure;
import org.jboss.pnc.dto.notification.SCMRepositoryCreationSuccess;
import org.jboss.pnc.dto.response.Page;
import org.jboss.pnc.dto.response.RepositoryCreationResponse;
import org.jboss.pnc.dto.tasks.RepositoryCreationResult;
import org.jboss.pnc.enums.JobNotificationType;
import org.jboss.pnc.facade.providers.api.BuildConfigurationProvider;
import org.jboss.pnc.facade.providers.api.SCMRepositoryProvider;
import org.jboss.pnc.facade.util.RepourClient;
import org.jboss.pnc.facade.validation.ConflictedEntryException;
import org.jboss.pnc.facade.validation.InvalidEntityException;
import org.jboss.pnc.mapper.api.SCMRepositoryMapper;
import org.jboss.pnc.model.RepositoryConfiguration;
import org.jboss.pnc.spi.datastore.predicates.RepositoryConfigurationPredicates;
import org.jboss.pnc.spi.datastore.repositories.RepositoryConfigurationRepository;
import org.jboss.pnc.spi.datastore.repositories.api.Predicate;
import org.jboss.pnc.spi.exception.CoreException;
import org.jboss.pnc.spi.exception.ProcessManagerException;
import org.jboss.pnc.spi.notifications.Notifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.security.PermitAll;
import javax.ejb.Stateless;
import javax.inject.Inject;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Pattern;
import static org.jboss.pnc.constants.Patterns.INTERNAL_REPOSITORY_NAME;
import static org.jboss.pnc.enums.JobNotificationType.SCM_REPOSITORY_CREATION;
import static org.jboss.pnc.spi.datastore.predicates.RepositoryConfigurationPredicates.withExactInternalScmRepoUrl;
@PermitAll
@Stateless
public class SCMRepositoryProviderImpl
extends AbstractUpdatableProvider
implements SCMRepositoryProvider {
private static final Logger log = LoggerFactory.getLogger(SCMRepositoryProviderImpl.class);
private static final String RC_REPO_CREATION_CONFLICT = "RC_REPO_CREATION_CONFLICT";
private ScmModuleConfig config;
private static final Pattern REPOSITORY_NAME_PATTERN = Pattern.compile(INTERNAL_REPOSITORY_NAME);
@Inject
private RepositoryConfigurationRepository repositoryConfigurationRepository;
@Inject
private KeycloakServiceClient keycloakServiceClient;
@Inject
private Notifier notifier;
@Inject
private BpmModuleConfig bpmConfig;
@Inject
private GlobalModuleGroup globalConfig;
@Inject
private RepourClient repour;
@Inject
private BuildConfigurationProvider buildConfigurationProvider;
@Inject
Connector connector;
@Inject
public SCMRepositoryProviderImpl(
RepositoryConfigurationRepository repository,
SCMRepositoryMapper mapper,
Configuration configuration) throws ConfigurationParseException {
super(repository, mapper, RepositoryConfiguration.class);
this.config = configuration.getModuleConfig(new PncConfigProvider<>(ScmModuleConfig.class));
}
@Override
public void delete(String id) {
throw new UnsupportedOperationException("Deleting scm repositories is prohibited!");
}
@Override
public Page getAllWithMatchAndSearchUrl(
int pageIndex,
int pageSize,
String sortingRsql,
String query,
String matchUrl,
String searchUrl) {
List> predicates = new ArrayList<>();
validateAndAddPredicate(predicates, matchUrl, RepositoryConfigurationPredicates::matchByScmUrl);
validateAndAddPredicate(predicates, searchUrl, RepositoryConfigurationPredicates::searchByScmUrl);
// transform list to array for 'predicates' varargs in 'queryForCollection' method
Predicate[] predicatesArray = new Predicate[predicates.size()];
predicatesArray = predicates.toArray(predicatesArray);
return queryForCollection(pageIndex, pageSize, sortingRsql, query, predicatesArray);
}
/**
* Creates predicate and add it to the list if the string is not null *and* not empty.
*
* @param list
* @param str
* @param item
* @param
* @throws InvalidEntityException when IllegalArgumentException is thrown when creating the predicate.
*/
private void validateAndAddPredicate(List> list, String str, Function> item) {
if (str != null && !str.isEmpty()) {
try {
list.add(item.apply(str));
} catch (IllegalArgumentException ex) {
throw new InvalidEntityException(ex.getMessage());
}
}
}
@Override
public RepositoryCreationResponse createSCMRepository(String scmUrl, Boolean preBuildSyncEnabled) {
RepositoryCreationResponse scmRepository = createSCMRepository(
scmUrl,
preBuildSyncEnabled,
SCM_REPOSITORY_CREATION,
Optional.empty());
// if it is an internal only repository, it should be created at this point and notification is sent now,
// otherwise the notification is sent on when the callback of cloning is processed
if (scmRepository.getTaskId() == null) {
notifySCMRepositoryCreated(
new RepositoryCreated(null, Integer.valueOf(scmRepository.getRepository().getId())));
}
return scmRepository;
}
private void notifySCMRepositoryCreated(RepositoryCreated event) {
final SCMRepository repository = getSpecific(Integer.toString(event.getRepositoryId()));
final String taskId = event.getTaskId() == null ? null : event.getTaskId().toString();
if (taskId != null) {
notifier.sendMessage(new SCMRepositoryCreationSuccess(repository, taskId));
}
}
/**
* Starts the task of creating SCMRepository.If the SCM URL is external, the task creates new internal repository
* and does initial synchronization.
*
* @param scmUrl The URL of the SCM repository.
* @param preBuildSyncEnabled If the SCM URL is external, this parameter specifies whether the external repository
* should be synchronized into the internal one before build.
* @param jobType Type of the job that requested the SCM repository creation (for notification purposes).
* @return id of the created
*/
@Override
public RepositoryCreationResponse createSCMRepository(
String scmUrl,
Boolean preBuildSyncEnabled,
JobNotificationType jobType,
Optional buildConfiguration) {
return createSCMRepository(scmUrl, null, preBuildSyncEnabled, jobType, buildConfiguration);
}
@Override
public RepositoryCreationResponse createSCMRepository(
String scmUrl,
String revision,
Boolean preBuildSyncEnabled,
JobNotificationType jobType,
Optional buildConfiguration) {
log.trace(
"Received request to start RC creation with url autodetect: " + scmUrl + " (sync enabled? "
+ preBuildSyncEnabled + ")");
if (StringUtils.isEmpty(scmUrl)) {
throw new InvalidEntityException("You must specify the SCM URL.");
}
String secondaryAuthority = config.getSecondaryInternalScmAuthority();
if (scmUrl.contains(config.getInternalScmAuthority())
|| (secondaryAuthority != null && scmUrl.contains(secondaryAuthority))) { // is internal repository
// validation phase
validateInternalRepository(scmUrl);
validateRepositoryWithInternalURLDoesNotExist(scmUrl, null);
SCMRepository scmRepository = createSCMRepositoryFromValues(null, scmUrl, false);
return new RepositoryCreationResponse(scmRepository);
} else {
// External repository needs to be cloned first. Starting the process ...
validateRepositoryWithExternalURLDoesNotExist(scmUrl, null);
boolean sync = preBuildSyncEnabled == null || preBuildSyncEnabled;
Long taskId = startRCreationTask(scmUrl, revision, sync, jobType, buildConfiguration);
return new RepositoryCreationResponse(taskId);
}
}
@Override
public void validateBeforeUpdating(Integer id, SCMRepository restEntity) {
super.validateBeforeUpdating(id, restEntity);
RepositoryConfiguration entityInDb = findInDB(id);
if (!entityInDb.getInternalUrl().equals(restEntity.getInternalUrl())) {
throw new InvalidEntityException("Updating internal URL is prohibited. SCMRepo: " + id);
}
if (restEntity.getExternalUrl() != null && !restEntity.getExternalUrl().equals(entityInDb.getExternalUrl())) {
validateRepositoryWithExternalURLDoesNotExist(restEntity.getExternalUrl(), id);
}
}
/**
* Crates and store SCMRepository to DB.
*/
private SCMRepository createSCMRepositoryFromValues(
String externalScmUrl,
String internalScmUrl,
boolean preBuildSyncEnabled) {
RepositoryConfiguration.Builder rcBuilder = RepositoryConfiguration.Builder.newBuilder()
.internalUrl(internalScmUrl)
.preBuildSyncEnabled(preBuildSyncEnabled);
if (externalScmUrl != null) {
rcBuilder.externalUrl(externalScmUrl);
}
RepositoryConfiguration entity = repository.save(rcBuilder.build());
log.info("Created SCM repository: {}.", entity.toString());
return mapper.toDTO(entity);
}
public void validateInternalRepository(String internalRepoUrl) throws InvalidEntityException {
String internalScmAuthority = config.getInternalScmAuthority();
String secondaryInternalScmAuthority = config.getSecondaryInternalScmAuthority();
if (!isInternalRepository(internalScmAuthority, secondaryInternalScmAuthority, internalRepoUrl)) {
log.info("Invalid internal repo url: " + internalRepoUrl);
throw new InvalidEntityException(
"Internal repository url has to start with: ://" + internalScmAuthority
+ " followed by a repository name or match the pattern: " + REPOSITORY_NAME_PATTERN);
} else if (usingGitlabButNotScpFormat(internalRepoUrl)) {
log.info("Invalid internal repo url: We only allow internal gitlab repo url in scp format");
throw new InvalidEntityException(
"Internal Gitlab repository url has to follow format: git@:.git");
} else if (internalRepoUrl.contains("/gerrit/")) {
log.info("Invalid internal repo url: " + internalRepoUrl);
throw new InvalidEntityException(
"Incorrect format of internal repository. Internal repository"
+ " url should not contain '/gerrit/' part of the url");
} else if (internalRepoUrl.split("://")[0].contains("http")) {
log.info("Invalid internal repo url: " + internalRepoUrl);
throw new InvalidEntityException(
"Incorrect url protocol. Http and https protocols are not supported for internal repository. Try using 'git+ssh'");
}
}
protected static Boolean isInternalRepository(
String internalScmAuthority,
String secondaryInternalScmAuthority,
String internalRepoUrl) {
if (StringUtils.isEmpty(internalRepoUrl) || internalScmAuthority == null) {
throw new IllegalArgumentException("InternalScmAuthority and internalRepoUrl parameters must be set.");
}
String internalRepoUrlNoProto;
if (internalRepoUrl.startsWith("git@")) {
internalRepoUrlNoProto = internalRepoUrl.substring(4);
} else {
internalRepoUrlNoProto = UrlUtils.stripProtocol(internalRepoUrl);
}
String internalRepoName;
internalRepoName = internalRepoUrlNoProto.replace(internalScmAuthority, "");
boolean isInternal = internalRepoUrlNoProto.startsWith(internalScmAuthority);
if (isInternal) {
internalRepoName = internalRepoUrlNoProto.replace(internalScmAuthority, "");
} else if (!StringUtils.isEmpty(secondaryInternalScmAuthority)) {
isInternal = internalRepoUrlNoProto.startsWith(secondaryInternalScmAuthority);
if (isInternal) {
internalRepoName = internalRepoUrlNoProto.replace(secondaryInternalScmAuthority, "");
}
}
return isInternal && REPOSITORY_NAME_PATTERN.matcher(internalRepoName).matches();
}
private void validateRepositoryWithInternalURLDoesNotExist(String internalUrl, Integer ignoreId)
throws ConflictedEntryException {
RepositoryConfiguration repositoryConfiguration = repositoryConfigurationRepository
.queryByInternalScm(internalUrl);
if (repositoryConfiguration != null && !repositoryConfiguration.getId().equals(ignoreId)) {
String message = "SCM Repository with internal URL '" + internalUrl + "'already exists (id: "
+ repositoryConfiguration.getId() + ")";
throw new ConflictedEntryException(
message,
RepositoryConfiguration.class,
repositoryConfiguration.getId().toString());
}
}
private void validateRepositoryWithExternalURLDoesNotExist(String externalUrl, Integer ignoreId)
throws ConflictedEntryException {
RepositoryConfiguration repositoryConfiguration = repositoryConfigurationRepository
.queryByExternalScm(externalUrl);
if (repositoryConfiguration != null && !repositoryConfiguration.getId().equals(ignoreId)) {
String message = "SCM Repository with external URL '" + externalUrl + "' already exists (id: "
+ repositoryConfiguration.getId() + ")";
throw new ConflictedEntryException(
message,
RepositoryConfiguration.class,
repositoryConfiguration.getId().toString());
}
String internalUrl = repour.translateExternalUrl(externalUrl);
validateRepositoryWithInternalURLDoesNotExist(internalUrl, ignoreId);
}
/**
*
* @param externalURL
* @param preBuildSyncEnabled
* @param jobType
* @param buildConfiguration required when repository is created as part of BC creation process
* @return
*/
private Long startRCreationTask(
String externalURL,
String revision,
boolean preBuildSyncEnabled,
JobNotificationType jobType,
Optional buildConfiguration) {
String authToken = keycloakServiceClient.getAuthToken();
org.jboss.pnc.bpm.model.RepositoryConfiguration repositoryConfiguration = org.jboss.pnc.bpm.model.RepositoryConfiguration
.builder()
.externalUrl(externalURL)
.preBuildSyncEnabled(preBuildSyncEnabled)
.build();
RepositoryCreationTask task;
RepositoryCreationProcess.RepositoryCreationProcessBuilder repositoryCreationProcess = RepositoryCreationProcess
.builder()
.repositoryConfiguration(repositoryConfiguration)
.revision(revision);
buildConfiguration.ifPresent(repositoryCreationProcess::buildConfiguration);
task = new RepositoryCreationTask(repositoryCreationProcess.build(), jobType, globalConfig);
long id = Sequence.nextId();
try {
Map parameters = new HashMap<>();
parameters.put("processParameters", task.getProcessParameters());
parameters.put("taskId", id);
connector.startProcess(bpmConfig.getNewBcCreationProcessId(), parameters, Objects.toString(id), authToken);
} catch (CoreException e) {
throw new RuntimeException("Could not get process parameters: " + task, e);
} catch (ProcessManagerException e) {
throw new RuntimeException("Could not start BPM task using REST connector: " + task, e);
}
return id;
}
@Override
public void repositoryCreationCompleted(RepositoryCreationResult result) {
if (result.getStatus().isSuccess()) {
RepositoryConfiguration existing = repository
.queryByPredicates(withExactInternalScmRepoUrl(result.getInternalScmUrl()));
if (existing != null) {
RepositoryCreationFailure error = new RepositoryCreationFailure(
result.getJobType(),
RC_REPO_CREATION_CONFLICT,
existing,
result.getTaskId().toString());
notifier.sendMessage(error);
} else {
SCMRepository scmRepo = createSCMRepositoryFromValues(
result.getExternalUrl(),
result.getInternalScmUrl(),
result.isPreBuildSyncEnabled());
RepositoryCreated notification = new RepositoryCreated(
result.getTaskId(),
Integer.parseInt(scmRepo.getId()));
log.debug("Repository created: {}", notification);
if (result.getJobType().equals(JobNotificationType.BUILD_CONFIG_CREATION)) {
buildConfigurationProvider.createBuildConfigurationWithRepository(
result.getTaskId().toString(),
notification.getRepositoryId(),
result.getBuildConfiguration());
}
notifySCMRepositoryCreated(notification);
}
} else {
String eventType;
if (!result.isRepoCreatedSuccessfully()) {
eventType = BpmEventType.RC_REPO_CREATION_ERROR.toString();
} else {
eventType = BpmEventType.RC_REPO_CLONE_ERROR.toString();
}
org.jboss.pnc.bpm.model.RepositoryConfiguration repositoryConfiguration = org.jboss.pnc.bpm.model.RepositoryConfiguration
.builder()
.externalUrl(result.getExternalUrl())
.preBuildSyncEnabled(result.isPreBuildSyncEnabled())
.build();
RepositoryCreationProcess repositoryCreationProcess = RepositoryCreationProcess.builder()
.repositoryConfiguration(repositoryConfiguration)
.build();
notifier.sendMessage(
new RepositoryCreationFailure(
result.getJobType(),
eventType,
repositoryCreationProcess,
result.getTaskId().toString()));
}
}
/**
* [NCL-8685] Gitlab internal urls should *only* use the scp format: git@{server}:{path to git repo}.git
*
* @param internalRepoUrl internal url to validate
* @return true if using gitlab internal url *and* the internal url is not in scp format
*/
static boolean usingGitlabButNotScpFormat(String internalRepoUrl) {
try {
ScmUrlGeneratorProvider.SCMProvider provider = ScmUrlGeneratorProvider
.determineInternalScmProvider(internalRepoUrl);
if (ScmUrlGeneratorProvider.SCMProvider.GITLAB != provider) {
// not using gitlab
return false;
}
} catch (ScmException e) {
// we couldn't determine the provider. Just return false
return false;
}
try {
GitSCPUrl.parse(internalRepoUrl);
// parsing done without MalformedUrlException,
return false;
} catch (MalformedURLException e) {
// we are using gitlab but the internal repo is not in scp format
return true;
}
}
}