diff --git a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties index 89a5c312863..56203c78c6c 100644 --- a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties +++ b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties @@ -546,8 +546,8 @@ che.singleport.wildcard_domain.ipless=false # Docker image of Che plugin broker app that resolves workspace tooling configuration and copies # plugins dependencies to a workspace -che.workspace.plugin_broker.init.image=eclipse/che-init-plugin-broker:v0.14.0 -che.workspace.plugin_broker.unified.image=eclipse/che-unified-plugin-broker:v0.15.1 +che.workspace.plugin_broker.init.image=eclipse/che-init-plugin-broker:v0.15.3 +che.workspace.plugin_broker.unified.image=eclipse/che-unified-plugin-broker:v0.15.3 # Docker image of Che plugin broker app that resolves workspace tooling configuration and copies # plugins dependencies to a workspace diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/KubernetesBrokerInitContainerApplier.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/KubernetesBrokerInitContainerApplier.java index f10c704c75e..ce68f821e94 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/KubernetesBrokerInitContainerApplier.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/KubernetesBrokerInitContainerApplier.java @@ -21,7 +21,7 @@ import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; -import org.eclipse.che.api.workspace.server.wsplugins.model.PluginMeta; +import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; @@ -52,11 +52,10 @@ public KubernetesBrokerInitContainerApplier( * broker's configmap, machines, and volumes added in addition to the init container */ public void apply( - E workspaceEnvironment, RuntimeIdentity runtimeID, Collection pluginsMeta) + E workspaceEnvironment, RuntimeIdentity runtimeID, Collection pluginFQNs) throws InfrastructureException { - E brokerEnvironment = - brokerEnvironmentFactory.create(pluginsMeta, runtimeID, new BrokersResult()); + E brokerEnvironment = brokerEnvironmentFactory.create(pluginFQNs, runtimeID); Map workspacePods = workspaceEnvironment.getPodsData(); if (workspacePods.size() != 1) { diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/PluginBrokerManager.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/PluginBrokerManager.java index 8616d20cf07..7d87cd974a8 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/PluginBrokerManager.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/PluginBrokerManager.java @@ -21,7 +21,7 @@ import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; -import org.eclipse.che.api.workspace.server.wsplugins.model.PluginMeta; +import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesEnvironmentProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.StartSynchronizer; @@ -95,7 +95,7 @@ public PluginBrokerManager( public List getTooling( RuntimeIdentity runtimeID, StartSynchronizer startSynchronizer, - Collection pluginsMeta, + Collection pluginFQNs, boolean isEphemeral) throws InfrastructureException { @@ -103,7 +103,7 @@ public List getTooling( KubernetesNamespace kubernetesNamespace = factory.create(workspaceId); BrokersResult brokersResult = new BrokersResult(); - E brokerEnvironment = brokerEnvironmentFactory.create(pluginsMeta, runtimeID, brokersResult); + E brokerEnvironment = brokerEnvironmentFactory.create(pluginFQNs, runtimeID); if (isEphemeral) { EphemeralWorkspaceUtility.makeEphemeral(brokerEnvironment.getAttributes()); } diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/SidecarToolingProvisioner.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/SidecarToolingProvisioner.java index 8d6c945bcd5..a5e172a6a43 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/SidecarToolingProvisioner.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/SidecarToolingProvisioner.java @@ -20,9 +20,9 @@ import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.wsplugins.ChePluginsApplier; -import org.eclipse.che.api.workspace.server.wsplugins.PluginMetaRetriever; +import org.eclipse.che.api.workspace.server.wsplugins.PluginFQNParser; import org.eclipse.che.api.workspace.server.wsplugins.model.ChePlugin; -import org.eclipse.che.api.workspace.server.wsplugins.model.PluginMeta; +import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; import org.eclipse.che.commons.annotation.Traced; import org.eclipse.che.workspace.infrastructure.kubernetes.StartSynchronizer; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; @@ -42,18 +42,18 @@ public class SidecarToolingProvisioner { private final Map workspaceNextAppliers; private final KubernetesBrokerInitContainerApplier brokerApplier; - private final PluginMetaRetriever pluginMetaRetriever; + private final PluginFQNParser pluginFQNParser; private final PluginBrokerManager pluginBrokerManager; @Inject public SidecarToolingProvisioner( Map workspaceNextAppliers, KubernetesBrokerInitContainerApplier brokerApplier, - PluginMetaRetriever pluginMetaRetriever, + PluginFQNParser pluginFQNParser, PluginBrokerManager pluginBrokerManager) { this.workspaceNextAppliers = ImmutableMap.copyOf(workspaceNextAppliers); this.brokerApplier = brokerApplier; - this.pluginMetaRetriever = pluginMetaRetriever; + this.pluginFQNParser = pluginFQNParser; this.pluginBrokerManager = pluginBrokerManager; } @@ -62,8 +62,8 @@ public SidecarToolingProvisioner( public void provision(RuntimeIdentity id, StartSynchronizer startSynchronizer, E environment) throws InfrastructureException { - Collection pluginsMeta = pluginMetaRetriever.get(environment.getAttributes()); - if (pluginsMeta.isEmpty()) { + Collection pluginFQNs = pluginFQNParser.parsePlugins(environment.getAttributes()); + if (pluginFQNs.isEmpty()) { return; } LOG.debug("Started sidecar tooling provisioning workspace '{}'", id.getWorkspaceId()); @@ -76,11 +76,11 @@ public void provision(RuntimeIdentity id, StartSynchronizer startSynchronizer, E boolean isEphemeral = EphemeralWorkspaceUtility.isEphemeral(environment.getAttributes()); List chePlugins = - pluginBrokerManager.getTooling(id, startSynchronizer, pluginsMeta, isEphemeral); + pluginBrokerManager.getTooling(id, startSynchronizer, pluginFQNs, isEphemeral); pluginsApplier.apply(id, environment, chePlugins); if (isEphemeral) { - brokerApplier.apply(environment, id, pluginsMeta); + brokerApplier.apply(environment, id, pluginFQNs); } LOG.debug("Finished sidecar tooling provisioning workspace '{}'", id.getWorkspaceId()); } diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/BrokerEnvironmentFactory.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/BrokerEnvironmentFactory.java index 8ef882d65fb..d0787eef898 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/BrokerEnvironmentFactory.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/BrokerEnvironmentFactory.java @@ -16,7 +16,9 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; +import com.google.common.base.Strings; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.Container; @@ -34,8 +36,6 @@ import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.inject.Inject; -import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.model.impl.VolumeImpl; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; @@ -43,7 +43,7 @@ import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; import org.eclipse.che.api.workspace.server.spi.provision.env.AgentAuthEnableEnvVarProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.MachineTokenEnvVarProvider; -import org.eclipse.che.api.workspace.server.wsplugins.model.PluginMeta; +import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.lang.Pair; @@ -65,10 +65,10 @@ @Beta public abstract class BrokerEnvironmentFactory { - private static final String CONFIG_MAP_NAME_SUFFIX = "broker-config-map"; + @VisibleForTesting static final String CONFIG_MAP_NAME_SUFFIX = "broker-config-map"; + @VisibleForTesting static final String CONFIG_FILE = "config.json"; private static final String BROKER_VOLUME = "broker-config-volume"; private static final String CONF_FOLDER = "/broker-config"; - private static final String CONFIG_FILE = "config.json"; private static final String PLUGINS_VOLUME_NAME = "plugins"; private static final String BROKERS_POD_NAME = "che-plugin-broker"; @@ -79,34 +79,35 @@ public abstract class BrokerEnvironmentFactory private final MachineTokenEnvVarProvider machineTokenEnvVarProvider; private final String unifiedBrokerImage; private final String initBrokerImage; + private final String pluginRegistryUrl; - @Inject public BrokerEnvironmentFactory( - @Named("che.websocket.endpoint") String cheWebsocketEndpoint, - @Named("che.workspace.plugin_broker.pull_policy") String brokerPullPolicy, + String cheWebsocketEndpoint, + String brokerPullPolicy, AgentAuthEnableEnvVarProvider authEnableEnvVarProvider, MachineTokenEnvVarProvider machineTokenEnvVarProvider, - @Named("che.workspace.plugin_broker.unified.image") String unifiedBrokerImage, - @Named("che.workspace.plugin_broker.init.image") String initBrokerImage) { + String unifiedBrokerImage, + String initBrokerImage, + String pluginRegistryUrl) { this.cheWebsocketEndpoint = cheWebsocketEndpoint; this.brokerPullPolicy = brokerPullPolicy; this.authEnableEnvVarProvider = authEnableEnvVarProvider; this.machineTokenEnvVarProvider = machineTokenEnvVarProvider; this.unifiedBrokerImage = unifiedBrokerImage; this.initBrokerImage = initBrokerImage; + this.pluginRegistryUrl = pluginRegistryUrl; } /** * Creates {@link KubernetesEnvironment} with everything needed to deploy Plugin broker. * - * @param pluginsMeta meta info of plugins that needs to be resolved by the broker + * @param pluginFQN fully qualified names of plugins that needs to be resolved by the broker * @param runtimeID ID of the runtime the broker would be started * @param brokersResult needs to be called with {@link BrokersResult#oneMoreBroker()} for each * broker to allow proper waiting of execution of all the brokers * @return kubernetes environment (or its extension) with the Plugin broker objects */ - public E create( - Collection pluginsMeta, RuntimeIdentity runtimeID, BrokersResult brokersResult) + public E create(Collection pluginFQNs, RuntimeIdentity runtimeID) throws InfrastructureException { BrokersConfigs brokersConfigs = new BrokersConfigs(); @@ -122,7 +123,7 @@ public E create( .collect(Collectors.toList()); BrokerConfig brokerConfig = - createBrokerConfig(runtimeID, pluginsMeta, envVars, unifiedBrokerImage, pod); + createBrokerConfig(runtimeID, pluginFQNs, envVars, unifiedBrokerImage, pod); brokersConfigs.machines.put(brokerConfig.machineName, brokerConfig.machineConfig); brokersConfigs.configMaps.put(brokerConfig.configMapName, brokerConfig.configMap); spec.getContainers().add(brokerConfig.container); @@ -168,7 +169,10 @@ private Container newContainer( "%s:%s:%s", runtimeId.getWorkspaceId(), MoreObjects.firstNonNull(runtimeId.getEnvName(), ""), - runtimeId.getOwnerId())) + runtimeId.getOwnerId()), + "--download-metas", + "--registry-address", + Strings.nullToEmpty(pluginRegistryUrl)) .withImagePullPolicy(brokerPullPolicy) .withEnv(envVars); if (brokerVolumeName != null) { @@ -192,14 +196,14 @@ private Pod newPod() { .build(); } - private ConfigMap newConfigMap(String configMapName, Collection pluginsMetas) + private ConfigMap newConfigMap(String configMapName, Collection pluginFQNs) throws InternalInfrastructureException { try { return new ConfigMapBuilder() .withNewMetadata() .withName(configMapName) .endMetadata() - .withData(singletonMap(CONFIG_FILE, objectMapper.writeValueAsString(pluginsMetas))) + .withData(singletonMap(CONFIG_FILE, objectMapper.writeValueAsString(pluginFQNs))) .build(); } catch (JsonProcessingException e) { throw new InternalInfrastructureException(e.getMessage(), e); @@ -212,7 +216,7 @@ private EnvVar asEnvVar(Pair envVar) { private BrokerConfig createBrokerConfig( RuntimeIdentity runtimeId, - @Nullable Collection pluginsMeta, + @Nullable Collection pluginFQNs, List envVars, String image, Pod pod) @@ -220,10 +224,10 @@ private BrokerConfig createBrokerConfig( BrokerConfig brokerConfig = new BrokerConfig(); String configMapVolume = null; - if (pluginsMeta != null) { + if (pluginFQNs != null) { brokerConfig.configMapName = generateUniqueName(CONFIG_MAP_NAME_SUFFIX); brokerConfig.configMapVolume = generateUniqueName(BROKER_VOLUME); - brokerConfig.configMap = newConfigMap(brokerConfig.configMapName, pluginsMeta); + brokerConfig.configMap = newConfigMap(brokerConfig.configMapName, pluginFQNs); configMapVolume = brokerConfig.configMapVolume; } brokerConfig.container = newContainer(runtimeId, envVars, image, configMapVolume); diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/KubernetesBrokerEnvironmentFactory.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/KubernetesBrokerEnvironmentFactory.java index 846d3221167..8d60b4aca30 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/KubernetesBrokerEnvironmentFactory.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/KubernetesBrokerEnvironmentFactory.java @@ -18,6 +18,7 @@ import javax.inject.Named; import org.eclipse.che.api.workspace.server.spi.provision.env.AgentAuthEnableEnvVarProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.MachineTokenEnvVarProvider; +import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; /** @@ -38,14 +39,16 @@ public KubernetesBrokerEnvironmentFactory( AgentAuthEnableEnvVarProvider authEnableEnvVarProvider, MachineTokenEnvVarProvider machineTokenEnvVarProvider, @Named("che.workspace.plugin_broker.init.image") String initBrokerImage, - @Named("che.workspace.plugin_broker.unified.image") String unifiedBrokerImage) { + @Named("che.workspace.plugin_broker.unified.image") String unifiedBrokerImage, + @Nullable @Named("che.workspace.plugin_registry_url") String pluginRegistryUrl) { super( cheWebsocketEndpoint, brokerPullPolicy, authEnableEnvVarProvider, machineTokenEnvVarProvider, unifiedBrokerImage, - initBrokerImage); + initBrokerImage, + pluginRegistryUrl); } @Override diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesBrokerInitContainerApplierTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesBrokerInitContainerApplierTest.java index d7c35ca7e27..79451082fa3 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesBrokerInitContainerApplierTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesBrokerInitContainerApplierTest.java @@ -37,7 +37,7 @@ import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; -import org.eclipse.che.api.workspace.server.wsplugins.model.PluginMeta; +import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.KubernetesBrokerInitContainerApplier; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.BrokerEnvironmentFactory; @@ -75,7 +75,7 @@ public class KubernetesBrokerInitContainerApplierTest { @Mock private BrokerEnvironmentFactory brokerEnvironmentFactory; @Mock private RuntimeIdentity runtimeID; - @Mock private Collection pluginsMeta; + @Mock private Collection pluginFQNs; // Broker Environment mocks @Mock private InternalMachineConfig brokerMachine; @@ -123,14 +123,14 @@ public void setUp() throws Exception { .setConfigMaps(ImmutableMap.of(BROKER_CONFIGMAP_NAME, brokerConfigMap)) .setMachines(ImmutableMap.of(BROKER_MACHINE_NAME, brokerMachine)) .build(); - doReturn(brokerEnvironment).when(brokerEnvironmentFactory).create(any(), any(), any()); + doReturn(brokerEnvironment).when(brokerEnvironmentFactory).create(any(), any()); applier = new KubernetesBrokerInitContainerApplier<>(brokerEnvironmentFactory); } @Test public void shouldAddBrokerMachineToWorkspaceEnvironment() throws Exception { - applier.apply(workspaceEnvironment, runtimeID, pluginsMeta); + applier.apply(workspaceEnvironment, runtimeID, pluginFQNs); assertNotNull(workspaceEnvironment.getMachines()); assertTrue(workspaceEnvironment.getMachines().values().contains(brokerMachine)); @@ -138,7 +138,7 @@ public void shouldAddBrokerMachineToWorkspaceEnvironment() throws Exception { @Test public void shouldAddBrokerConfigMapsToWorkspaceEnvironment() throws Exception { - applier.apply(workspaceEnvironment, runtimeID, pluginsMeta); + applier.apply(workspaceEnvironment, runtimeID, pluginFQNs); ConfigMap workspaceConfigMap = workspaceEnvironment.getConfigMaps().get(BROKER_CONFIGMAP_NAME); assertNotNull(workspaceConfigMap); @@ -153,7 +153,7 @@ public void shouldAddBrokerConfigMapsToWorkspaceEnvironment() throws Exception { @Test public void shouldAddBrokerAsInitContainerOnWorkspacePod() throws Exception { - applier.apply(workspaceEnvironment, runtimeID, pluginsMeta); + applier.apply(workspaceEnvironment, runtimeID, pluginFQNs); List initContainers = workspacePod.getSpec().getInitContainers(); assertEquals(initContainers.size(), 1); @@ -162,7 +162,7 @@ public void shouldAddBrokerAsInitContainerOnWorkspacePod() throws Exception { @Test public void shouldAddBrokerVolumesToWorkspacePod() throws Exception { - applier.apply(workspaceEnvironment, runtimeID, pluginsMeta); + applier.apply(workspaceEnvironment, runtimeID, pluginFQNs); List workspaceVolumes = workspacePod.getSpec().getVolumes(); assertEquals(workspaceVolumes.size(), 1); diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/SidecarToolingProvisionerTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/SidecarToolingProvisionerTest.java index fde3206e5e4..79a13a00a07 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/SidecarToolingProvisionerTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/SidecarToolingProvisionerTest.java @@ -24,8 +24,8 @@ import java.util.Map; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.wsplugins.ChePluginsApplier; -import org.eclipse.che.api.workspace.server.wsplugins.PluginMetaRetriever; -import org.eclipse.che.api.workspace.server.wsplugins.model.PluginMeta; +import org.eclipse.che.api.workspace.server.wsplugins.PluginFQNParser; +import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.EphemeralWorkspaceUtility; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.KubernetesBrokerInitContainerApplier; @@ -46,11 +46,12 @@ public class SidecarToolingProvisionerTest { private static final String RECIPE_TYPE = "TestingRecipe"; - private static final String PLUGIN_META_NAME = "TestPluginMeta"; + private static final String PLUGIN_FQN_ID = "TestPluginId"; + private static final String PLUGIN_FQN_VERSION = "TestPluginVersion"; @Mock private StartSynchronizer startSynchronizer; @Mock private KubernetesBrokerInitContainerApplier brokerApplier; - @Mock private PluginMetaRetriever pluginMetaRetriever; + @Mock private PluginFQNParser pluginFQNParser; @Mock private PluginBrokerManager brokerManager; @Mock private KubernetesEnvironment nonEphemeralEnvironment; @Mock private KubernetesEnvironment ephemeralEnvironment; @@ -62,8 +63,8 @@ public class SidecarToolingProvisionerTest { "editor", "org.eclipse.che.editor.theia:1.0.0", "plugins", "che-machine-exec-plugin:0.0.1"); - private Collection pluginsMeta = - ImmutableList.of(new PluginMeta().name(PLUGIN_META_NAME)); + private Collection pluginFQNs = + ImmutableList.of(new PluginFQN(null, PLUGIN_FQN_ID, PLUGIN_FQN_VERSION)); private SidecarToolingProvisioner provisioner; @@ -83,11 +84,11 @@ public void setUp() throws Exception { .when(nonEphemeralEnvironment) .getAttributes(); lenient().doReturn(ephemeralEnvironmentAttributes).when(ephemeralEnvironment).getAttributes(); - doReturn(pluginsMeta).when(pluginMetaRetriever).get(any()); + doReturn(pluginFQNs).when(pluginFQNParser).parsePlugins(any()); provisioner = new SidecarToolingProvisioner<>( - workspaceNextAppliers, brokerApplier, pluginMetaRetriever, brokerManager); + workspaceNextAppliers, brokerApplier, pluginFQNParser, brokerManager); } @Test diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/BrokerEnvironmentFactoryTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/BrokerEnvironmentFactoryTest.java index 0199d23d856..7311fcb1586 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/BrokerEnvironmentFactoryTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/wsplugins/brokerphases/BrokerEnvironmentFactoryTest.java @@ -18,20 +18,24 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; +import com.google.common.collect.ImmutableList; +import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.PodSpec; +import java.net.URI; import java.util.Collection; import java.util.List; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.workspace.server.spi.provision.env.AgentAuthEnableEnvVarProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.MachineTokenEnvVarProvider; -import org.eclipse.che.api.workspace.server.wsplugins.model.PluginMeta; +import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers; -import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.BrokersResult; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.BrokerEnvironmentFactory.BrokersConfigs; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -46,6 +50,7 @@ public class BrokerEnvironmentFactoryTest { private static final String INIT_IMAGE = "init:image"; private static final String UNIFIED_BROKER_IMAGE = "unified:image"; + private static final String DEFAULT_REGISTRY = "default.registry"; private static final String IMAGE_PULL_POLICY = "Never"; private static final String PUSH_ENDPOINT = "http://localhost:8080"; @@ -65,7 +70,8 @@ public void setUp() throws Exception { authEnableEnvVarProvider, machineTokenEnvVarProvider, UNIFIED_BROKER_IMAGE, - INIT_IMAGE) { + INIT_IMAGE, + DEFAULT_REGISTRY) { @Override protected KubernetesEnvironment doCreate(BrokersConfigs brokersConfigs) { return null; @@ -84,11 +90,11 @@ protected KubernetesEnvironment doCreate(BrokersConfigs brokersConfigs) { @Test public void testInitBrokerContainer() throws Exception { // given - Collection metas = singletonList(new PluginMeta()); + Collection pluginFQNs = singletonList(new PluginFQN(null, "id", "version")); ArgumentCaptor captor = ArgumentCaptor.forClass(BrokersConfigs.class); // when - factory.create(metas, runtimeId, new BrokersResult()); + factory.create(pluginFQNs, runtimeId); // then verify(factory).doCreate(captor.capture()); @@ -110,7 +116,10 @@ public void testInitBrokerContainer() throws Exception { "-runtime-id", String.format( "%s:%s:%s", - runtimeId.getWorkspaceId(), runtimeId.getEnvName(), runtimeId.getOwnerId()) + runtimeId.getWorkspaceId(), runtimeId.getEnvName(), runtimeId.getOwnerId()), + "--download-metas", + "--registry-address", + DEFAULT_REGISTRY }); assertEquals(Containers.getRamLimit(initContainer), 262144000); assertEquals(Containers.getRamLimit(initContainer), 262144000); @@ -119,11 +128,11 @@ public void testInitBrokerContainer() throws Exception { @Test public void shouldNameContainersAfterPluginBrokerImage() throws Exception { // given - Collection metas = singletonList(new PluginMeta()); + Collection pluginFQNs = singletonList(new PluginFQN(null, "id", "version")); ArgumentCaptor captor = ArgumentCaptor.forClass(BrokersConfigs.class); // when - factory.create(metas, runtimeId, new BrokersResult()); + factory.create(pluginFQNs, runtimeId); // then verify(factory).doCreate(captor.capture()); @@ -138,4 +147,38 @@ public void shouldNameContainersAfterPluginBrokerImage() throws Exception { assertEquals(containers.size(), 1); assertEquals(containers.get(0).getName(), "unified-image"); } + + @Test + public void shouldCreateConfigmapWithPluginFQNs() throws Exception { + // given + Collection pluginFQNs = + ImmutableList.of( + new PluginFQN(null, "testPlugin1", "testver1"), + new PluginFQN(new URI("testregistry"), "testPlugin2", "testver2")); + ArgumentCaptor captor = ArgumentCaptor.forClass(BrokersConfigs.class); + + // when + factory.create(pluginFQNs, runtimeId); + + // then + verify(factory).doCreate(captor.capture()); + BrokersConfigs brokersConfigs = captor.getValue(); + ConfigMap brokerConfigMap = brokersConfigs.configMaps.values().iterator().next(); + String config = brokerConfigMap.getData().get(BrokerEnvironmentFactory.CONFIG_FILE); + + assertFalse(config.contains("\"registry\":null"), "Should not serialize null registry"); + List expected = + ImmutableList.of( + "\"id\":\"testPlugin1\"", + "\"version\":\"testver2\"", + "\"registry\":\"testregistry\"", + "\"id\":\"testPlugin2\"", + "\"version\":\"testver2\""); + for (String expect : expected) { + assertTrue( + config.contains(expect), + String.format( + "Missing field from serialized config: expected '%s' in '%s'", expect, config)); + } + } } diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/wsplugins/brokerphases/OpenshiftBrokerEnvironmentFactory.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/wsplugins/brokerphases/OpenshiftBrokerEnvironmentFactory.java index 076b5c6c650..04ce5f35a76 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/wsplugins/brokerphases/OpenshiftBrokerEnvironmentFactory.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/wsplugins/brokerphases/OpenshiftBrokerEnvironmentFactory.java @@ -18,6 +18,7 @@ import javax.inject.Named; import org.eclipse.che.api.workspace.server.spi.provision.env.AgentAuthEnableEnvVarProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.MachineTokenEnvVarProvider; +import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.BrokerEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.KubernetesBrokerEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; @@ -40,14 +41,16 @@ public OpenshiftBrokerEnvironmentFactory( AgentAuthEnableEnvVarProvider authEnableEnvVarProvider, MachineTokenEnvVarProvider machineTokenEnvVarProvider, @Named("che.workspace.plugin_broker.init.image") String initBrokerImage, - @Named("che.workspace.plugin_broker.unified.image") String unifiedBrokerImage) { + @Named("che.workspace.plugin_broker.unified.image") String unifiedBrokerImage, + @Nullable @Named("che.workspace.plugin_registry_url") String pluginRegistryUrl) { super( cheWebsocketEndpoint, brokerPullPolicy, authEnableEnvVarProvider, machineTokenEnvVarProvider, unifiedBrokerImage, - initBrokerImage); + initBrokerImage, + pluginRegistryUrl); } @Override diff --git a/wsmaster/che-core-api-workspace/pom.xml b/wsmaster/che-core-api-workspace/pom.xml index 49040e06092..87506a47a33 100644 --- a/wsmaster/che-core-api-workspace/pom.xml +++ b/wsmaster/che-core-api-workspace/pom.xml @@ -30,14 +30,6 @@ com.fasterxml.jackson.core jackson-annotations - - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - com.google.code.gson gson diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/PluginFQNParser.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/PluginFQNParser.java new file mode 100644 index 00000000000..80746f7cb27 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/PluginFQNParser.java @@ -0,0 +1,116 @@ +/* + * 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 com.google.common.base.Strings.isNullOrEmpty; +import static java.util.Collections.emptyList; + +import com.google.common.annotations.Beta; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; +import org.eclipse.che.api.workspace.shared.Constants; + +/** + * Parses workspace attributes into a list of {@link PluginFQN}. + * + *

This API is in Beta and is subject to changes or removal. + * + * @author Oleksander Garagatyi + * @author Angel Misevski + */ +@Beta +public class PluginFQNParser { + + /** + * Parses a workspace attributes map into a collection of {@link PluginFQN}. + * + * @param attributes workspace attributes containing plugin and/or editor fields + * @return a Collection of PluginFQN containing the editor and all plugins for this attributes + * @throws InfrastructureException if attributes defines more than one editor + */ + public Collection parsePlugins(Map attributes) + throws InfrastructureException { + if (attributes == null) { + return emptyList(); + } + + String pluginsAttribute = + attributes.getOrDefault(Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE, null); + String editorAttribute = + attributes.getOrDefault(Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, null); + + List metaFQNs = new ArrayList<>(); + if (!isNullOrEmpty(pluginsAttribute)) { + String[] plugins = splitAttribute(pluginsAttribute); + if (plugins.length != 0) { + metaFQNs.addAll(parsePluginFQNs(plugins)); + } + } + if (!isNullOrEmpty(editorAttribute)) { + String[] editor = splitAttribute(editorAttribute); + if (editor.length > 1) { + throw new InfrastructureException( + "Multiple editors found in workspace config attributes. " + + "Only one editor is supported per workspace."); + } + metaFQNs.addAll(parsePluginFQNs(editor)); + } + return metaFQNs; + } + + private Collection parsePluginFQNs(String... plugins) throws InfrastructureException { + List 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( + String.format( + "Plugin registry URL is incorrect. Problematic plugin entry: %s", plugin)); + } + } + String[] idAndVersion = idVersionString.split(":"); + if (idAndVersion.length != 2 || idAndVersion[0].isEmpty() || idAndVersion[1].isEmpty()) { + throw new InfrastructureException( + String.format("Plugin format is illegal. Problematic plugin entry: %s", plugin)); + } + if (collectedFQNs + .stream() + .anyMatch( + p -> p.getId().equals(idAndVersion[0]) && p.getVersion().equals(idAndVersion[1]))) { + throw new InfrastructureException( + String.format( + "Invalid Che tooling plugins configuration: plugin %s:%s is duplicated", + idAndVersion[0], idAndVersion[1])); // even if different repos + } + collectedFQNs.add(new PluginFQN(repo, idAndVersion[0], idAndVersion[1])); + } + return collectedFQNs; + } + + private String[] splitAttribute(String attribute) { + String[] plugins = attribute.split(","); + return Arrays.stream(plugins).map(s -> s.trim()).toArray(String[]::new); + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/PluginMetaRetriever.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/PluginMetaRetriever.java deleted file mode 100644 index 09ffc89ebc4..00000000000 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/PluginMetaRetriever.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * 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 com.google.common.base.Strings.isNullOrEmpty; -import static java.lang.String.format; -import static java.util.Collections.emptyList; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.google.common.annotations.Beta; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Strings; -import com.google.common.io.CharStreams; -import java.io.IOException; -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.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.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Fetches Che tooling plugin objects corresponding to attributes of a workspace config. - * - *

This API is in Beta and is subject to changes or removal. - * - * @author Oleksander Garagatyi - */ -@Beta -public class PluginMetaRetriever { - - private static final Logger LOG = LoggerFactory.getLogger(PluginMetaRetriever.class); - private static final String CHE_PLUGIN_OBJECT_ERROR = - "Che plugin '%s:%s' configuration is invalid. %s"; - private static final String CHE_REGISTRY_MISSING_ERROR = - String.format( - "Workspace requested Che Editor/Plugins but plugin registry is not configured. " - + "Property %s should be set for to allow Editors and Plugins", - Constants.CHE_WORKSPACE_PLUGIN_REGISTRY_URL_PROPERTY); - - private static final ObjectMapper YAML_PARSER = new ObjectMapper(new YAMLFactory()); - - private final UriBuilder pluginRegistry; - - @Inject - public PluginMetaRetriever( - @Nullable @Named(Constants.CHE_WORKSPACE_PLUGIN_REGISTRY_URL_PROPERTY) - String pluginRegistry) { - if (pluginRegistry == null) { - LOG.info( - format( - "Che tooling plugins feature is disabled - Che plugin registry API endpoint property '%s' is not configured", - Constants.CHE_WORKSPACE_PLUGIN_REGISTRY_URL_PROPERTY)); - this.pluginRegistry = null; - } else { - this.pluginRegistry = UriBuilder.fromUri(pluginRegistry).path("plugins"); - } - } - - /** - * Gets Che tooling plugins list from provided workspace config attributes, fetches corresponding - * meta objects from Che plugin registry and returns list of {@link PluginMeta} with meta - * information about plugins in a workspace. - * - *

This API is in Beta and is subject to changes or removal. - * - * @param attributes workspace config attributes - * @throws InfrastructureException when attributes contain invalid Che plugins entries or Che - * plugin meta files retrieval from Che plugin registry fails or returns invalid data - */ - @Beta - public Collection get(Map attributes) throws InfrastructureException { - if (attributes == null) { - return emptyList(); - } - - // Have to check for empty value instead of plain null as it's possible to have empty - // plugins attribute - String pluginsAttribute = - attributes.getOrDefault(Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE, null); - String editorAttribute = - attributes.getOrDefault(Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, null); - - // Check if any plugins/editor is in workspace - if (Strings.isNullOrEmpty(editorAttribute) && Strings.isNullOrEmpty(pluginsAttribute)) { - return emptyList(); - } - - // If workspace plugins or editor are not null, but registry is null, throw error - if (pluginRegistry == null) { - throw new InfrastructureException(CHE_REGISTRY_MISSING_ERROR); - } - - List metaFQNs = new ArrayList<>(); - if (!isNullOrEmpty(pluginsAttribute)) { - String[] plugins = pluginsAttribute.split(" *, *"); - if (plugins.length != 0) { - Collection pluginsFQNs = parsePluginFQNs(plugins); - metaFQNs.addAll(pluginsFQNs); - } - } - if (!isNullOrEmpty(editorAttribute)) { - Collection 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."); - } - metaFQNs.addAll(editorIdVersionCollection); - } - - return getMetas(metaFQNs); - } - - private Collection parsePluginFQNs(String... plugins) throws InfrastructureException { - List 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); - } - 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", - idVersion[0] + ":" + idVersion[1])); // even if different repos - } - collectedFQNs.add(new PluginFQN(repo, idVersion[0], idVersion[1])); - } - return collectedFQNs; - } - - private Collection getMetas(List pluginFQNs) - throws InfrastructureException { - ArrayList metas = new ArrayList<>(); - for (PluginFQN pluginFqn : pluginFQNs) { - metas.add(getMeta(pluginFqn)); - } - - return metas; - } - - private PluginMeta getMeta(PluginFQN pluginFQN) throws InfrastructureException { - final String id = pluginFQN.getId(); - final String version = pluginFQN.getVersion(); - try { - 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 | MalformedURLException e) { - throw new InternalInfrastructureException( - format( - "Metadata of plugin %s:%s retrieval failed. Error is %s", - id, version, e.getMessage())); - } catch (IOException e) { - throw new InfrastructureException( - format( - "Error occurred on retrieval of plugin %s. Error: %s", - id + ':' + version, e.getMessage())); - } - } - - @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, - meta.getId(), - "Plugin id in attribute doesn't match plugin metadata. Plugin object seems broken."); - requireNotNullNorEmpty( - meta.getVersion(), CHE_PLUGIN_OBJECT_ERROR, id, version, "Version is missing."); - requireEqual( - version, - meta.getVersion(), - "Plugin version in workspace config attributes doesn't match plugin metadata. Plugin object seems broken."); - requireNotNullNorEmpty( - meta.getName(), CHE_PLUGIN_OBJECT_ERROR, id, version, "Name is missing."); - requireNotNullNorEmpty( - meta.getType(), CHE_PLUGIN_OBJECT_ERROR, id, version, "Type is missing."); - } - - @VisibleForTesting - protected T getBody(URI uri, Class clas) throws IOException { - HttpURLConnection httpURLConnection = null; - try { - httpURLConnection = (HttpURLConnection) uri.toURL().openConnection(); - - int responseCode = httpURLConnection.getResponseCode(); - if (responseCode != 200) { - throw new IOException( - format( - "Can't get object by URI '%s'. Error: %s", - uri.toString(), getError(httpURLConnection))); - } - - return parseYamlResponseStreamAndClose(httpURLConnection.getInputStream(), clas); - } finally { - if (httpURLConnection != null) { - httpURLConnection.disconnect(); - } - } - } - - private String getError(HttpURLConnection httpURLConnection) throws IOException { - try (InputStreamReader isr = new InputStreamReader(httpURLConnection.getInputStream())) { - return CharStreams.toString(isr); - } - } - - protected T parseYamlResponseStreamAndClose(InputStream inputStream, Class clazz) - throws IOException { - try (InputStreamReader reader = new InputStreamReader(inputStream)) { - return YAML_PARSER.readValue(reader, clazz); - } catch (IOException e) { - throw new IOException( - "Internal server error. Unexpected response body received from Che plugin registry API." - + e.getLocalizedMessage(), - e); - } - } - - @SuppressWarnings("SameParameterValue") - private void requireNotNullNorEmpty(String s, String error, String... errorArgs) - throws InfrastructureException { - if (s == null || s.isEmpty()) { - throw new InfrastructureException(format(error, (Object[]) errorArgs)); - } - } - - private void requireEqual(String version, String version1, String error, String... errorArgs) - throws InfrastructureException { - if (!version.equals(version1)) { - throw new InfrastructureException(format(error, (Object[]) errorArgs)); - } - } -} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/PluginFQN.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/PluginFQN.java index 31c5ad1dfa6..6235a995e1b 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/PluginFQN.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/wsplugins/model/PluginFQN.java @@ -11,13 +11,17 @@ */ package org.eclipse.che.api.workspace.server.wsplugins.model; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import java.net.URI; +import java.util.Objects; /** * Represents full information about plugin, including registry address, id and version. * * @author Max Shaposhnyk */ +@JsonInclude(Include.NON_NULL) public class PluginFQN { private URI registry; @@ -53,4 +57,28 @@ public String getVersion() { public void setVersion(String version) { this.version = version; } + + @Override + public int hashCode() { + return Objects.hash(getRegistry(), getId(), getVersion()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + PluginFQN other = (PluginFQN) obj; + return Objects.equals(id, other.id) + && Objects.equals(version, other.version) + && Objects.equals(registry, other.registry); + } + + @Override + public String toString() { + return String.format("{id:%s, version:%s, registry:%s}", this.id, this.version, this.registry); + } } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/wsplugins/PluginFQNParserTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/wsplugins/PluginFQNParserTest.java new file mode 100644 index 00000000000..ad63c10f9e2 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/wsplugins/PluginFQNParserTest.java @@ -0,0 +1,223 @@ +/* + * 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.testng.Assert.assertEqualsNoOrder; +import static org.testng.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.api.workspace.server.wsplugins.model.PluginFQN; +import org.eclipse.che.api.workspace.shared.Constants; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +@Listeners(MockitoTestNGListener.class) +public class PluginFQNParserTest { + + private static final String PLUGIN_FORMAT = "%s/%s:%s"; + private static final String PLUGIN_FORMAT_WITHOUT_REGISTRY = "%s:%s"; + + private PluginFQNParser parser; + + @BeforeClass + public void setUp() throws Exception { + parser = new PluginFQNParser(); + } + + @Test + public void shouldReturnEmptyListWhenNoPluginsOrEditors() throws Exception { + Map attributes = ImmutableMap.of("testProperty", "testValue"); + + Collection result = parser.parsePlugins(attributes); + + assertTrue( + result.isEmpty(), + "PluginFQNParser should return empty list when attributes does not contain plugins or editors"); + } + + @Test(dataProvider = "validAttributesProvider") + public void shouldParseAllPluginsAndEditor( + String desc, List expected, Map attributes) throws Exception { + Collection actual = parser.parsePlugins(attributes); + assertEqualsNoOrder(actual.toArray(), expected.toArray(), desc); + } + + @Test( + dataProvider = "invalidAttributeStringProvider", + expectedExceptions = InfrastructureException.class) + public void shouldThrowExceptionWhenPluginStringIsInvalid(String plugin) throws Exception { + Map attributes = createAttributes("", plugin); + parser.parsePlugins(attributes); + } + + @Test( + dataProvider = "invalidAttributeStringProvider", + expectedExceptions = InfrastructureException.class) + public void shouldThrowExceptionWhenEditorStringIsInvalid(String editor) throws Exception { + Map attributes = createAttributes(editor, ""); + + parser.parsePlugins(attributes); + } + + @Test( + expectedExceptions = InfrastructureException.class, + expectedExceptionsMessageRegExp = "Multiple editors.*") + public void shouldThrowExceptionWhenMultipleEditorsDefined() throws Exception { + Map attributes = createAttributes("editor1,editor2", ""); + + parser.parsePlugins(attributes); + } + + @Test( + expectedExceptions = InfrastructureException.class, + expectedExceptionsMessageRegExp = + "Invalid Che tooling plugins configuration: plugin .*:.* is duplicated") + public void shouldThrowExceptionWhenDuplicatePluginDefined() throws Exception { + Map attributes = + createAttributes( + "", + formatPlugin("http://testregistry1:8080", "testplugin", "1.0"), + formatPlugin("http://testregistry2:8080", "testplugin", "1.0")); + + parser.parsePlugins(attributes); + } + + @Test( + expectedExceptions = InfrastructureException.class, + expectedExceptionsMessageRegExp = + "Invalid Che tooling plugins configuration: plugin .*:.* is duplicated") + public void shouldDetectDuplicatedPluginsIfTheyArePrefixedSuffixedWithEmptySpaces() + throws Exception { + Map attributes = + createAttributes( + "", + " " + formatPlugin("http://testregistry1:8080", "testplugin", "1.0"), + formatPlugin("http://testregistry2:8080", "testplugin", "1.0") + " "); + + parser.parsePlugins(attributes); + } + + @DataProvider(name = "invalidAttributeStringProvider") + public static Object[][] invalidAttributeStringProvider() { + return new Object[][] { + {formatPlugin("http://bad registry url", "testplugin", "1.0")}, + {formatPlugin("http://testregistry:8080", "bad:pluginname", "1.0")}, + {formatPlugin("http://testregistry:8080", "", "emptyID")}, + {formatPlugin("http://testregistry:8080", "emptyVersion", "")} + }; + } + + // Objects are + // (String description, List expectedPlugins, Map attributes) + @DataProvider(name = "validAttributesProvider") + public static Object[][] validAttributesProvider() { + PluginFQN basicEditor = new PluginFQN(URI.create("http://registry:8080"), "editor", "ver"); + PluginFQN withRegistry = new PluginFQN(URI.create("http://registry:8080"), "plugin", "1.0"); + PluginFQN noRegistry = new PluginFQN(null, "pluginNoRegistry", "2.0"); + PluginFQN pathRegistry = + new PluginFQN(URI.create("http://registry/multiple/path/"), "pluginPathRegistry", "3.0"); + return new Object[][] { + { + "Test plugin with registry", + ImmutableList.of(basicEditor, withRegistry), + createAttributes(formatPlugin(basicEditor), formatPlugins(withRegistry)) + }, + { + "Test plugin without registry", + ImmutableList.of(basicEditor, noRegistry), + createAttributes(formatPlugin(basicEditor), formatPlugins(noRegistry)) + }, + { + "Test plugin with multi-level path in registry", + ImmutableList.of(basicEditor, pathRegistry), + createAttributes(formatPlugin(basicEditor), formatPlugins(pathRegistry)) + }, + { + "Test attributes with no editor field", + ImmutableList.of(withRegistry), + createAttributes(null, formatPlugins(withRegistry)) + }, + { + "Test attributes with empty editor field", + ImmutableList.of(withRegistry), + createAttributes("", formatPlugins(withRegistry)) + }, + { + "Test attributes with no plugin field", + ImmutableList.of(basicEditor), + createAttributes(formatPlugin(basicEditor), (String[]) null) + }, + { + "Test attributes with empty plugin field", + ImmutableList.of(basicEditor), + createAttributes(formatPlugin(basicEditor), "") + }, + { + "Test attributes with no plugin or editor field", + Collections.emptyList(), + createAttributes(null, (String[]) null) + }, + { + "Test attributes with empty plugin and editor field", + Collections.emptyList(), + createAttributes("", "") + }, + { + "Test multiple plugins and an editor", + ImmutableList.of(basicEditor, withRegistry, noRegistry, pathRegistry), + createAttributes( + formatPlugin(basicEditor), formatPlugins(withRegistry, noRegistry, pathRegistry)) + }, + }; + } + + private static String[] formatPlugins(PluginFQN... plugins) { + return Arrays.stream(plugins).map(p -> formatPlugin(p)).toArray(String[]::new); + } + + private static String formatPlugin(PluginFQN plugin) { + String registry = plugin.getRegistry() == null ? null : plugin.getRegistry().toString(); + return formatPlugin(registry, plugin.getId(), plugin.getVersion()); + } + + private static String formatPlugin(String registry, String id, String version) { + if (registry == null) { + return String.format(PLUGIN_FORMAT_WITHOUT_REGISTRY, id, version); + } else { + return String.format(PLUGIN_FORMAT, registry, id, version); + } + } + + private static Map createAttributes(String editor, String... plugins) { + Map attributes = new HashMap<>(); + if (plugins != null) { + String allPlugins = String.join(",", plugins); + attributes.put(Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE, allPlugins); + } + if (editor != null) { + attributes.put(Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, editor); + } + return ImmutableMap.copyOf(attributes); + } +} diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/wsplugins/PluginMetaRetrieverTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/wsplugins/PluginMetaRetrieverTest.java deleted file mode 100644 index a48f2fdf2af..00000000000 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/wsplugins/PluginMetaRetrieverTest.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * 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 static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import com.google.common.collect.ImmutableMap; -import java.net.URI; -import java.util.Collection; -import java.util.Collections; -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.mockito.testng.MockitoTestNGListener; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -/** @author Angel Misevski */ -@Listeners(MockitoTestNGListener.class) -public class PluginMetaRetrieverTest { - - 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 = "pluginMetaRetrieverWithAndWithoutRegistry") - public void shouldReturnEmptyListWhenAttributesNull(PluginMetaRetriever retriever) - throws Exception { - - Collection metas = retriever.get(null); - - assertTrue( - metas.isEmpty(), "PluginMetaRetriever should return empty list when attributes is null"); - } - - @Test(dataProvider = "pluginMetaRetrieverWithAndWithoutRegistry") - public void shouldReturnEmptyListWhenNoPluginsOrEditors(PluginMetaRetriever retriever) - throws Exception { - Map attributes = Collections.emptyMap(); - - Collection metas = retriever.get(attributes); - - assertTrue( - metas.isEmpty(), "PluginMetaRetriever should return empty list when attributes is empty"); - } - - @Test(dataProvider = "pluginMetaRetrieverWithAndWithoutRegistry") - public void shouldReturnEmptyListWhenPluginsValueEmpty(PluginMetaRetriever retriever) - throws Exception { - Map attributes = - ImmutableMap.builder() - .put(Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE, "") - .build(); - - Collection metas = retriever.get(attributes); - - assertTrue( - metas.isEmpty(), - "PluginMetaRetriever should return empty list when plugins value is empty"); - } - - @Test(dataProvider = "pluginMetaRetrieverWithAndWithoutRegistry") - public void shouldReturnEmptyListWhenEditorValueEmpty(PluginMetaRetriever retriever) - throws Exception { - Map attributes = - ImmutableMap.builder() - .put(Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, "") - .build(); - - Collection metas = retriever.get(attributes); - - assertTrue( - metas.isEmpty(), "PluginMetaRetriever should return empty list when editor value is empty"); - } - - @Test(expectedExceptions = InfrastructureException.class) - public void shouldThrowExceptionWhenPluginsNotEmptyAndRegistryNotDefined() throws Exception { - Map attributes = - ImmutableMap.builder() - .put(Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE, "plugin1") - .build(); - - metaRetriever.get(attributes); - - fail( - "PluginMetaRetriever should throw Exception when attributes includes " - + "plugins and no registry is defined"); - } - - @Test(expectedExceptions = InfrastructureException.class) - public void shouldThrowExceptionWhenEditorNotEmptyAndRegistryNotDefined() throws Exception { - Map attributes = - ImmutableMap.builder() - .put(Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, "editor") - .build(); - - metaRetriever.get(attributes); - - fail( - "PluginMetaRetriever should throw Exception when attributes includes " - + "editor and no registry is defined"); - } - - @DataProvider - public Object[][] pluginMetaRetrieverWithAndWithoutRegistry() { - return new Object[][] { - {new PluginMetaRetriever(BASE_REGISTRY)}, {new PluginMetaRetriever(null)} - }; - } - - @Test(dataProvider = "pluginProvider") - public void shouldGetMetaByCorrectURLUsingBaseRegistry( - Map 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 createAttributes(String plugins, String editor) { - return ImmutableMap.of( - Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE, - plugins, - Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, - editor); - } -}