KubernetesComponentToWorkspaceApplier implements ComponentToWorkspaceApplier { @Override public void apply( WorkspaceConfigImpl workspaceConfig, ComponentImpl k8sComponent, FileContentProvider contentProvider) throws DevfileException { checkArgument(workspaceConfig != null, "Workspace config must not be null"); checkArgument(k8sComponent != null, "Component must not be null"); checkArgument( kubernetesBasedComponentTypes.contains(k8sComponent.getType()), format("Plugin must have %s type", String.join(" or ", kubernetesBasedComponentTypes))); String componentContent = retrieveContent(k8sComponent, contentProvider); final List<HasMetadata> componentObjects = prepareComponentObjects(k8sComponent, componentContent); List<PodData> podsData = getPodDatas(componentObjects); podsData .stream() .flatMap(e -> e.getSpec().getContainers().stream()) .forEach(c -> c.setImagePullPolicy(imagePullPolicy)); if (Boolean.TRUE.equals(k8sComponent.getMountSources())) { applyProjectsVolumes(podsData, componentObjects); } if (!k8sComponent.getEnv().isEmpty()) { podsData.forEach(p -> envVars.apply(p, k8sComponent.getEnv())); } Map<String, MachineConfigImpl> machineConfigs = prepareMachineConfigs(podsData, k8sComponent); linkCommandsToMachineName(workspaceConfig, k8sComponent, machineConfigs.keySet()); k8sEnvProvisioner.provision(workspaceConfig, environmentType, componentObjects, machineConfigs); } @Inject  KubernetesComponentToWorkspaceApplier(
      KubernetesRecipeParser objectsParser,
      KubernetesEnvironmentProvisioner k8sEnvProvisioner,
      EnvVars envVars,
      @Named("che.workspace.projects.storage") String projectFolderPath,
      @Named("che.workspace.projects.storage.default.size") String defaultProjectPVCSize,
      @Named("che.infra.kubernetes.pvc.access_mode") String defaultPVCAccessMode,
      @Named("che.infra.kubernetes.pvc.storage_class_name") String pvcStorageClassName,
      @Named("che.workspace.sidecar.image_pull_policy") String imagePullPolicy,
      @Named(KUBERNETES_BASED_COMPONENTS_KEY_NAME) Set<String> kubernetesBasedComponentTypes); protected  KubernetesComponentToWorkspaceApplier(
      KubernetesRecipeParser objectsParser,
      KubernetesEnvironmentProvisioner k8sEnvProvisioner,
      EnvVars envVars,
      String environmentType,
      String projectFolderPath,
      String defaultProjectPVCSize,
      String defaultPVCAccessMode,
      String pvcStorageClassName,
      String imagePullPolicy,
      Set<String> kubernetesBasedComponentTypes); @Override void apply(
      WorkspaceConfigImpl workspaceConfig,
      ComponentImpl k8sComponent,
      FileContentProvider contentProvider);  }
@Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Fetching content of file `reference.yaml` specified in `reference` field of component `foo` is not " + "supported. Please provide its content in `referenceContent` field. Cause: fetch is not supported") public void shouldThrowExceptionWhenRecipeComponentIsPresentAndContentProviderDoesNotSupportFetching() throws Exception { ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); applier.apply( workspaceConfig, component, e -> { throw new DevfileException("fetch is not supported"); }); }
@Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Error occurred during parsing list from file " + REFERENCE_FILENAME + " for component '" + COMPONENT_NAME + "': .*") public void shouldThrowExceptionWhenRecipeContentIsNotAValidYaml() throws Exception { doThrow(new ValidationException("non valid")).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); applier.apply(workspaceConfig, component, s -> "some_non_yaml_content"); }
@Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Error during recipe content retrieval for component 'foo' with type 'kubernetes': fetch failed") public void shouldThrowExceptionWhenExceptionHappensOnContentProvider() throws Exception { ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); applier.apply( workspaceConfig, component, e -> { throw new IOException("fetch failed"); }); }
@Test public void shouldProvisionEnvironmentWithCorrectRecipeTypeAndContentFromK8SList() throws Exception { String yamlRecipeContent = getResource("devfile/petclinic.yaml"); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); applier.apply(workspaceConfig, component, s -> yamlRecipeContent); KubernetesList list = toK8SList(yamlRecipeContent); replaceImagePullPolicy(list, "Always"); verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), eq(list.getItems()), anyMap()); }
@Test public void shouldUseReferenceContentAsRecipeIfPresent() throws Exception { String yamlRecipeContent = getResource("devfile/petclinic.yaml"); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setReferenceContent(yamlRecipeContent); component.setAlias(COMPONENT_NAME); applier.apply(workspaceConfig, component, new URLFileContentProvider(null, null)); KubernetesList list = toK8SList(yamlRecipeContent); replaceImagePullPolicy(list, "Always"); verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), eq(list.getItems()), anyMap()); }
@Test public void shouldProvisionProjectVolumesIfSpecifiedIntoK8SList() throws Exception { String yamlRecipeContent = getResource("devfile/petclinic.yaml"); List<HasMetadata> k8sList = toK8SList(yamlRecipeContent).getItems(); doReturn(k8sList).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setMountSources(true); applier.apply(workspaceConfig, component, s -> yamlRecipeContent); verify(k8sEnvProvisioner).provision(any(), any(), objectsCaptor.capture(), any()); List<HasMetadata> list = objectsCaptor.getValue(); assertTrue( list.stream() .filter(hasMeta -> hasMeta instanceof PersistentVolumeClaim) .map(o -> (PersistentVolumeClaim) o) .anyMatch(claim -> claim.getMetadata().getName().equals(PROJECTS_VOLUME_NAME))); for (HasMetadata o : list) { if (o instanceof Pod) { Pod p = (Pod) o; if (p.getSpec() == null) { continue; } assertTrue( p.getSpec() .getVolumes() .stream() .anyMatch( v -> v.getName().equals(PROJECTS_VOLUME_NAME) && v.getPersistentVolumeClaim() .getClaimName() .equals(PROJECTS_VOLUME_NAME))); for (Container c : p.getSpec().getContainers()) { assertEquals(c.getImagePullPolicy(), "Always"); assertTrue( c.getVolumeMounts() .stream() .anyMatch( vm -> vm.getName().equals(PROJECTS_VOLUME_NAME) && vm.getMountPath().equals(PROJECT_MOUNT_PATH))); } } } }
@Test public void shouldProvisionDevfileVolumesIfSpecifiedIntoMachineConfig() throws Exception { String yamlRecipeContent = getResource("devfile/petclinic.yaml"); List<HasMetadata> k8sList = toK8SList(yamlRecipeContent).getItems(); doReturn(k8sList).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setVolumes(asList(new VolumeImpl("foo", "/foo1"), new VolumeImpl("bar", "/bar1"))); ArgumentCaptor<Map<String, MachineConfigImpl>> mapCaptor = ArgumentCaptor.forClass(Map.class); applier.apply(workspaceConfig, component, s -> yamlRecipeContent); verify(k8sEnvProvisioner).provision(any(), any(), any(), mapCaptor.capture()); Map<String, MachineConfigImpl> configMaps = mapCaptor.getValue(); for (MachineConfig config : configMaps.values()) { assertEquals(config.getVolumes().size(), 2); assertTrue( config .getVolumes() .entrySet() .stream() .anyMatch( entry -> entry.getKey().equals("foo") && entry.getValue().getPath().equals("/foo1"))); assertTrue( config .getVolumes() .entrySet() .stream() .anyMatch( entry -> entry.getKey().equals("bar") && entry.getValue().getPath().equals("/bar1"))); } }
@Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Conflicting volume with same name \\('foo_volume'\\) but different path \\('/foo1'\\) found for component 'foo' and its container 'server'.") public void shouldThrowExceptionWhenDevfileVolumeNameExists() throws Exception { String yamlRecipeContent = getResource("devfile/petclinic.yaml"); List<HasMetadata> k8sList = toK8SList(yamlRecipeContent).getItems(); doReturn(k8sList).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setVolumes(Collections.singletonList(new VolumeImpl("foo_volume", "/foo1"))); applier.apply(workspaceConfig, component, s -> yamlRecipeContent); }
@Test( expectedExceptions = DevfileException.class, expectedExceptionsMessageRegExp = "Conflicting volume with same path \\('/foo/bar'\\) but different name \\('foo'\\) found for component 'foo' and its container 'server'.") public void shouldThrowExceptionWhenDevfileVolumePathExists() throws Exception { String yamlRecipeContent = getResource("devfile/petclinic.yaml"); List<HasMetadata> k8sList = toK8SList(yamlRecipeContent).getItems(); doReturn(k8sList).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setVolumes(Collections.singletonList(new VolumeImpl("foo", "/foo/bar"))); applier.apply(workspaceConfig, component, s -> yamlRecipeContent); }
@Test public void shouldProvisionEnvIntoK8SList() throws Exception { List<HasMetadata> k8sList = new ArrayList<>(); Pod pod1 = new PodBuilder() .withNewMetadata() .withName("pod1") .endMetadata() .withNewSpec() .endSpec() .build(); Pod pod2 = new PodBuilder() .withNewMetadata() .withName("pod2") .endMetadata() .withNewSpec() .endSpec() .build(); k8sList.add(pod1); k8sList.add(pod2); doReturn(k8sList).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); List<EnvImpl> envToApply = singletonList(new EnvImpl("TEST_ENV", "anyValue")); component.setEnv(envToApply); applier.apply(workspaceConfig, component, s -> "content"); envVars.apply(new PodData(pod1), envToApply); envVars.apply(new PodData(pod2), envToApply); }
@Test public void shouldProvisionMachinesMapWithComponentAttributePreSet() throws Exception { @SuppressWarnings("unchecked") ArgumentCaptor<Map<String, MachineConfigImpl>> mapCaptor = ArgumentCaptor.forClass(Map.class); String yamlRecipeContent = getResource("devfile/petclinic.yaml"); List<HasMetadata> k8sList = toK8SList(yamlRecipeContent).getItems(); doReturn(k8sList).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setMountSources(true); applier.apply(workspaceConfig, component, s -> yamlRecipeContent); verify(k8sEnvProvisioner).provision(any(), any(), any(), mapCaptor.capture()); Map<String, MachineConfigImpl> machines = mapCaptor.getValue(); assertTrue( machines .values() .stream() .allMatch( config -> config .getAttributes() .get(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE) .equals(component.getAlias()))); }
@Test public void shouldFilterRecipeWithGivenSelectors() throws Exception { String yamlRecipeContent = getResource("devfile/petclinic.yaml"); final Map<String, String> selector = singletonMap("app.kubernetes.io/component", "webapp"); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setSelector(selector); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); applier.apply(workspaceConfig, component, s -> yamlRecipeContent); verify(k8sEnvProvisioner) .provision( eq(workspaceConfig), eq(KubernetesEnvironment.TYPE), objectsCaptor.capture(), anyMap()); List<HasMetadata> resultItemsList = objectsCaptor.getValue(); assertEquals(resultItemsList.size(), 3); assertEquals(1, resultItemsList.stream().filter(it -> "Pod".equals(it.getKind())).count()); assertEquals(1, resultItemsList.stream().filter(it -> "Service".equals(it.getKind())).count()); assertEquals(1, resultItemsList.stream().filter(it -> "Route".equals(it.getKind())).count()); }
@Test(dependsOnMethods = "shouldFilterRecipeWithGivenSelectors", enabled = false) public void shouldSetMachineNameAttributeToCommandConfiguredInOpenShiftComponentWithOneContainer() throws Exception { String yamlRecipeContent = getResource("devfile/petclinic.yaml"); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); final Map<String, String> selector = singletonMap("app.kubernetes.io/component", "webapp"); ComponentImpl component = new ComponentImpl(); component.setType(OPENSHIFT_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setSelector(selector); CommandImpl command = new CommandImpl(); command.getAttributes().put(COMPONENT_ALIAS_COMMAND_ATTRIBUTE, COMPONENT_NAME); workspaceConfig.getCommands().add(command); applier.apply(workspaceConfig, component, s -> yamlRecipeContent); CommandImpl actualCommand = workspaceConfig.getCommands().get(0); assertEquals(actualCommand.getAttributes().get(MACHINE_NAME_ATTRIBUTE), "petclinic/server"); }
@Test public void shouldNotSetMachineNameAttributeToCommandConfiguredInOpenShiftComponentWithMultipleContainers() throws Exception { String yamlRecipeContent = getResource("devfile/petclinic.yaml"); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(OPENSHIFT_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); CommandImpl command = new CommandImpl(); command.getAttributes().put(COMPONENT_ALIAS_COMMAND_ATTRIBUTE, COMPONENT_NAME); workspaceConfig.getCommands().add(command); applier.apply(workspaceConfig, component, s -> yamlRecipeContent); CommandImpl actualCommand = workspaceConfig.getCommands().get(0); assertNull(actualCommand.getAttributes().get(MACHINE_NAME_ATTRIBUTE)); }
@Test public void shouldChangeEntrypointsOnMatchingContainers() throws Exception { String yamlRecipeContent = getResource("devfile/petclinic.yaml"); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); List<String> command = asList("teh", "command"); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); EntrypointImpl entrypoint = new EntrypointImpl(); entrypoint.setParentName("petclinic"); entrypoint.setCommand(command); component.setEntrypoints(singletonList(entrypoint)); applier.apply(workspaceConfig, component, s -> yamlRecipeContent); verify(k8sEnvProvisioner).provision(any(), any(), objectsCaptor.capture(), any()); List<HasMetadata> list = objectsCaptor.getValue(); for (HasMetadata o : list) { if (o instanceof Pod) { Pod p = (Pod) o; if (p.getSpec() == null) { continue; } Container c = p.getSpec().getContainers().get(0); if (o.getMetadata().getName().equals("petclinic")) { assertEquals(c.getCommand(), command); } else { assertTrue(c.getCommand() == null || c.getCommand().isEmpty()); } } } }
@Test public void shouldBeAbleToOverrideImagePullPolicy() throws Exception { applier = new KubernetesComponentToWorkspaceApplier( k8sRecipeParser, k8sEnvProvisioner, envVars, PROJECT_MOUNT_PATH, "1Gi", "ReadWriteOnce", "", "Never", k8sBasedComponents); String yamlRecipeContent = getResource("devfile/petclinic.yaml"); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); applier.apply(workspaceConfig, component, s -> yamlRecipeContent); verify(k8sEnvProvisioner).provision(any(), any(), objectsCaptor.capture(), any()); List<HasMetadata> list = objectsCaptor.getValue(); for (HasMetadata o : list) { if (o instanceof Pod) { Pod p = (Pod) o; if (p.getSpec() == null) { continue; } for (Container con : p.getSpec().getContainers()) { assertEquals(con.getImagePullPolicy(), "Never"); } } } }
@Test public void shouldProvisionEndpointsToAllMachines() throws IOException, ValidationException, InfrastructureException, DevfileException { String endpointName = "petclinic-endpoint"; Integer endpointPort = 8081; String yamlRecipeContent = getResource("devfile/petclinicPods.yaml"); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setEndpoints(singletonList(new EndpointImpl(endpointName, endpointPort, emptyMap()))); applier.apply(workspaceConfig, component, s -> yamlRecipeContent); @SuppressWarnings("unchecked") ArgumentCaptor<Map<String, MachineConfigImpl>> objectsCaptor = ArgumentCaptor.forClass(Map.class); verify(k8sEnvProvisioner).provision(any(), any(), any(), objectsCaptor.capture()); Map<String, MachineConfigImpl> configs = objectsCaptor.getValue(); assertEquals(configs.size(), 3); configs .values() .forEach( machineConfig -> { Map<String, ServerConfigImpl> serverConfigs = machineConfig.getServers(); assertEquals(serverConfigs.size(), 1); assertTrue(serverConfigs.containsKey(endpointName)); assertEquals(serverConfigs.get(endpointName).getPort(), endpointPort.toString()); assertNull(serverConfigs.get(endpointName).getPath()); }); }
@Test public void shouldProvisionEndpointWithAttributes() throws IOException, ValidationException, InfrastructureException, DevfileException { String endpointName = "petclinic-endpoint"; Integer endpointPort = 8081; String endpointProtocol = "tcp"; String endpointPath = "path"; String endpointPublic = "false"; String endpointSecure = "false"; String yamlRecipeContent = getResource("devfile/petclinicPods.yaml"); doReturn(toK8SList(yamlRecipeContent).getItems()).when(k8sRecipeParser).parse(anyString()); ComponentImpl component = new ComponentImpl(); component.setType(KUBERNETES_COMPONENT_TYPE); component.setReference(REFERENCE_FILENAME); component.setAlias(COMPONENT_NAME); component.setEndpoints( singletonList( new EndpointImpl( endpointName, endpointPort, ImmutableMap.of( "protocol", endpointProtocol, "path", endpointPath, "public", endpointPublic, "secure", endpointSecure)))); applier.apply(workspaceConfig, component, s -> yamlRecipeContent); @SuppressWarnings("unchecked") ArgumentCaptor<Map<String, MachineConfigImpl>> objectsCaptor = ArgumentCaptor.forClass(Map.class); verify(k8sEnvProvisioner).provision(any(), any(), any(), objectsCaptor.capture()); Map<String, MachineConfigImpl> configs = objectsCaptor.getValue(); assertEquals(configs.size(), 3); configs .values() .forEach( machineConfig -> { Map<String, ServerConfigImpl> serverConfigs = machineConfig.getServers(); assertEquals(serverConfigs.size(), 1); assertTrue(serverConfigs.containsKey(endpointName)); assertEquals(serverConfigs.get(endpointName).getPort(), endpointPort.toString()); assertEquals(serverConfigs.get(endpointName).getPath(), endpointPath); assertEquals(serverConfigs.get(endpointName).getProtocol(), endpointProtocol); assertEquals( serverConfigs.get(endpointName).getAttributes().get(REQUIRE_SUBDOMAIN), "true"); assertEquals( serverConfigs.get(endpointName).isSecure(), Boolean.parseBoolean(endpointSecure)); assertEquals( serverConfigs.get(endpointName).isInternal(), !Boolean.parseBoolean(endpointPublic)); }); }