Skip to content

Commit

Permalink
Add an ability to use a custom registry for WS.NEXT plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
mshaposhnik committed Oct 12, 2018
1 parent 518c5f2 commit 941b8e8
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 28 deletions.
Expand Up @@ -25,21 +25,23 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriBuilderException;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException;
import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN;
import org.eclipse.che.api.workspace.server.wsplugins.model.PluginMeta;
import org.eclipse.che.api.workspace.shared.Constants;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.commons.lang.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -115,67 +117,89 @@ public Collection<PluginMeta> get(Map<String, String> attributes) throws Infrast
throw new InfrastructureException(CHE_REGISTRY_MISSING_ERROR);
}

ArrayList<Pair<String, String>> metasIdsVersions = new ArrayList<>();
List<PluginFQN> metaFQNs = new ArrayList<>();
if (!isNullOrEmpty(pluginsAttribute)) {
String[] plugins = pluginsAttribute.split(" *, *");
if (plugins.length != 0) {
Collection<Pair<String, String>> pluginsIdsVersions = parseIdsVersions(plugins);
metasIdsVersions.addAll(pluginsIdsVersions);
Collection<PluginFQN> pluginsFQNs = parsePluginFQNs(plugins);
metaFQNs.addAll(pluginsFQNs);
}
}
if (!isNullOrEmpty(editorAttribute)) {
Collection<Pair<String, String>> editorIdVersionCollection =
parseIdsVersions(editorAttribute);
Collection<PluginFQN> editorIdVersionCollection =
parsePluginFQNs(editorAttribute.split(" *, *"));
if (editorIdVersionCollection.size() > 1) {
throw new InfrastructureException(
"Multiple editors found in workspace config attributes. "
+ "It is not supported. Please, use one editor only.");
}
metasIdsVersions.addAll(editorIdVersionCollection);
metaFQNs.addAll(editorIdVersionCollection);
}

return getMetas(metasIdsVersions);
return getMetas(metaFQNs);
}

private Collection<Pair<String, String>> parseIdsVersions(String... idsVersions)
throws InfrastructureException {
Map<String, Pair<String, String>> collectedIdVersion = new HashMap<>();
for (String plugin : idsVersions) {
String[] idVersion = plugin.split(":");
private Collection<PluginFQN> parsePluginFQNs(String... plugins) throws InfrastructureException {
List<PluginFQN> collectedFQNs = new ArrayList<>();
for (String plugin : plugins) {
URI repo = null;
String idVersionString;
final int idVersionTagDelimiter = plugin.lastIndexOf("/");
idVersionString = plugin.substring(idVersionTagDelimiter + 1);
if (idVersionTagDelimiter > -1) {
try {
repo = new URI(plugin.substring(0, idVersionTagDelimiter));
} catch (URISyntaxException e) {
throw new InfrastructureException(
"Plugin registry URL is incorrect. Problematic plugin entry:" + plugin);
}
}
String[] idVersion = idVersionString.split(":");
if (idVersion.length != 2 || idVersion[0].isEmpty() || idVersion[1].isEmpty()) {
throw new InfrastructureException(
"Plugin format is illegal. Problematic plugin entry:" + plugin);
}
String key = idVersion[0] + ':' + idVersion[1];
if (collectedIdVersion.containsKey(key)) {
if (collectedFQNs
.stream()
.anyMatch(p -> p.getId().equals(idVersion[0]) && p.getVersion().equals(idVersion[1]))) {
throw new InfrastructureException(
format("Invalid Che tooling plugins configuration: plugin %s is duplicated", key));
format(
"Invalid Che tooling plugins configuration: plugin %s is duplicated",
idVersion[0] + ":" + idVersion[1])); // even if different repos
}
collectedIdVersion.put(key, Pair.of(idVersion[0], idVersion[1]));
collectedFQNs.add(new PluginFQN(repo, idVersion[0], idVersion[1]));
}
return collectedIdVersion.values();
return collectedFQNs;
}

private Collection<PluginMeta> getMetas(ArrayList<Pair<String, String>> metasIdsVersions)
private Collection<PluginMeta> getMetas(List<PluginFQN> pluginFQNs)
throws InfrastructureException {
ArrayList<PluginMeta> metas = new ArrayList<>();
for (Pair<String, String> metaIdVersion : metasIdsVersions) {
metas.add(getMeta(metaIdVersion.first, metaIdVersion.second));
for (PluginFQN pluginFqn : pluginFQNs) {
metas.add(getMeta(pluginFqn));
}

return metas;
}

private PluginMeta getMeta(String id, String version) throws InfrastructureException {
private PluginMeta getMeta(PluginFQN pluginFQN) throws InfrastructureException {
final String id = pluginFQN.getId();
final String version = pluginFQN.getVersion();
try {
URI metaURI = pluginRegistry.clone().path(id).path(version).path("meta.yaml").build();
UriBuilder metaURIBuilder =
pluginFQN.getRegistry() == null
? pluginRegistry.clone()
: UriBuilder.fromUri(pluginFQN.getRegistry());

URI metaURI = metaURIBuilder.path(id).path(version).path("meta.yaml").build();
PluginMeta meta = getBody(metaURI, PluginMeta.class);
validateMeta(meta, id, version);
return meta;
} catch (IllegalArgumentException | UriBuilderException e) {
} catch (IllegalArgumentException | UriBuilderException | MalformedURLException e) {
throw new InternalInfrastructureException(
format("Metadata of plugin %s:%s retrieval failed", id, version));
format(
"Metadata of plugin %s:%s retrieval failed. Error is %s",
id, version, e.getMessage()));
} catch (IOException e) {
throw new InfrastructureException(
format(
Expand All @@ -184,8 +208,8 @@ private PluginMeta getMeta(String id, String version) throws InfrastructureExcep
}
}

private void validateMeta(PluginMeta meta, String id, String version)
throws InfrastructureException {
@VisibleForTesting
void validateMeta(PluginMeta meta, String id, String version) throws InfrastructureException {
requireNotNullNorEmpty(meta.getId(), CHE_PLUGIN_OBJECT_ERROR, id, version, "ID is missing.");
requireEqual(
id,
Expand Down
@@ -0,0 +1,56 @@
/*
* 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.workspace.server.wsplugins.model;

import java.net.URI;

/**
* Represents full information about plugin, including registry address, id and version.
*
* @author Max Shaposhnyk
*/
public class PluginFQN {

private URI registry;
private String id;
private String version;

public PluginFQN(URI registry, String id, String version) {
this.registry = registry;
this.id = id;
this.version = version;
}

public URI getRegistry() {
return registry;
}

public void setRegistry(URI registry) {
this.registry = registry;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getVersion() {
return version;
}

public void setVersion(String version) {
this.version = version;
}
}
@@ -0,0 +1,105 @@
/*
* 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.workspace.server.wsplugins;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

import com.google.common.collect.ImmutableMap;
import java.net.URI;
import java.util.Map;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.wsplugins.model.PluginMeta;
import org.eclipse.che.api.workspace.shared.Constants;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class ChePluginMetaRetrieverTest {

private static final String BASE_REGISTRY = "https://che-plugin-registry.openshift.io";
private PluginMetaRetriever metaRetriever;

@BeforeClass
public void setUp() throws Exception {
metaRetriever = spy(new PluginMetaRetriever(BASE_REGISTRY));
doReturn(null).when(metaRetriever).getBody(any(URI.class), any());
doNothing().when(metaRetriever).validateMeta(any(), anyString(), anyString());
}

@Test(dataProvider = "pluginProvider")
public void shouldGetMetaByCorrectURLUsingBaseRegistry(
Map<String, String> attributes, String expectedUri) throws Exception {
metaRetriever.get(attributes);

verify(metaRetriever).getBody(eq(new URI(expectedUri)), eq(PluginMeta.class));
}

@Test(
expectedExceptions = InfrastructureException.class,
expectedExceptionsMessageRegExp =
"Multiple editors found in workspace config attributes."
+ " It is not supported. Please, use one editor only.")
public void shouldThrowExceptionWhenMultipleEditorsSpecified() throws Exception {

metaRetriever.get(createAttributes("", "theia:1.0, idea:2.0"));
}

@Test(
expectedExceptions = InfrastructureException.class,
expectedExceptionsMessageRegExp = "Plugin format is illegal. Problematic plugin entry:.*")
public void shouldThrowExceptionWhenPluginFormatBad() throws Exception {

metaRetriever.get(createAttributes("my-plugin:4.0, my_new_plugin:part:1.0", ""));
}

@Test(
expectedExceptions = InfrastructureException.class,
expectedExceptionsMessageRegExp =
"Invalid Che tooling plugins configuration: plugin .* is duplicated")
public void shouldThrowExceptionWhenPluginIsDuplicated() throws Exception {

metaRetriever.get(
createAttributes(
"http://registry.myregistry1.com:8080/my-plugin:4.0, "
+ "http://registry2.myregistry2.com:8080/my-plugin:4.0",
""));
}

@DataProvider(name = "pluginProvider")
public static Object[][] pluginProvider() {
return new Object[][] {
{createAttributes("my-plugin:4.0", ""), BASE_REGISTRY + "/plugins/my-plugin/4.0/meta.yaml"},
{
createAttributes("http://registry.myregistry.com:8080/my-plugin:4.0", ""),
"http://registry.myregistry.com:8080/my-plugin/4.0/meta.yaml"
},
{
createAttributes("https://myregistry.com/registry/my.plugin:4.0", ""),
"https://myregistry.com/registry/my.plugin/4.0/meta.yaml"
}
};
}

private static Map<String, String> createAttributes(String plugins, String editor) {
return ImmutableMap.of(
Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE,
plugins,
Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE,
editor);
}
}

0 comments on commit 941b8e8

Please sign in to comment.