Make Devfile parse k8s/os recipe content in the same way as infrastructures do (#12810)

Signed-off-by: Sergii Leshchenko <sleshche@redhat.com>
7.20.x
Sergii Leshchenko 2019-03-06 14:14:36 +02:00 committed by GitHub
parent 1dc5b8b946
commit 78f1cabc3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 265 additions and 181 deletions

View File

@ -23,8 +23,6 @@ import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.extensions.Ingress;
import io.fabric8.kubernetes.client.KubernetesClientException;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@ -36,11 +34,17 @@ import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.installer.server.InstallerRegistry;
import org.eclipse.che.api.workspace.server.model.impl.WarningImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.*;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.api.workspace.server.spi.environment.MachineConfigsValidator;
import org.eclipse.che.api.workspace.server.spi.environment.MemoryAttributeProvisioner;
import org.eclipse.che.api.workspace.server.spi.environment.RecipeRetriever;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.Names;
import org.eclipse.che.workspace.infrastructure.kubernetes.Warnings;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers;
/**
@ -51,7 +55,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers;
public class KubernetesEnvironmentFactory
extends InternalEnvironmentFactory<KubernetesEnvironment> {
private final KubernetesClientFactory clientFactory;
private final KubernetesRecipeParser recipeParser;
private final KubernetesEnvironmentValidator envValidator;
private final MemoryAttributeProvisioner memoryProvisioner;
@ -60,11 +64,11 @@ public class KubernetesEnvironmentFactory
InstallerRegistry installerRegistry,
RecipeRetriever recipeRetriever,
MachineConfigsValidator machinesValidator,
KubernetesClientFactory clientFactory,
KubernetesRecipeParser recipeParser,
KubernetesEnvironmentValidator envValidator,
MemoryAttributeProvisioner memoryProvisioner) {
super(installerRegistry, recipeRetriever, machinesValidator);
this.clientFactory = clientFactory;
this.recipeParser = recipeParser;
this.envValidator = envValidator;
this.memoryProvisioner = memoryProvisioner;
}
@ -80,37 +84,8 @@ public class KubernetesEnvironmentFactory
if (sourceWarnings != null) {
warnings.addAll(sourceWarnings);
}
String content = recipe.getContent();
String contentType = recipe.getContentType();
checkNotNull(contentType, "Kubernetes Recipe content type should not be null");
switch (contentType) {
case "application/x-yaml":
case "text/yaml":
case "text/x-yaml":
break;
default:
throw new ValidationException(
"Provided environment recipe content type '"
+ contentType
+ "' is unsupported. Supported values are: "
+ "application/x-yaml, text/yaml, text/x-yaml");
}
final List<HasMetadata> list;
try {
// Load list of Kubernetes objects into java List.
list = clientFactory.create().load(new ByteArrayInputStream(content.getBytes())).get();
} catch (KubernetesClientException e) {
// KubernetesClient wraps the error when a JsonMappingException occurs so we need the cause
String message = e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
if (message.contains("\n")) {
// Clean up message if it comes from JsonMappingException. Format is e.g.
// `No resource type found for:v1#Route1\n at [...]`
message = message.split("\\n", 2)[0];
}
throw new ValidationException(format("Could not parse Kubernetes recipe: %s", message));
}
final List<HasMetadata> recipeObjects = recipeParser.parse(recipe);
Map<String, Pod> pods = new HashMap<>();
Map<String, Deployment> deployments = new HashMap<>();
@ -119,14 +94,11 @@ public class KubernetesEnvironmentFactory
Map<String, PersistentVolumeClaim> pvcs = new HashMap<>();
Map<String, Secret> secrets = new HashMap<>();
boolean isAnyIngressPresent = false;
for (HasMetadata object : list) {
for (HasMetadata object : recipeObjects) {
checkNotNull(object.getKind(), "Environment contains object without specified kind field");
checkNotNull(object.getMetadata(), "%s metadata must not be null", object.getKind());
checkNotNull(object.getMetadata().getName(), "%s name must not be null", object.getKind());
// needed because Che master namespace is set by K8s API during list loading
object.getMetadata().setNamespace(null);
if (object instanceof Pod) {
Pod pod = (Pod) object;
pods.put(pod.getMetadata().getName(), pod);
@ -161,8 +133,6 @@ public class KubernetesEnvironmentFactory
Warnings.INGRESSES_IGNORED_WARNING_CODE, Warnings.INGRESSES_IGNORED_WARNING_MESSAGE));
}
addRamAttributes(machines, pods.values());
KubernetesEnvironment k8sEnv =
KubernetesEnvironment.builder()
.setInternalRecipe(recipe)
@ -178,14 +148,16 @@ public class KubernetesEnvironmentFactory
.setConfigMaps(configMaps)
.build();
addRamAttributes(k8sEnv.getMachines(), k8sEnv.getPodsData().values());
envValidator.validate(k8sEnv);
return k8sEnv;
}
@VisibleForTesting
void addRamAttributes(Map<String, InternalMachineConfig> machines, Collection<Pod> pods) {
for (Pod pod : pods) {
void addRamAttributes(Map<String, InternalMachineConfig> machines, Collection<PodData> pods) {
for (PodData pod : pods) {
for (Container container : pod.getSpec().getContainers()) {
final String machineName = Names.machineName(pod, container);
InternalMachineConfig machineConfig;

View File

@ -0,0 +1,117 @@
/*
* 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.workspace.infrastructure.kubernetes.environment;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;
import com.google.common.collect.ImmutableSet;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClientException;
import java.io.ByteArrayInputStream;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
/**
* Parses Kubernetes objects from recipe.
*
* <p>Note that this class can also parse OpenShift specific objects.
*
* @author Sergii Leshchenko
*/
public class KubernetesRecipeParser {
private static final Set<String> SUPPORTED_CONTENT_TYPES =
ImmutableSet.of("application/x-yaml", "text/yaml", "text/x-yaml");
private final KubernetesClientFactory clientFactory;
@Inject
public KubernetesRecipeParser(KubernetesClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
/**
* Parses Kubernetes objects from recipe.
*
* @param recipe that contains objects to parse
* @return parsed objects
* @throws IllegalArgumentException if recipe content is null
* @throws IllegalArgumentException if recipe content type is null
* @throws ValidationException if recipe content has broken format
* @throws ValidationException if recipe content has unrecognized objects
* @throws InfrastructureException when exception occurred during kubernetes client creation
*/
public List<HasMetadata> parse(InternalRecipe recipe)
throws ValidationException, InfrastructureException {
String content = recipe.getContent();
String contentType = recipe.getContentType();
checkNotNull(contentType, "Recipe content type must not be null");
if (!SUPPORTED_CONTENT_TYPES.contains(contentType)) {
throw new ValidationException(
format(
"Provided environment recipe content type '%s' is unsupported. Supported values are: %s",
contentType, String.join(", ", SUPPORTED_CONTENT_TYPES)));
}
return parse(content);
}
/**
* Parses Kubernetes objects from recipe content.
*
* @param recipeContent recipe content that should be parsed
* @return parsed objects
* @throws IllegalArgumentException if recipe content is null
* @throws ValidationException if recipe content has broken format
* @throws ValidationException if recipe content has unrecognized objects
* @throws InfrastructureException when exception occurred during kubernetes client creation
*/
public List<HasMetadata> parse(String recipeContent)
throws ValidationException, InfrastructureException {
checkNotNull(recipeContent, "Recipe content type must not be null");
try {
// Behavior:
// - If `content` is a single object like Deployment, load().get() will get the object in that
// list
// - If `content` is a Kubernetes List, load().get() will get the objects in that list
// - If `content` is an OpenShift template, load().get() will get the objects in the template
// with parameters substituted (e.g. with default values).
List<HasMetadata> parsed =
clientFactory.create().load(new ByteArrayInputStream(recipeContent.getBytes())).get();
// needed because Che master namespace is set by K8s API during list loading
parsed
.stream()
.filter(o -> o.getMetadata() != null)
.forEach(o -> o.getMetadata().setNamespace(null));
return parsed;
} catch (KubernetesClientException e) {
// KubernetesClient wraps the error when a JsonMappingException occurs so we need the cause
String message = e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
if (message.contains("\n")) {
// Clean up message if it comes from JsonMappingException. Format is e.g.
// `No resource type found for:v1#Route1\n at [...]`
message = message.split("\\n", 2)[0];
}
throw new ValidationException(format("Could not parse Kubernetes recipe: %s", message));
}
}
}

View File

@ -53,10 +53,6 @@ import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
import io.fabric8.kubernetes.api.model.extensions.IngressBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.ParameterNamespaceListVisitFromServerGetDeleteRecreateWaitApplicable;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@ -66,7 +62,7 @@ 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.spi.environment.InternalRecipe;
import org.eclipse.che.api.workspace.server.spi.environment.MemoryAttributeProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
@ -81,37 +77,27 @@ import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class KubernetesEnvironmentFactoryTest {
private static final String YAML_RECIPE = "application/x-yaml";
public static final String MACHINE_NAME_1 = "machine1";
public static final String MACHINE_NAME_2 = "machine2";
private KubernetesEnvironmentFactory k8sEnvFactory;
@Mock private KubernetesClientFactory clientFactory;
@Mock private KubernetesEnvironmentValidator k8sEnvValidator;
@Mock private KubernetesClient client;
@Mock private InternalEnvironment internalEnvironment;
@Mock private InternalRecipe internalRecipe;
@Mock private InternalMachineConfig machineConfig1;
@Mock private InternalMachineConfig machineConfig2;
@Mock private MemoryAttributeProvisioner memoryProvisioner;
@Mock private KubernetesRecipeParser k8sRecipeParser;
private Map<String, InternalMachineConfig> machines;
@Mock
private ParameterNamespaceListVisitFromServerGetDeleteRecreateWaitApplicable<HasMetadata, Boolean>
loadedRecipe;
@BeforeMethod
public void setup() throws Exception {
k8sEnvFactory =
new KubernetesEnvironmentFactory(
null, null, null, clientFactory, k8sEnvValidator, memoryProvisioner);
when(clientFactory.create()).thenReturn(client);
when(client.load(any(InputStream.class))).thenReturn(loadedRecipe);
null, null, null, k8sRecipeParser, k8sEnvValidator, memoryProvisioner);
lenient().when(internalEnvironment.getRecipe()).thenReturn(internalRecipe);
when(internalRecipe.getContentType()).thenReturn(YAML_RECIPE);
when(internalRecipe.getContent()).thenReturn("recipe content");
machines = ImmutableMap.of(MACHINE_NAME_1, machineConfig1, MACHINE_NAME_2, machineConfig2);
}
@ -122,7 +108,7 @@ public class KubernetesEnvironmentFactoryTest {
new ServiceBuilder().withNewMetadata().withName("service1").endMetadata().build();
Service service2 =
new ServiceBuilder().withNewMetadata().withName("service2").endMetadata().build();
when(loadedRecipe.get()).thenReturn(asList(service1, service2));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(service1, service2));
// when
KubernetesEnvironment k8sEnv = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
@ -140,7 +126,7 @@ public class KubernetesEnvironmentFactoryTest {
new PersistentVolumeClaimBuilder().withNewMetadata().withName("pvc1").endMetadata().build();
PersistentVolumeClaim pvc2 =
new PersistentVolumeClaimBuilder().withNewMetadata().withName("pvc2").endMetadata().build();
when(loadedRecipe.get()).thenReturn(asList(pvc1, pvc2));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(pvc1, pvc2));
// when
KubernetesEnvironment k8sEnv = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
@ -153,7 +139,7 @@ public class KubernetesEnvironmentFactoryTest {
@Test
public void ignoreIgressesWhenRecipeContainsThem() throws Exception {
when(loadedRecipe.get())
when(k8sRecipeParser.parse(any(InternalRecipe.class)))
.thenReturn(
asList(
new IngressBuilder().withNewMetadata().withName("ingress1").endMetadata().build(),
@ -173,7 +159,7 @@ public class KubernetesEnvironmentFactoryTest {
public void addSecretsWhenRecipeContainsThem() throws Exception {
Secret secret =
new SecretBuilder().withNewMetadata().withName("test-secret").endMetadata().build();
when(loadedRecipe.get()).thenReturn(singletonList(secret));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(secret));
final KubernetesEnvironment parsed =
k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
@ -188,7 +174,7 @@ public class KubernetesEnvironmentFactoryTest {
public void addConfigMapsWhenRecipeContainsThem() throws Exception {
ConfigMap configMap =
new ConfigMapBuilder().withNewMetadata().withName("test-configmap").endMetadata().build();
when(loadedRecipe.get()).thenReturn(singletonList(configMap));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(configMap));
final KubernetesEnvironment parsed =
k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
@ -207,7 +193,7 @@ public class KubernetesEnvironmentFactoryTest {
.endMetadata()
.withSpec(new PodSpec())
.build();
when(loadedRecipe.get()).thenReturn(singletonList(pod));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(pod));
// when
KubernetesEnvironment k8sEnv = k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
@ -242,7 +228,7 @@ public class KubernetesEnvironmentFactoryTest {
.endSpec()
.build();
when(loadedRecipe.get()).thenReturn(singletonList(deployment));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(deployment));
// when
final KubernetesEnvironment k8sEnv =
@ -286,7 +272,7 @@ public class KubernetesEnvironmentFactoryTest {
.withNewSpec()
.endSpec()
.build();
when(loadedRecipe.get()).thenReturn(asList(deployment, pod));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(deployment, pod));
// when
final KubernetesEnvironment k8sEnv =
@ -305,7 +291,8 @@ public class KubernetesEnvironmentFactoryTest {
@Test(expectedExceptions = ValidationException.class)
public void exceptionOnRecipeLoadError() throws Exception {
when(loadedRecipe.get()).thenThrow(new KubernetesClientException("Could not parse recipe"));
when(k8sRecipeParser.parse(any(InternalRecipe.class)))
.thenThrow(new ValidationException("Could not parse recipe"));
k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
}
@ -316,7 +303,7 @@ public class KubernetesEnvironmentFactoryTest {
public void exceptionOnObjectWithNoKindSpecified() throws Exception {
HasMetadata object = mock(HasMetadata.class);
when(object.getKind()).thenReturn(null);
when(loadedRecipe.get()).thenReturn(singletonList(object));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object));
k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
}
@ -328,7 +315,7 @@ public class KubernetesEnvironmentFactoryTest {
HasMetadata object = mock(HasMetadata.class);
when(object.getKind()).thenReturn("MyObject");
when(object.getMetadata()).thenReturn(null);
when(loadedRecipe.get()).thenReturn(singletonList(object));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object));
k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
}
@ -340,7 +327,7 @@ public class KubernetesEnvironmentFactoryTest {
HasMetadata object = mock(HasMetadata.class);
when(object.getKind()).thenReturn("MyObject");
when(object.getMetadata()).thenReturn(new ObjectMetaBuilder().withName(null).build());
when(loadedRecipe.get()).thenReturn(singletonList(object));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object));
k8sEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
}
@ -353,10 +340,10 @@ public class KubernetesEnvironmentFactoryTest {
final long secondMachineRamRequest = 512;
when(machineConfig1.getAttributes()).thenReturn(new HashMap<>());
when(machineConfig2.getAttributes()).thenReturn(new HashMap<>());
final Set<Pod> pods =
final Set<PodData> pods =
ImmutableSet.of(
mockPod(MACHINE_NAME_1, firstMachineRamLimit, firstMachineRamRequest),
mockPod(MACHINE_NAME_2, secondMachineRamLimit, secondMachineRamRequest));
createPodData(MACHINE_NAME_1, firstMachineRamLimit, firstMachineRamRequest),
createPodData(MACHINE_NAME_2, secondMachineRamLimit, secondMachineRamRequest));
k8sEnvFactory.addRamAttributes(machines, pods);
@ -366,17 +353,14 @@ public class KubernetesEnvironmentFactoryTest {
.provision(eq(machineConfig2), eq(secondMachineRamLimit), eq(secondMachineRamRequest));
}
private static Pod mockPod(String machineName, long ramLimit, long ramRequest) {
private static PodData createPodData(String machineName, long ramLimit, long ramRequest) {
final String containerName = "container_" + machineName;
final Container containerMock = mock(Container.class);
final ResourceRequirements resourcesMock = mock(ResourceRequirements.class);
final Quantity limitQuantityMock = mock(Quantity.class);
final Quantity requestQuantityMock = mock(Quantity.class);
final Pod podMock = mock(Pod.class);
final PodSpec specMock = mock(PodSpec.class);
final ObjectMeta metadataMock = mock(ObjectMeta.class);
when(podMock.getSpec()).thenReturn(specMock);
when(podMock.getMetadata()).thenReturn(metadataMock);
when(limitQuantityMock.getAmount()).thenReturn(String.valueOf(ramLimit));
when(requestQuantityMock.getAmount()).thenReturn(String.valueOf(ramRequest));
when(resourcesMock.getLimits()).thenReturn(ImmutableMap.of("memory", limitQuantityMock));
@ -387,6 +371,6 @@ public class KubernetesEnvironmentFactoryTest {
.thenReturn(
ImmutableMap.of(format(MACHINE_NAME_ANNOTATION_FMT, containerName), machineName));
when(specMock.getContainers()).thenReturn(ImmutableList.of(containerMock));
return podMock;
return new PodData(specMock, metadataMock);
}
}

View File

@ -22,10 +22,8 @@ import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.openshift.api.model.DeploymentConfig;
import io.fabric8.openshift.api.model.Route;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@ -36,11 +34,18 @@ import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.Warning;
import org.eclipse.che.api.installer.server.InstallerRegistry;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.*;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironmentFactory;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.api.workspace.server.spi.environment.MachineConfigsValidator;
import org.eclipse.che.api.workspace.server.spi.environment.MemoryAttributeProvisioner;
import org.eclipse.che.api.workspace.server.spi.environment.RecipeRetriever;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.workspace.infrastructure.kubernetes.Names;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.Containers;
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory;
/**
* Parses {@link InternalEnvironment} into {@link OpenShiftEnvironment}.
@ -49,8 +54,8 @@ import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory
*/
public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<OpenShiftEnvironment> {
private final OpenShiftClientFactory clientFactory;
private final OpenShiftEnvironmentValidator envValidator;
private final KubernetesRecipeParser k8sObjectsParser;
private final MemoryAttributeProvisioner memoryProvisioner;
@Inject
@ -58,12 +63,12 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
InstallerRegistry installerRegistry,
RecipeRetriever recipeRetriever,
MachineConfigsValidator machinesValidator,
OpenShiftClientFactory clientFactory,
OpenShiftEnvironmentValidator envValidator,
KubernetesRecipeParser k8sObjectsParser,
MemoryAttributeProvisioner memoryProvisioner) {
super(installerRegistry, recipeRetriever, machinesValidator);
this.clientFactory = clientFactory;
this.envValidator = envValidator;
this.k8sObjectsParser = k8sObjectsParser;
this.memoryProvisioner = memoryProvisioner;
}
@ -78,40 +83,8 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
if (sourceWarnings != null) {
warnings.addAll(sourceWarnings);
}
String content = recipe.getContent();
String contentType = recipe.getContentType();
checkNotNull(contentType, "OpenShift Recipe content type should not be null");
switch (contentType) {
case "application/x-yaml":
case "text/yaml":
case "text/x-yaml":
break;
default:
throw new ValidationException(
"Provided environment recipe content type '"
+ contentType
+ "' is unsupported. Supported values are: "
+ "application/x-yaml, text/yaml, text/x-yaml");
}
final List<HasMetadata> list;
try {
// Behavior:
// - If `content` is a Kubernetes List, load().get() will get the objects in that list
// - If `content` is an OpenShift template, load().get() will get the objects in the template
// with parameters substituted (e.g. with default values).
list = clientFactory.create().load(new ByteArrayInputStream(content.getBytes())).get();
} catch (KubernetesClientException e) {
// KubernetesClient wraps the error when a JsonMappingException occurs so we need the cause
String message = e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
if (message.contains("\n")) {
// Clean up message if it comes from JsonMappingException. Format is e.g.
// `No resource type found for:v1#Route1\n at [...]`
message = message.split("\\n", 2)[0];
}
throw new ValidationException(format("Could not parse OpenShift recipe: %s", message));
}
final List<HasMetadata> list = k8sObjectsParser.parse(recipe);
Map<String, Pod> pods = new HashMap<>();
Map<String, Deployment> deployments = new HashMap<>();
@ -125,9 +98,6 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
checkNotNull(object.getMetadata(), "%s metadata must not be null", object.getKind());
checkNotNull(object.getMetadata().getName(), "%s name must not be null", object.getKind());
// needed because Che master namespace is set by K8s API during list loading
object.getMetadata().setNamespace(null);
if (object instanceof DeploymentConfig) {
throw new ValidationException("Supporting of deployment configs is not implemented yet.");
} else if (object instanceof Pod) {
@ -159,8 +129,6 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
}
}
addRamAttributes(machines, pods.values());
OpenShiftEnvironment osEnv =
OpenShiftEnvironment.builder()
.setInternalRecipe(recipe)
@ -175,14 +143,16 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
.setRoutes(routes)
.build();
addRamAttributes(osEnv.getMachines(), osEnv.getPodsData().values());
envValidator.validate(osEnv);
return osEnv;
}
@VisibleForTesting
void addRamAttributes(Map<String, InternalMachineConfig> machines, Collection<Pod> pods) {
for (Pod pod : pods) {
void addRamAttributes(Map<String, InternalMachineConfig> machines, Collection<PodData> pods) {
for (PodData pod : pods) {
for (Container container : pod.getSpec().getContainers()) {
final String machineName = Names.machineName(pod, container);
InternalMachineConfig machineConfig;

View File

@ -48,12 +48,8 @@ import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.ParameterNamespaceListVisitFromServerGetDeleteRecreateWaitApplicable;
import io.fabric8.openshift.api.model.Route;
import io.fabric8.openshift.api.model.RouteBuilder;
import io.fabric8.openshift.client.OpenShiftClient;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@ -62,7 +58,8 @@ import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfi
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.api.workspace.server.spi.environment.MemoryAttributeProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
@ -77,36 +74,26 @@ import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class OpenShiftEnvironmentFactoryTest {
private static final String YAML_RECIPE = "application/x-yaml";
private static final long BYTES_IN_MB = 1024 * 1024;
private static final String MACHINE_NAME_1 = "machine1";
private static final String MACHINE_NAME_2 = "machine2";
private OpenShiftEnvironmentFactory osEnvFactory;
@Mock private OpenShiftClientFactory clientFactory;
@Mock private OpenShiftEnvironmentValidator openShiftEnvValidator;
@Mock private OpenShiftClient client;
@Mock private InternalRecipe internalRecipe;
@Mock private InternalMachineConfig machineConfig1;
@Mock private InternalMachineConfig machineConfig2;
@Mock private MemoryAttributeProvisioner memoryProvisioner;
@Mock private KubernetesRecipeParser k8sRecipeParser;
private Map<String, InternalMachineConfig> machines;
@Mock
private ParameterNamespaceListVisitFromServerGetDeleteRecreateWaitApplicable<HasMetadata, Boolean>
loadedRecipe;
@BeforeMethod
public void setup() throws Exception {
osEnvFactory =
new OpenShiftEnvironmentFactory(
null, null, null, clientFactory, openShiftEnvValidator, memoryProvisioner);
when(clientFactory.create()).thenReturn(client);
when(client.load(any(InputStream.class))).thenReturn(loadedRecipe);
when(internalRecipe.getContentType()).thenReturn(YAML_RECIPE);
when(internalRecipe.getContent()).thenReturn("recipe content");
null, null, null, openShiftEnvValidator, k8sRecipeParser, memoryProvisioner);
machines = ImmutableMap.of(MACHINE_NAME_1, machineConfig1, MACHINE_NAME_2, machineConfig2);
}
@ -117,7 +104,7 @@ public class OpenShiftEnvironmentFactoryTest {
new ServiceBuilder().withNewMetadata().withName("service1").endMetadata().build();
Service service2 =
new ServiceBuilder().withNewMetadata().withName("service2").endMetadata().build();
when(loadedRecipe.get()).thenReturn(asList(service1, service2));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(service1, service2));
// when
KubernetesEnvironment osEnv = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
@ -135,7 +122,7 @@ public class OpenShiftEnvironmentFactoryTest {
new PersistentVolumeClaimBuilder().withNewMetadata().withName("pvc1").endMetadata().build();
PersistentVolumeClaim pvc2 =
new PersistentVolumeClaimBuilder().withNewMetadata().withName("pvc2").endMetadata().build();
when(loadedRecipe.get()).thenReturn(asList(pvc1, pvc2));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(pvc1, pvc2));
// when
OpenShiftEnvironment osEnv = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
@ -149,7 +136,7 @@ public class OpenShiftEnvironmentFactoryTest {
@Test
public void addRoutesWhenRecipeContainsThem() throws Exception {
Route route = new RouteBuilder().withNewMetadata().withName("test-route").endMetadata().build();
when(loadedRecipe.get()).thenReturn(singletonList(route));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(route));
final OpenShiftEnvironment parsed =
osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
@ -164,7 +151,7 @@ public class OpenShiftEnvironmentFactoryTest {
public void addSecretsWhenRecipeContainsThem() throws Exception {
Secret secret =
new SecretBuilder().withNewMetadata().withName("test-secret").endMetadata().build();
when(loadedRecipe.get()).thenReturn(singletonList(secret));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(secret));
final OpenShiftEnvironment parsed =
osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
@ -179,7 +166,7 @@ public class OpenShiftEnvironmentFactoryTest {
public void addConfigMapsWhenRecipeContainsThem() throws Exception {
ConfigMap configMap =
new ConfigMapBuilder().withNewMetadata().withName("test-configmap").endMetadata().build();
when(loadedRecipe.get()).thenReturn(singletonList(configMap));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(configMap));
final KubernetesEnvironment parsed =
osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
@ -200,7 +187,7 @@ public class OpenShiftEnvironmentFactoryTest {
.endMetadata()
.withSpec(new PodSpec())
.build();
when(loadedRecipe.get()).thenReturn(singletonList(pod));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(pod));
// when
KubernetesEnvironment osEnv = osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
@ -235,7 +222,7 @@ public class OpenShiftEnvironmentFactoryTest {
.endSpec()
.build();
when(loadedRecipe.get()).thenReturn(singletonList(deployment));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(deployment));
// when
final KubernetesEnvironment osEnv =
@ -279,7 +266,7 @@ public class OpenShiftEnvironmentFactoryTest {
.withNewSpec()
.endSpec()
.build();
when(loadedRecipe.get()).thenReturn(asList(deployment, pod));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(asList(deployment, pod));
// when
final KubernetesEnvironment osEnv =
@ -298,7 +285,8 @@ public class OpenShiftEnvironmentFactoryTest {
@Test(expectedExceptions = ValidationException.class)
public void exceptionOnRecipeLoadError() throws Exception {
when(loadedRecipe.get()).thenThrow(new KubernetesClientException("Could not parse recipe"));
when(k8sRecipeParser.parse(any(InternalRecipe.class)))
.thenThrow(new ValidationException("Could not parse recipe"));
osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
}
@ -309,7 +297,7 @@ public class OpenShiftEnvironmentFactoryTest {
public void exceptionOnObjectWithNoKindSpecified() throws Exception {
HasMetadata object = mock(HasMetadata.class);
when(object.getKind()).thenReturn(null);
when(loadedRecipe.get()).thenReturn(singletonList(object));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object));
osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
}
@ -321,7 +309,7 @@ public class OpenShiftEnvironmentFactoryTest {
HasMetadata object = mock(HasMetadata.class);
when(object.getKind()).thenReturn("MyObject");
when(object.getMetadata()).thenReturn(null);
when(loadedRecipe.get()).thenReturn(singletonList(object));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object));
osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
}
@ -333,7 +321,7 @@ public class OpenShiftEnvironmentFactoryTest {
HasMetadata object = mock(HasMetadata.class);
when(object.getKind()).thenReturn("MyObject");
when(object.getMetadata()).thenReturn(new ObjectMetaBuilder().withName(null).build());
when(loadedRecipe.get()).thenReturn(singletonList(object));
when(k8sRecipeParser.parse(any(InternalRecipe.class))).thenReturn(singletonList(object));
osEnvFactory.doCreate(internalRecipe, emptyMap(), emptyList());
}
@ -346,10 +334,10 @@ public class OpenShiftEnvironmentFactoryTest {
final long secondMachineRamRequest = 512 * BYTES_IN_MB;
when(machineConfig1.getAttributes()).thenReturn(new HashMap<>());
when(machineConfig2.getAttributes()).thenReturn(new HashMap<>());
final Set<Pod> pods =
final Set<PodData> pods =
ImmutableSet.of(
mockPod(MACHINE_NAME_1, firstMachineRamLimit, firstMachineRamRequest),
mockPod(MACHINE_NAME_2, secondMachineRamLimit, secondMachineRamRequest));
createPodData(MACHINE_NAME_1, firstMachineRamLimit, firstMachineRamRequest),
createPodData(MACHINE_NAME_2, secondMachineRamLimit, secondMachineRamRequest));
osEnvFactory.addRamAttributes(machines, pods);
@ -360,7 +348,7 @@ public class OpenShiftEnvironmentFactoryTest {
}
/** If provided {@code ramLimit} is {@code null} ram limit won't be set in POD */
private static Pod mockPod(String machineName, Long ramLimit, Long ramRequest) {
private static PodData createPodData(String machineName, Long ramLimit, Long ramRequest) {
final String containerName = "container_" + machineName;
final Container containerMock = mock(Container.class);
final Pod podMock = mock(Pod.class);
@ -385,6 +373,6 @@ public class OpenShiftEnvironmentFactoryTest {
.thenReturn(
ImmutableMap.of(format(MACHINE_NAME_ANNOTATION_FMT, containerName), machineName));
when(specMock.getContainers()).thenReturn(ImmutableList.of(containerMock));
return podMock;
return new PodData(specMock, metadataMock);
}
}

View File

@ -19,4 +19,8 @@ public class DevfileRecipeFormatException extends DevfileException {
public DevfileRecipeFormatException(String message) {
super(message);
}
public DevfileRecipeFormatException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -15,6 +15,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import static java.util.Collections.emptyMap;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static org.eclipse.che.api.core.model.workspace.config.Command.MACHINE_NAME_ATTRIBUTE;
import static org.eclipse.che.api.devfile.server.Constants.KUBERNETES_TOOL_TYPE;
@ -22,14 +23,16 @@ import static org.eclipse.che.api.devfile.server.Constants.OPENSHIFT_TOOL_TYPE;
import com.google.common.annotations.VisibleForTesting;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesList;
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.utils.Serialization;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.eclipse.che.api.core.model.workspace.WorkspaceConfig;
import org.eclipse.che.api.core.model.workspace.config.Command;
import org.eclipse.che.api.devfile.model.Tool;
@ -43,6 +46,7 @@ import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.workspace.infrastructure.kubernetes.Names;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser;
/**
* Applies changes on workspace config according to the specified kubernetes/openshift tool.
@ -53,6 +57,13 @@ public class KubernetesToolToWorkspaceApplier implements ToolToWorkspaceApplier
@VisibleForTesting static final String YAML_CONTENT_TYPE = "application/x-yaml";
private final KubernetesRecipeParser objectsParser;
@Inject
public KubernetesToolToWorkspaceApplier(KubernetesRecipeParser objectsParser) {
this.objectsParser = objectsParser;
}
/**
* Applies changes on workspace config according to the specified kubernetes/openshift tool.
*
@ -79,10 +90,10 @@ public class KubernetesToolToWorkspaceApplier implements ToolToWorkspaceApplier
String recipeFileContent = retrieveContent(k8sTool, contentProvider);
final KubernetesList list = unmarshal(k8sTool, recipeFileContent);
List<HasMetadata> list = unmarshal(k8sTool, recipeFileContent);
if (!k8sTool.getSelector().isEmpty()) {
list.setItems(filter(list, k8sTool.getSelector()));
list = filter(list, k8sTool.getSelector());
}
estimateCommandsMachineName(workspaceConfig, k8sTool, list);
@ -128,8 +139,16 @@ public class KubernetesToolToWorkspaceApplier implements ToolToWorkspaceApplier
return recipeFileContent;
}
private List<HasMetadata> filter(KubernetesList list, Map<String, String> selector) {
return list.getItems().stream().filter(item -> matchLabels(item, selector)).collect(toList());
/**
* Returns a lists consisting of the elements of the specified list that match the given selector.
*
* @param list list to filter
* @param selector selector that should be matched with objects' labels
*/
private List<HasMetadata> filter(List<HasMetadata> list, Map<String, String> selector) {
return list.stream()
.filter(item -> matchLabels(item, selector))
.collect(toCollection(ArrayList::new));
}
/**
@ -160,7 +179,7 @@ public class KubernetesToolToWorkspaceApplier implements ToolToWorkspaceApplier
* <p>Machine name will be set only if the specified recipe objects has the only one container.
*/
private void estimateCommandsMachineName(
WorkspaceConfig workspaceConfig, Tool tool, KubernetesList recipeObjects) {
WorkspaceConfig workspaceConfig, Tool tool, List<HasMetadata> recipeObjects) {
List<? extends Command> toolCommands =
workspaceConfig
.getCommands()
@ -175,7 +194,6 @@ public class KubernetesToolToWorkspaceApplier implements ToolToWorkspaceApplier
}
List<Pod> pods =
recipeObjects
.getItems()
.stream()
.filter(hasMetadata -> hasMetadata instanceof Pod)
.map(hasMetadata -> (Pod) hasMetadata)
@ -192,11 +210,11 @@ public class KubernetesToolToWorkspaceApplier implements ToolToWorkspaceApplier
toolCommands.forEach(c -> c.getAttributes().put(MACHINE_NAME_ATTRIBUTE, machineName));
}
private KubernetesList unmarshal(Tool tool, String recipeContent)
private List<HasMetadata> unmarshal(Tool tool, String recipeContent)
throws DevfileRecipeFormatException {
try {
return Serialization.unmarshal(recipeContent, KubernetesList.class);
} catch (KubernetesClientException e) {
return objectsParser.parse(recipeContent);
} catch (Exception e) {
throw new DevfileRecipeFormatException(
format(
"Error occurred during parsing list from file %s for tool '%s': %s",
@ -204,14 +222,15 @@ public class KubernetesToolToWorkspaceApplier implements ToolToWorkspaceApplier
}
}
private String asYaml(Tool tool, KubernetesList list) throws DevfileRecipeFormatException {
private String asYaml(Tool tool, List<HasMetadata> list) throws DevfileRecipeFormatException {
try {
return Serialization.asYaml(list);
return Serialization.asYaml(new KubernetesListBuilder().withItems(list).build());
} catch (KubernetesClientException e) {
throw new DevfileRecipeFormatException(
format(
"Unable to deserialize specified local file content for tool '%s'. Error: %s",
tool.getName(), e.getMessage()));
tool.getName(), e.getMessage()),
e);
}
}
}

View File

@ -14,7 +14,7 @@ package org.eclipse.che.api.devfile.server.exception;
/** Describes general devfile exception. */
public class DevfileException extends Exception {
public DevfileException(String message, Exception cause) {
public DevfileException(String message, Throwable cause) {
super(message, cause);
}

View File

@ -18,16 +18,21 @@ import static org.eclipse.che.api.devfile.server.Constants.KUBERNETES_TOOL_TYPE;
import static org.eclipse.che.api.devfile.server.Constants.OPENSHIFT_TOOL_TYPE;
import static org.eclipse.che.api.devfile.server.Constants.TOOL_NAME_COMMAND_ATTRIBUTE;
import static org.eclipse.che.api.devfile.server.convert.tool.kubernetes.KubernetesToolToWorkspaceApplier.YAML_CONTENT_TYPE;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesList;
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.devfile.model.Tool;
import org.eclipse.che.api.devfile.server.FileContentProvider.FetchNotSupportedProvider;
import org.eclipse.che.api.devfile.server.exception.DevfileException;
@ -35,11 +40,16 @@ import org.eclipse.che.api.workspace.server.model.impl.CommandImpl;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesRecipeParser;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import org.testng.reporters.Files;
/** @author Sergii Leshchenko */
@Listeners(MockitoTestNGListener.class)
public class KubernetesToolToWorkspaceApplierTest {
public static final String LOCAL_FILENAME = "local.yaml";
@ -48,10 +58,11 @@ public class KubernetesToolToWorkspaceApplierTest {
private WorkspaceConfigImpl workspaceConfig;
private KubernetesToolToWorkspaceApplier applier;
@Mock private KubernetesRecipeParser k8sRecipeParser;
@BeforeMethod
public void setUp() {
applier = new KubernetesToolToWorkspaceApplier();
applier = new KubernetesToolToWorkspaceApplier(k8sRecipeParser);
workspaceConfig = new WorkspaceConfigImpl();
}
@ -86,6 +97,7 @@ public class KubernetesToolToWorkspaceApplierTest {
+ "': .*")
public void shouldThrowExceptionWhenRecipeContentIsNotAValidYaml() throws Exception {
// given
doThrow(new ValidationException("non valid")).when(k8sRecipeParser).parse(anyString());
Tool tool =
new Tool().withType(KUBERNETES_TOOL_TYPE).withLocal(LOCAL_FILENAME).withName(TOOL_NAME);
@ -116,6 +128,7 @@ public class KubernetesToolToWorkspaceApplierTest {
throws Exception {
// given
String yamlRecipeContent = getResource("petclinic.yaml");
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
Tool tool =
new Tool()
.withType(KUBERNETES_TOOL_TYPE)
@ -135,12 +148,17 @@ public class KubernetesToolToWorkspaceApplierTest {
assertNotNull(recipe);
assertEquals(recipe.getType(), KUBERNETES_TOOL_TYPE);
assertEquals(recipe.getContentType(), YAML_CONTENT_TYPE);
assertEquals(toK8SList(recipe.getContent()), toK8SList(yamlRecipeContent));
// it is expected that applier wrap original recipes objects in new Kubernetes list
KubernetesList expectedKubernetesList =
new KubernetesListBuilder().withItems(toK8SList(yamlRecipeContent).getItems()).build();
assertEquals(toK8SList(recipe.getContent()).getItems(), expectedKubernetesList.getItems());
}
@Test
public void shouldUseLocalContentAsRecipeIfPresent() throws Exception {
String yamlRecipeContent = getResource("petclinic.yaml");
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
Tool tool =
new Tool()
.withType(KUBERNETES_TOOL_TYPE)
@ -159,7 +177,11 @@ public class KubernetesToolToWorkspaceApplierTest {
assertNotNull(recipe);
assertEquals(recipe.getType(), KUBERNETES_TOOL_TYPE);
assertEquals(recipe.getContentType(), YAML_CONTENT_TYPE);
assertEquals(toK8SList(recipe.getContent()), toK8SList(yamlRecipeContent));
// it is expected that applier wrap original recipes objects in new Kubernetes list
KubernetesList expectedKubernetesList =
new KubernetesListBuilder().withItems(toK8SList(yamlRecipeContent).getItems()).build();
assertEquals(toK8SList(recipe.getContent()).getItems(), expectedKubernetesList.getItems());
}
@Test
@ -167,6 +189,7 @@ public class KubernetesToolToWorkspaceApplierTest {
throws Exception {
// given
String yamlRecipeContent = getResource("petclinic.yaml");
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
Tool tool =
new Tool()
.withType(OPENSHIFT_TOOL_TYPE)
@ -186,7 +209,11 @@ public class KubernetesToolToWorkspaceApplierTest {
assertNotNull(recipe);
assertEquals(recipe.getType(), OPENSHIFT_TOOL_TYPE);
assertEquals(recipe.getContentType(), YAML_CONTENT_TYPE);
assertEquals(toK8SList(recipe.getContent()), toK8SList(yamlRecipeContent));
// it is expected that applier wrap original recipes objects in new Kubernetes list
KubernetesList expectedKubernetesList =
new KubernetesListBuilder().withItems(toK8SList(yamlRecipeContent).getItems()).build();
assertEquals(toK8SList(recipe.getContent()).getItems(), expectedKubernetesList.getItems());
}
@Test
@ -201,6 +228,7 @@ public class KubernetesToolToWorkspaceApplierTest {
.withLocal(LOCAL_FILENAME)
.withName(TOOL_NAME)
.withSelector(selector);
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
// when
applier.apply(workspaceConfig, tool, s -> yamlRecipeContent);
@ -224,6 +252,7 @@ public class KubernetesToolToWorkspaceApplierTest {
throws Exception {
// given
String yamlRecipeContent = getResource("petclinic.yaml");
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
final Map<String, String> selector = singletonMap("app.kubernetes.io/component", "webapp");
Tool tool =
@ -250,6 +279,7 @@ public class KubernetesToolToWorkspaceApplierTest {
throws Exception {
// given
String yamlRecipeContent = getResource("petclinic.yaml");
doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString());
Tool tool =
new Tool()