Skip to content
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

Bitbucket public repo factories support #18423

Merged
merged 16 commits into from
Dec 7, 2020
Merged
4 changes: 4 additions & 0 deletions assembly/assembly-wsmaster-war/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory-bitbucket-server</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory-github</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.eclipse.che.api.factory.server.FactoryCreateValidator;
import org.eclipse.che.api.factory.server.FactoryEditValidator;
import org.eclipse.che.api.factory.server.FactoryParametersResolver;
import org.eclipse.che.api.factory.server.bitbucket.BitbucketServerFactoryParametersResolver;
import org.eclipse.che.api.factory.server.github.GithubFactoryParametersResolver;
import org.eclipse.che.api.metrics.WsMasterMetricsModule;
import org.eclipse.che.api.system.server.ServiceTermination;
Expand Down Expand Up @@ -150,6 +151,7 @@ protected void configure() {
Multibinder<FactoryParametersResolver> factoryParametersResolverMultibinder =
Multibinder.newSetBinder(binder(), FactoryParametersResolver.class);
factoryParametersResolverMultibinder.addBinding().to(GithubFactoryParametersResolver.class);
factoryParametersResolverMultibinder.addBinding().to(BitbucketServerFactoryParametersResolver.class);
mshaposhnik marked this conversation as resolved.
Show resolved Hide resolved

bind(org.eclipse.che.api.core.rest.ApiInfoService.class);
bind(org.eclipse.che.api.ssh.server.SshService.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -672,3 +672,7 @@ che.infra.kubernetes.async.storage.shutdown_timeout_min=120

# Defines the period with which the Asynchronous Storage Pod stopping ability will be performed (once in 30 minutes by default)
che.infra.kubernetes.async.storage.shutdown_check_period_min=30

# Bitbucket endpoint used for both factory and OAuth integrations. Should contain bitbucket server
# URL or NULL if no integration expected.
bitbucket.server.endpoint=NULL
mshaposhnik marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,11 @@
<version>${che.version}</version>
<classifier>tests</classifier>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory-bitbucket-server</artifactId>
<version>${che.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory-github</artifactId>
Expand Down
100 changes: 100 additions & 0 deletions wsmaster/che-core-api-factory-bitbucket-server/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

Copyright (c) 2012-2018 Red Hat, Inc.
This program and the accompanying materials are made
available under the terms of the Eclipse Public License 2.0
which is available at https://www.eclipse.org/legal/epl-2.0/

SPDX-License-Identifier: EPL-2.0

Contributors:
Red Hat, Inc. - initial API and implementation

-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-master-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.22.0-SNAPSHOT</version>
</parent>
<artifactId>che-core-api-factory-bitbucket-server</artifactId>
<packaging>jar</packaging>
<name>Che Core :: API :: Factory Resolver Bitbucket Server</name>
<properties>
<findbugs.failonerror>true</findbugs.failonerror>
mshaposhnik marked this conversation as resolved.
Show resolved Hide resolved
</properties>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-dto</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-annotations</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-json</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-testng</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.api.factory.server.bitbucket;

import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION;
import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME;
import static org.eclipse.che.dto.server.DtoFactory.newDto;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.constraints.NotNull;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.factory.server.DefaultFactoryParameterResolver;
import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto;
import org.eclipse.che.api.workspace.shared.dto.devfile.SourceDto;

/**
* Provides Factory Parameters resolver for bitbucket repositories.
*
* @author Max Shaposhnyk
*/
@Singleton
public class BitbucketServerFactoryParametersResolver extends DefaultFactoryParameterResolver {

/** Parser which will allow to check validity of URLs and create objects. */
private BitbucketURLParser bitbucketURLParser;
mshaposhnik marked this conversation as resolved.
Show resolved Hide resolved

@Inject
public BitbucketServerFactoryParametersResolver(
URLFactoryBuilder urlFactoryBuilder,
URLFetcher urlFetcher,
BitbucketURLParser bitbucketURLParser) {
super(urlFactoryBuilder, urlFetcher);
this.bitbucketURLParser = bitbucketURLParser;
}

/**
* Check if this resolver can be used with the given parameters.
*
* @param factoryParameters map of parameters dedicated to factories
* @return true if it will be accepted by the resolver implementation or false if it is not
* accepted
*/
@Override
public boolean accept(@NotNull final Map<String, String> factoryParameters) {
return factoryParameters.containsKey(URL_PARAMETER_NAME)
&& bitbucketURLParser.isValid(factoryParameters.get(URL_PARAMETER_NAME));
}

/**
* Create factory object based on provided parameters
*
* @param factoryParameters map containing factory data parameters provided through URL
* @throws BadRequestException when data are invalid
*/
@Override
public FactoryDto createFactory(@NotNull final Map<String, String> factoryParameters)
throws BadRequestException {

// no need to check null value of url parameter as accept() method has performed the check
final BitbucketUrl bitbucketUrl =
bitbucketURLParser.parse(factoryParameters.get(URL_PARAMETER_NAME));

// create factory from the following location if location exists, else create default factory
FactoryDto factory =
urlFactoryBuilder
.createFactoryFromDevfile(
bitbucketUrl,
fileName -> urlFetcher.fetch(bitbucketUrl.rawFileLocation(fileName)),
extractOverrideParams(factoryParameters))
.orElseGet(
() ->
urlFactoryBuilder
.createFactoryFromJson(bitbucketUrl)
.orElseGet(
() ->
newDto(FactoryDto.class)
.withV(CURRENT_VERSION)
.withSource("repo")));

if (factory.getDevfile() == null) {
// initialize default devfile
factory.setDevfile(urlFactoryBuilder.buildDefaultDevfile(bitbucketUrl.getRepository()));
}

List<ProjectDto> projects = factory.getDevfile().getProjects();
// if no projects set, set the default one from Bitbucket url
if (projects.isEmpty()) {
factory
.getDevfile()
.setProjects(
Collections.singletonList(
newDto(ProjectDto.class)
.withSource(
newDto(SourceDto.class)
.withLocation(bitbucketUrl.repositoryLocation())
.withType("git")
.withBranch(bitbucketUrl.getBranch()))
.withName(bitbucketUrl.getRepository())));
} else {
// update existing project with same repository, set current branch if needed
projects.forEach(
project -> {
final String location = project.getSource().getLocation();
if (location.equals(bitbucketUrl.repositoryLocation())) {
project.getSource().setBranch(bitbucketUrl.getBranch());
}
});
}
return factory;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.api.factory.server.bitbucket;

import static java.lang.String.format;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.validation.constraints.NotNull;
import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
import org.eclipse.che.commons.annotation.Nullable;

/**
* Parser of String Bitbucket URLs and provide {@link BitbucketUrl} objects.
*
* @author Max Shaposhnyk
*/
@Singleton
public class BitbucketURLParser {

private final URLFetcher urlFetcher;
private final DevfileFilenamesProvider devfileFilenamesProvider;
private static final String bitbucketUrlPatternTemplate =
"^(?<host>%s)/scm/(?<project>[^/]++)/(?<repo>[^.]++).git(\\?at=)?(?<branch>[\\w\\d-_]*)";
private Pattern bitbucketUrlPattern;
mshaposhnik marked this conversation as resolved.
Show resolved Hide resolved

@Inject
public BitbucketURLParser(
@Nullable @Named("bitbucket.server.endpoint") String bitbucketEndpoint,
URLFetcher urlFetcher,
DevfileFilenamesProvider devfileFilenamesProvider) {
if (bitbucketEndpoint != null) {
String trimmedEndpoint =
bitbucketEndpoint.endsWith("/")
? bitbucketEndpoint.substring(0, bitbucketEndpoint.length() - 1)
: bitbucketEndpoint;
this.bitbucketUrlPattern =
mshaposhnik marked this conversation as resolved.
Show resolved Hide resolved
Pattern.compile(format(bitbucketUrlPatternTemplate, trimmedEndpoint));
}
this.urlFetcher = urlFetcher;
this.devfileFilenamesProvider = devfileFilenamesProvider;
}

public boolean isValid(@NotNull String url) {
return bitbucketUrlPattern != null && bitbucketUrlPattern.matcher(url).matches();
}

/**
* Parses url-s like
* https://bitbucket.apps.cluster-cb82.cb82.example.opentlc.com/scm/test/test1.git into
* BitbucketUrl objects.
*/
public BitbucketUrl parse(String url) {

Matcher matcher = bitbucketUrlPattern.matcher(url);
if (!matcher.matches()) {
throw new IllegalArgumentException(
String.format(
"The given url %s is not a valid Bitbucket server URL. Check either URL or server configuration.",
url));
}

String host = matcher.group("host");
String project = matcher.group("project");
String repoName = matcher.group("repo");
String branch = matcher.group("branch");

return new BitbucketUrl()
.withHostName(host)
.withProject(project)
.withRepository(repoName)
.withBranch(branch)
.withFactoryFilename(".factory.json")
skabashnyuk marked this conversation as resolved.
Show resolved Hide resolved
.withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames());
}
}
Loading