Per-component override of secrets automount and paths override for file mounts;

7.20.x
Max Shaposhnik 2020-06-26 19:22:05 +03:00 committed by GitHub
parent 9bbb5eab8e
commit 3b2dbca536
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1234 additions and 375 deletions

View File

@ -149,4 +149,7 @@ public interface Component {
* type.
*/
List<? extends Endpoint> getEndpoints();
/** Indicates whether namespace secrets should be mount into containers of this component. */
Boolean getAutomountWorkspaceSecrets();
}

View File

@ -92,7 +92,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.LogWatc
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.PodLogToEventPublisher;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.WorkspaceVolumesStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.PreviewUrlCommandProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.SecretAsContainerResourceProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.SecretAsContainerResourceProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerResolver;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressPathTransformInverter;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;
@ -213,7 +213,8 @@ public class KubernetesInternalRuntime<E extends KubernetesEnvironment>
// from previous provisioners into infrastructure specific objects
kubernetesEnvironmentProvisioner.provision(context.getEnvironment(), context.getIdentity());
secretAsContainerResourceProvisioner.provision(context.getEnvironment(), namespace);
secretAsContainerResourceProvisioner.provision(
context.getEnvironment(), context.getIdentity(), namespace);
LOG.debug("Provisioning of workspace '{}' completed.", workspaceId);
volumesStrategy.prepare(

View File

@ -1,208 +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.workspace.infrastructure.kubernetes.provision;
import static com.google.common.base.MoreObjects.firstNonNull;
import static java.lang.String.format;
import static java.util.stream.Collectors.toMap;
import com.google.common.annotations.Beta;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
import io.fabric8.kubernetes.api.model.EnvVarSourceBuilder;
import io.fabric8.kubernetes.api.model.LabelSelector;
import io.fabric8.kubernetes.api.model.LabelSelectorBuilder;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretKeySelectorBuilder;
import io.fabric8.kubernetes.api.model.SecretVolumeSourceBuilder;
import io.fabric8.kubernetes.api.model.Volume;
import io.fabric8.kubernetes.api.model.VolumeBuilder;
import io.fabric8.kubernetes.api.model.VolumeMountBuilder;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.commons.lang.NameGenerator;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodRole;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace;
/**
* Finds secrets with specific labels in namespace, and mount their values as file or environment
* variable into all (or specified by "org.eclipse.che/target-container" annotation) workspace
* containers. Secrets with annotation "org.eclipse.che/mount-as=env" are mount as env variables,
* env name is read from "org.eclipse.che/env-name" annotation. Secrets which don't have
* "org.eclipse.che/mount-as=env" or having "org.eclipse.che/mount-as=file" are mounted as file in
* the folder specified by "org.eclipse.che/mount-path" annotation. Refer to che-docs for concrete
* examples.
*/
@Beta
@Singleton
public class SecretAsContainerResourceProvisioner<E extends KubernetesEnvironment> {
private static final String ANNOTATION_PREFIX = "che.eclipse.org";
static final String ANNOTATION_MOUNT_AS = ANNOTATION_PREFIX + "/" + "mount-as";
static final String ANNOTATION_TARGET_CONTAINER = ANNOTATION_PREFIX + "/" + "target-container";
static final String ANNOTATION_ENV_NAME = ANNOTATION_PREFIX + "/" + "env-name";
static final String ANNOTATION_ENV_NAME_TEMPLATE = ANNOTATION_PREFIX + "/%s_" + "env-name";
static final String ANNOTATION_MOUNT_PATH = ANNOTATION_PREFIX + "/" + "mount-path";
private final Map<String, String> secretLabels;
@Inject
public SecretAsContainerResourceProvisioner(
@Named("che.workspace.provision.secret.labels") String[] labels) {
this.secretLabels =
Arrays.stream(labels)
.map(item -> item.split("=", 2))
.collect(toMap(p -> p[0], p -> p.length == 1 ? "" : p[1]));
}
public void provision(E env, KubernetesNamespace namespace) throws InfrastructureException {
LabelSelector selector = new LabelSelectorBuilder().withMatchLabels(secretLabels).build();
for (Secret secret : namespace.secrets().get(selector)) {
String targetContainerName =
secret.getMetadata().getAnnotations().get(ANNOTATION_TARGET_CONTAINER);
String mountType = secret.getMetadata().getAnnotations().get(ANNOTATION_MOUNT_AS);
if ("env".equalsIgnoreCase(mountType)) {
mountAsEnv(env, secret, targetContainerName);
} else if ("file".equalsIgnoreCase(mountType)) {
mountAsFile(env, secret, targetContainerName);
} else {
throw new InfrastructureException(
format(
"Unable to mount secret '%s': it has missing or unknown type of the mount. Please make sure that '%s' annotation has value either 'env' or 'file'.",
secret.getMetadata().getName(), ANNOTATION_MOUNT_AS));
}
}
}
private void mountAsEnv(E env, Secret secret, String targetContainerName)
throws InfrastructureException {
for (PodData podData : env.getPodsData().values()) {
if (!podData.getRole().equals(PodRole.DEPLOYMENT)) {
continue;
}
for (Container container : podData.getSpec().getContainers()) {
if (targetContainerName != null && !container.getName().equals(targetContainerName)) {
continue;
}
for (Entry<String, String> secretDataEntry : secret.getData().entrySet()) {
final String mountEnvName = envName(secret, secretDataEntry.getKey());
container
.getEnv()
.add(
new EnvVarBuilder()
.withName(mountEnvName)
.withValueFrom(
new EnvVarSourceBuilder()
.withSecretKeyRef(
new SecretKeySelectorBuilder()
.withName(secret.getMetadata().getName())
.withKey(secretDataEntry.getKey())
.build())
.build())
.build());
}
}
}
}
private void mountAsFile(E env, Secret secret, String targetContainerName)
throws InfrastructureException {
final String mountPath = secret.getMetadata().getAnnotations().get(ANNOTATION_MOUNT_PATH);
if (mountPath == null) {
throw new InfrastructureException(
format(
"Unable to mount secret '%s': It is configured to be mounted as a file but the mount path was not specified. Please define the '%s' annotation on the secret to specify it.",
secret.getMetadata().getName(), ANNOTATION_MOUNT_PATH));
}
Volume volumeFromSecret =
new VolumeBuilder()
.withName(secret.getMetadata().getName())
.withSecret(
new SecretVolumeSourceBuilder()
.withNewSecretName(secret.getMetadata().getName())
.build())
.build();
for (PodData podData : env.getPodsData().values()) {
if (!podData.getRole().equals(PodRole.DEPLOYMENT)) {
continue;
}
if (podData
.getSpec()
.getVolumes()
.stream()
.anyMatch(v -> v.getName().equals(volumeFromSecret.getName()))) {
volumeFromSecret.setName(volumeFromSecret.getName() + "_" + NameGenerator.generate("", 6));
}
podData.getSpec().getVolumes().add(volumeFromSecret);
for (Container container : podData.getSpec().getContainers()) {
if (targetContainerName != null && !container.getName().equals(targetContainerName)) {
continue;
}
secret
.getData()
.keySet()
.forEach(
secretFile ->
container
.getVolumeMounts()
.add(
new VolumeMountBuilder()
.withName(volumeFromSecret.getName())
.withMountPath(mountPath + "/" + secretFile)
.withSubPath(secretFile)
.withReadOnly(true)
.build()));
}
}
}
private String envName(Secret secret, String key) throws InfrastructureException {
String mountEnvName;
if (secret.getData().size() == 1) {
try {
mountEnvName =
firstNonNull(
secret.getMetadata().getAnnotations().get(ANNOTATION_ENV_NAME),
secret
.getMetadata()
.getAnnotations()
.get(format(ANNOTATION_ENV_NAME_TEMPLATE, key)));
} catch (NullPointerException e) {
throw new InfrastructureException(
format(
"Unable to mount secret '%s': It is configured to be mount as a environment variable, but its was not specified. Please define the '%s' annotation on the secret to specify it.",
secret.getMetadata().getName(), ANNOTATION_ENV_NAME));
}
} else {
mountEnvName =
secret.getMetadata().getAnnotations().get(format(ANNOTATION_ENV_NAME_TEMPLATE, key));
if (mountEnvName == null) {
throw new InfrastructureException(
format(
"Unable to mount key '%s' of secret '%s': It is configured to be mount as a environment variable, but its was not specified. Please define the '%s' annotation on the secret to specify it.",
key, secret.getMetadata().getName(), format(ANNOTATION_ENV_NAME_TEMPLATE, key)));
}
}
return mountEnvName;
}
}

View File

@ -0,0 +1,150 @@
/*
* 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.provision.secret;
import static java.lang.String.format;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.SecretAsContainerResourceProvisioner.ANNOTATION_PREFIX;
import com.google.common.annotations.Beta;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
import io.fabric8.kubernetes.api.model.EnvVarSourceBuilder;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretKeySelectorBuilder;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodRole;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Mounts Kubernetes secret with specific annotations as an environment variable(s) in workspace
* containers. Allows per-component control of secret applying in devfile.
*/
@Beta
@Singleton
public class EnvironmentVariableSecretApplier
extends KubernetesSecretApplier<KubernetesEnvironment> {
@Inject private RuntimeEventsPublisher runtimeEventsPublisher;
private static final Logger LOG = LoggerFactory.getLogger(EnvironmentVariableSecretApplier.class);
static final String ANNOTATION_ENV_NAME = ANNOTATION_PREFIX + "/" + "env-name";
static final String ANNOTATION_ENV_NAME_TEMPLATE = ANNOTATION_PREFIX + "/%s_" + "env-name";
/**
* Applies secret as environment variable into workspace containers, respecting automount
* attribute and optional devfile automount property override.
*
* @param env kubernetes environment with workspace containers configuration
* @param runtimeIdentity identity of current runtime
* @param secret source secret to apply
* @throws InfrastructureException on misconfigured secrets or other apply error
*/
@Override
public void applySecret(KubernetesEnvironment env, RuntimeIdentity runtimeIdentity, Secret secret)
throws InfrastructureException {
boolean secretAutomount =
Boolean.parseBoolean(secret.getMetadata().getAnnotations().get(ANNOTATION_AUTOMOUNT));
for (PodData podData : env.getPodsData().values()) {
if (!podData.getRole().equals(PodRole.DEPLOYMENT)) {
continue;
}
for (Container container : podData.getSpec().getContainers()) {
Optional<ComponentImpl> component = getComponent(env, container.getName());
// skip components that explicitly disable automount
if (component.isPresent() && isComponentAutomountFalse(component.get())) {
continue;
}
// if automount disabled globally and not overridden in component
if (!secretAutomount
&& (!component.isPresent() || !isComponentAutomountTrue(component.get()))) {
continue;
}
for (Entry<String, String> secretDataEntry : secret.getData().entrySet()) {
final String mountEnvName = envName(secret, secretDataEntry.getKey(), runtimeIdentity);
container
.getEnv()
.add(
new EnvVarBuilder()
.withName(mountEnvName)
.withValueFrom(
new EnvVarSourceBuilder()
.withSecretKeyRef(
new SecretKeySelectorBuilder()
.withName(secret.getMetadata().getName())
.withKey(secretDataEntry.getKey())
.build())
.build())
.build());
}
}
}
}
private String envName(Secret secret, String key, RuntimeIdentity runtimeIdentity)
throws InfrastructureException {
String mountEnvName;
if (secret.getData().size() == 1) {
List<String> providedNames =
Stream.of(
secret.getMetadata().getAnnotations().get(ANNOTATION_ENV_NAME),
secret
.getMetadata()
.getAnnotations()
.get(format(ANNOTATION_ENV_NAME_TEMPLATE, key)))
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (providedNames.isEmpty()) {
throw new InfrastructureException(
format(
"Unable to mount secret '%s': It is configured to be mount as a environment variable, but its name was not specified. Please define the '%s' annotation on the secret to specify it.",
secret.getMetadata().getName(), ANNOTATION_ENV_NAME));
}
if (providedNames.size() > 1) {
String msg =
String.format(
"Secret '%s' defines multiple environment variable name annotations, but contains only one data entry. That may cause inconsistent behavior and needs to be corrected.",
secret.getMetadata().getName());
LOG.warn(msg);
runtimeEventsPublisher.sendRuntimeLogEvent(
msg, ZonedDateTime.now().toString(), runtimeIdentity);
}
mountEnvName = providedNames.get(0);
} else {
mountEnvName =
secret.getMetadata().getAnnotations().get(format(ANNOTATION_ENV_NAME_TEMPLATE, key));
if (mountEnvName == null) {
throw new InfrastructureException(
format(
"Unable to mount key '%s' of secret '%s': It is configured to be mount as a environment variable, but its name was not specified. Please define the '%s' annotation on the secret to specify it.",
key, secret.getMetadata().getName(), format(ANNOTATION_ENV_NAME_TEMPLATE, key)));
}
}
return mountEnvName;
}
}

View File

@ -0,0 +1,141 @@
/*
* 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.provision.secret;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.SecretAsContainerResourceProvisioner.ANNOTATION_PREFIX;
import com.google.common.annotations.Beta;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretVolumeSourceBuilder;
import io.fabric8.kubernetes.api.model.Volume;
import io.fabric8.kubernetes.api.model.VolumeBuilder;
import io.fabric8.kubernetes.api.model.VolumeMountBuilder;
import java.nio.file.Paths;
import java.util.Optional;
import javax.inject.Singleton;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl;
import org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.commons.lang.NameGenerator;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodRole;
/**
* Mounts Kubernetes secret with specific annotations as an file in workspace containers. Via
* devfile, allows per-component control of secret applying and path overrides using specific
* property.
*/
@Beta
@Singleton
public class FileSecretApplier extends KubernetesSecretApplier<KubernetesEnvironment> {
static final String ANNOTATION_MOUNT_PATH = ANNOTATION_PREFIX + "/" + "mount-path";
/**
* Applies secret as file into workspace containers, respecting automount attribute and optional
* devfile automount property and/or mount path override.
*
* @param env kubernetes environment with workspace containers configuration
* @param runtimeIdentity identity of current runtime
* @param secret source secret to apply
* @throws InfrastructureException on misconfigured secrets or other apply error
*/
@Override
public void applySecret(KubernetesEnvironment env, RuntimeIdentity runtimeIdentity, Secret secret)
throws InfrastructureException {
final String secretMountPath = secret.getMetadata().getAnnotations().get(ANNOTATION_MOUNT_PATH);
boolean secretAutomount =
Boolean.parseBoolean(secret.getMetadata().getAnnotations().get(ANNOTATION_AUTOMOUNT));
if (secretMountPath == null) {
throw new InfrastructureException(
format(
"Unable to mount secret '%s': It is configured to be mounted as a file but the mount path was not specified. Please define the '%s' annotation on the secret to specify it.",
secret.getMetadata().getName(), ANNOTATION_MOUNT_PATH));
}
Volume volumeFromSecret =
new VolumeBuilder()
.withName(secret.getMetadata().getName())
.withSecret(
new SecretVolumeSourceBuilder()
.withNewSecretName(secret.getMetadata().getName())
.build())
.build();
for (PodData podData : env.getPodsData().values()) {
if (!podData.getRole().equals(PodRole.DEPLOYMENT)) {
continue;
}
if (podData
.getSpec()
.getVolumes()
.stream()
.anyMatch(v -> v.getName().equals(volumeFromSecret.getName()))) {
volumeFromSecret.setName(volumeFromSecret.getName() + "_" + NameGenerator.generate("", 6));
}
podData.getSpec().getVolumes().add(volumeFromSecret);
for (Container container : podData.getSpec().getContainers()) {
Optional<ComponentImpl> component = getComponent(env, container.getName());
// skip components that explicitly disable automount
if (component.isPresent() && isComponentAutomountFalse(component.get())) {
continue;
}
// if automount disabled globally and not overridden in component
if (!secretAutomount
&& (!component.isPresent() || !isComponentAutomountTrue(component.get()))) {
continue;
}
// find path override if any
Optional<String> overridePathOptional = Optional.empty();
if (component.isPresent()) {
overridePathOptional =
getOverridenComponentPath(component.get(), secret.getMetadata().getName());
}
final String componentMountPath = overridePathOptional.orElse(secretMountPath);
container
.getVolumeMounts()
.removeIf(vm -> Paths.get(vm.getMountPath()).equals(Paths.get(componentMountPath)));
secret
.getData()
.keySet()
.forEach(
secretFile ->
container
.getVolumeMounts()
.add(
new VolumeMountBuilder()
.withName(volumeFromSecret.getName())
.withMountPath(componentMountPath + "/" + secretFile)
.withSubPath(secretFile)
.withReadOnly(true)
.build()));
}
}
}
private Optional<String> getOverridenComponentPath(ComponentImpl component, String secretName) {
Optional<VolumeImpl> matchedVolume =
component.getVolumes().stream().filter(v -> v.getName().equals(secretName)).findFirst();
if (matchedVolume.isPresent() && !isNullOrEmpty(matchedVolume.get().getContainerPath())) {
return Optional.of(matchedVolume.get().getContainerPath());
}
return Optional.empty();
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.provision.secret;
import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.DEVFILE_COMPONENT_ALIAS_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.SecretAsContainerResourceProvisioner.ANNOTATION_PREFIX;
import com.google.common.annotations.Beta;
import io.fabric8.kubernetes.api.model.Secret;
import java.util.Optional;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
/**
* Base class for secret appliers. Contains common functionality to find devfile components by name
* and check override automount properties.
*/
@Beta
public abstract class KubernetesSecretApplier<E extends KubernetesEnvironment> {
static final String ANNOTATION_AUTOMOUNT = ANNOTATION_PREFIX + "/" + "automount-workspace-secret";
/**
* Applies particular secret to workspace containers.
*
* @param env environment to retrieve components from
* @param runtimeIdentity identity of current runtime
* @param secret secret to apply
* @throws InfrastructureException when secret applying error
*/
public abstract void applySecret(E env, RuntimeIdentity runtimeIdentity, Secret secret)
throws InfrastructureException;
/**
* Tries to retrieve devfile component by given container name.
*
* @param env kubernetes environment of the workspace
* @param containerName name of container to find it's parent component
* @return matched component
*/
final Optional<ComponentImpl> getComponent(E env, String containerName) {
InternalMachineConfig internalMachineConfig = env.getMachines().get(containerName);
if (internalMachineConfig != null) {
String componentName =
internalMachineConfig.getAttributes().get(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE);
if (componentName != null) {
return env.getDevfile()
.getComponents()
.stream()
.filter(c -> c.getAlias().equals(componentName))
.findFirst();
}
}
return Optional.empty();
}
/**
* @param component source component
* @return {@code true} when {@code automountWorkspaceSecret} property explicitly set to {@code
* false},or {@code false} otherwise.
*/
final boolean isComponentAutomountFalse(ComponentImpl component) {
return component.getAutomountWorkspaceSecrets() != null
&& !component.getAutomountWorkspaceSecrets();
}
/**
* @param component source component
* @return {@code true} when {@code automountWorkspaceSecret} property explicitly set to {@code
* true},or {@code false} otherwise.
*/
final boolean isComponentAutomountTrue(ComponentImpl component) {
return component.getAutomountWorkspaceSecrets() != null
&& component.getAutomountWorkspaceSecrets();
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.provision.secret;
import static java.lang.String.format;
import static java.util.stream.Collectors.toMap;
import com.google.common.annotations.Beta;
import io.fabric8.kubernetes.api.model.LabelSelector;
import io.fabric8.kubernetes.api.model.LabelSelectorBuilder;
import io.fabric8.kubernetes.api.model.Secret;
import java.util.Arrays;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace;
/**
* Finds secrets with specific labels in namespace, and mount their values as file or environment
* variable into workspace containers. Secrets annotated with "che.eclipse.org/mount-as=env" are
* mount as env variables, env name is read from "che.eclipse.org/env-name" annotation. Secrets
* which having "che.eclipse.org/mount-as=file" are mounted as file in the folder specified by
* "che.eclipse.org/mount-path" annotation. Refer to che docs for concrete examples.
*/
@Beta
@Singleton
public class SecretAsContainerResourceProvisioner<E extends KubernetesEnvironment> {
static final String ANNOTATION_PREFIX = "che.eclipse.org";
static final String ANNOTATION_MOUNT_AS = ANNOTATION_PREFIX + "/" + "mount-as";
private final FileSecretApplier fileSecretApplier;
private final EnvironmentVariableSecretApplier environmentVariableSecretApplier;
private final Map<String, String> secretLabels;
@Inject
public SecretAsContainerResourceProvisioner(
FileSecretApplier fileSecretApplier,
EnvironmentVariableSecretApplier environmentVariableSecretApplier,
@Named("che.workspace.provision.secret.labels") String[] labels) {
this.fileSecretApplier = fileSecretApplier;
this.environmentVariableSecretApplier = environmentVariableSecretApplier;
this.secretLabels =
Arrays.stream(labels)
.map(item -> item.split("=", 2))
.collect(toMap(p -> p[0], p -> p.length == 1 ? "" : p[1]));
}
public void provision(E env, RuntimeIdentity runtimeIdentity, KubernetesNamespace namespace)
throws InfrastructureException {
LabelSelector selector = new LabelSelectorBuilder().withMatchLabels(secretLabels).build();
for (Secret secret : namespace.secrets().get(selector)) {
if (secret.getMetadata().getAnnotations() == null) {
throw new InfrastructureException(
format(
"Unable to mount secret '%s': it has missing required annotations. Please check documentation for secret format guide.",
secret.getMetadata().getName()));
}
String mountType = secret.getMetadata().getAnnotations().get(ANNOTATION_MOUNT_AS);
if ("env".equalsIgnoreCase(mountType)) {
environmentVariableSecretApplier.applySecret(env, runtimeIdentity, secret);
} else if ("file".equalsIgnoreCase(mountType)) {
fileSecretApplier.applySecret(env, runtimeIdentity, secret);
} else {
throw new InfrastructureException(
format(
"Unable to mount secret '%s': it has missing or unknown type of the mount. Please make sure that '%s' annotation has value either 'env' or 'file'.",
secret.getMetadata().getName(), ANNOTATION_MOUNT_AS));
}
}
}
}

View File

@ -132,7 +132,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.LogWatc
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.log.PodLogHandler;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.WorkspaceVolumesStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.KubernetesPreviewUrlCommandProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.SecretAsContainerResourceProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.SecretAsContainerResourceProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerResolver;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressPathTransformInverter;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;

View File

@ -9,25 +9,23 @@
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.provision;
package org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret;
import static java.lang.String.format;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.SecretAsContainerResourceProvisioner.ANNOTATION_ENV_NAME;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.SecretAsContainerResourceProvisioner.ANNOTATION_ENV_NAME_TEMPLATE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.SecretAsContainerResourceProvisioner.ANNOTATION_MOUNT_AS;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.SecretAsContainerResourceProvisioner.ANNOTATION_MOUNT_PATH;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.SecretAsContainerResourceProvisioner.ANNOTATION_TARGET_CONTAINER;
import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.DEVFILE_COMPONENT_ALIAS_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.EnvironmentVariableSecretApplier.ANNOTATION_ENV_NAME;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.EnvironmentVariableSecretApplier.ANNOTATION_ENV_NAME_TEMPLATE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretApplier.ANNOTATION_AUTOMOUNT;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.SecretAsContainerResourceProvisioner.ANNOTATION_MOUNT_AS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
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 com.google.common.collect.ImmutableMap;
@ -37,16 +35,16 @@ import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.LabelSelector;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.PodSpecBuilder;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.api.model.Volume;
import io.fabric8.kubernetes.api.model.VolumeMount;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl;
import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodRole;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
@ -55,26 +53,23 @@ import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class SecretAsContainerResourceProvisionerTest {
private SecretAsContainerResourceProvisioner<KubernetesEnvironment> provisioner =
new SecretAsContainerResourceProvisioner<>(new String[] {"app:che"});
public class EnvironmentVariableSecretApplierTest {
@Mock private KubernetesEnvironment environment;
@Mock private KubernetesNamespace namespace;
@Mock private KubernetesSecrets secrets;
@Mock private PodData podData;
@Mock private PodSpec podSpec;
@Mock private RuntimeIdentity runtimeIdentity;
EnvironmentVariableSecretApplier secretApplier = new EnvironmentVariableSecretApplier();
@BeforeMethod
public void setUp() throws Exception {
when(namespace.secrets()).thenReturn(secrets);
when(environment.getPodsData()).thenReturn(singletonMap("pod1", podData));
when(podData.getRole()).thenReturn(PodRole.DEPLOYMENT);
when(podData.getSpec()).thenReturn(podSpec);
}
@ -98,20 +93,16 @@ public class SecretAsContainerResourceProvisionerTest {
"MY_FOO",
ANNOTATION_MOUNT_AS,
"env",
ANNOTATION_TARGET_CONTAINER,
"maven"))
ANNOTATION_AUTOMOUNT,
"true"))
.withLabels(emptyMap())
.build())
.build();
when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret));
provisioner.provision(environment, namespace);
secretApplier.applySecret(environment, runtimeIdentity, secret);
// nothing to do with unmatched container
verify(container_unmatch).getName();
verifyNoMoreInteractions(container_unmatch);
// matched container has env set
// container has env set
assertEquals(container_match.getEnv().size(), 1);
EnvVar var = container_match.getEnv().get(0);
assertEquals(var.getName(), "MY_FOO");
@ -122,9 +113,8 @@ public class SecretAsContainerResourceProvisionerTest {
@Test
public void shouldProvisionMultiEnvVariable() throws Exception {
Container container_match = new ContainerBuilder().withName("maven").build();
Container container_unmatch = spy(new ContainerBuilder().withName("other").build());
when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match, container_unmatch));
when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match));
Secret secret =
new SecretBuilder()
@ -134,26 +124,22 @@ public class SecretAsContainerResourceProvisionerTest {
.withName("test_secret")
.withAnnotations(
ImmutableMap.of(
format(ANNOTATION_ENV_NAME_TEMPLATE, "foo"),
String.format(ANNOTATION_ENV_NAME_TEMPLATE, "foo"),
"MY_FOO",
format(ANNOTATION_ENV_NAME_TEMPLATE, "bar"),
String.format(ANNOTATION_ENV_NAME_TEMPLATE, "bar"),
"MY_BAR",
ANNOTATION_MOUNT_AS,
"env",
ANNOTATION_TARGET_CONTAINER,
"maven"))
ANNOTATION_AUTOMOUNT,
"true"))
.withLabels(emptyMap())
.build())
.build();
when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret));
provisioner.provision(environment, namespace);
secretApplier.applySecret(environment, runtimeIdentity, secret);
// nothing to do with unmatched container
verify(container_unmatch).getName();
verifyNoMoreInteractions(container_unmatch);
// matched container has env set
// container has env set
assertEquals(container_match.getEnv().size(), 2);
EnvVar var = container_match.getEnv().get(0);
assertEquals(var.getName(), "MY_FOO");
@ -167,7 +153,7 @@ public class SecretAsContainerResourceProvisionerTest {
}
@Test
public void shouldProvisionAllContainersIfNotSpecifyOne() throws Exception {
public void shouldProvisionAllContainersIfAutomountEnabled() throws Exception {
Container container_match1 = new ContainerBuilder().withName("maven").build();
Container container_match2 = new ContainerBuilder().withName("other").build();
@ -180,13 +166,16 @@ public class SecretAsContainerResourceProvisionerTest {
new ObjectMetaBuilder()
.withName("test_secret")
.withAnnotations(
ImmutableMap.of(ANNOTATION_ENV_NAME, "MY_FOO", ANNOTATION_MOUNT_AS, "env"))
ImmutableMap.of(
ANNOTATION_ENV_NAME, "MY_FOO",
ANNOTATION_MOUNT_AS, "env",
ANNOTATION_AUTOMOUNT, "true"))
.withLabels(emptyMap())
.build())
.build();
when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret));
provisioner.provision(environment, namespace);
secretApplier.applySecret(environment, runtimeIdentity, secret);
// both containers has env set
assertEquals(container_match1.getEnv().size(), 1);
@ -203,152 +192,126 @@ public class SecretAsContainerResourceProvisionerTest {
}
@Test
public void shouldProvisionAsFiles() throws Exception {
Container container_match = new ContainerBuilder().withName("maven").build();
Container container_unmatch = new ContainerBuilder().withName("other").build();
public void shouldProvisionContainersWithAutomountOverrideTrue() throws Exception {
Container container_match1 = new ContainerBuilder().withName("maven").build();
Container container_match2 = new ContainerBuilder().withName("other").build();
DevfileImpl mock_defvile = mock(DevfileImpl.class);
ComponentImpl component = new ComponentImpl();
component.setAlias("maven");
component.setAutomountWorkspaceSecrets(true);
PodSpec localSpec =
new PodSpecBuilder()
.withContainers(ImmutableList.of(container_match, container_unmatch))
.build();
when(podData.getSpec()).thenReturn(localSpec);
when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match1, container_match2));
InternalMachineConfig internalMachineConfig = new InternalMachineConfig();
internalMachineConfig.getAttributes().put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, "maven");
when(environment.getMachines()).thenReturn(ImmutableMap.of("maven", internalMachineConfig));
when(environment.getDevfile()).thenReturn(mock_defvile);
when(mock_defvile.getComponents()).thenReturn(singletonList(component));
Secret secret =
new SecretBuilder()
.withData(ImmutableMap.of("settings.xml", "random", "another.xml", "freedom"))
.withData(singletonMap("foo", "random"))
.withMetadata(
new ObjectMetaBuilder()
.withName("test_secret")
.withAnnotations(
ImmutableMap.of(
ANNOTATION_MOUNT_AS,
"file",
ANNOTATION_MOUNT_PATH,
"/home/user/.m2",
ANNOTATION_TARGET_CONTAINER,
"maven"))
ANNOTATION_ENV_NAME, "MY_FOO",
ANNOTATION_MOUNT_AS, "env",
ANNOTATION_AUTOMOUNT, "false"))
.withLabels(emptyMap())
.build())
.build();
when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret));
provisioner.provision(environment, namespace);
secretApplier.applySecret(environment, runtimeIdentity, secret);
// pod has volume created
assertEquals(environment.getPodsData().get("pod1").getSpec().getVolumes().size(), 1);
Volume volume = environment.getPodsData().get("pod1").getSpec().getVolumes().get(0);
assertEquals(volume.getName(), "test_secret");
assertEquals(volume.getSecret().getSecretName(), "test_secret");
// only first container has env set
assertEquals(container_match1.getEnv().size(), 1);
EnvVar var = container_match1.getEnv().get(0);
assertEquals(var.getName(), "MY_FOO");
assertEquals(var.getValueFrom().getSecretKeyRef().getName(), "test_secret");
assertEquals(var.getValueFrom().getSecretKeyRef().getKey(), "foo");
// matched container has mounts set
assertEquals(
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(0)
.getVolumeMounts()
.size(),
2);
VolumeMount mount1 =
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(0)
.getVolumeMounts()
.get(0);
assertEquals(mount1.getName(), "test_secret");
assertEquals(mount1.getMountPath(), "/home/user/.m2/" + mount1.getSubPath());
assertFalse(mount1.getSubPath().isEmpty());
assertTrue(mount1.getReadOnly());
VolumeMount mount2 =
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(0)
.getVolumeMounts()
.get(1);
assertEquals(mount2.getName(), "test_secret");
assertEquals(mount2.getMountPath(), "/home/user/.m2/" + mount2.getSubPath());
assertFalse(mount2.getSubPath().isEmpty());
assertTrue(mount2.getReadOnly());
// unmatched container has no mounts
assertEquals(
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(1)
.getVolumeMounts()
.size(),
0);
if ("settings.xml".equals(mount1.getSubPath())) {
assertEquals(mount1.getSubPath(), "settings.xml");
assertEquals(mount2.getSubPath(), "another.xml");
} else {
assertEquals(mount1.getSubPath(), "another.xml");
assertEquals(mount2.getSubPath(), "settings.xml");
}
assertEquals(container_match2.getEnv().size(), 0);
}
@Test(
expectedExceptions = InfrastructureException.class,
expectedExceptionsMessageRegExp =
"Unable to mount secret 'test_secret': It is configured to be mounted as a file but the mount path was not specified. Please define the 'che.eclipse.org/mount-path' annotation on the secret to specify it.")
public void shouldThrowExceptionWhenNoMountPathSpecifiedForFiles() throws Exception {
Container container_match = new ContainerBuilder().withName("maven").build();
@Test
public void shouldNotProvisionContainersWithAutomountOverrideFalse() throws Exception {
Container container_match1 = new ContainerBuilder().withName("maven").build();
Container container_match2 = new ContainerBuilder().withName("other").build();
DevfileImpl mock_defvile = mock(DevfileImpl.class);
ComponentImpl component = new ComponentImpl();
component.setAlias("maven");
component.setAutomountWorkspaceSecrets(false);
PodSpec localSpec =
new PodSpecBuilder().withContainers(ImmutableList.of(container_match)).build();
when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match1, container_match2));
InternalMachineConfig internalMachineConfig = new InternalMachineConfig();
internalMachineConfig.getAttributes().put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, "maven");
when(environment.getMachines()).thenReturn(ImmutableMap.of("maven", internalMachineConfig));
when(environment.getDevfile()).thenReturn(mock_defvile);
when(mock_defvile.getComponents()).thenReturn(singletonList(component));
when(podData.getSpec()).thenReturn(localSpec);
Secret secret =
new SecretBuilder()
.withData(ImmutableMap.of("settings.xml", "random", "another.xml", "freedom"))
.withData(singletonMap("foo", "random"))
.withMetadata(
new ObjectMetaBuilder()
.withName("test_secret")
.withAnnotations(singletonMap(ANNOTATION_MOUNT_AS, "file"))
.withAnnotations(
ImmutableMap.of(
ANNOTATION_ENV_NAME, "MY_FOO",
ANNOTATION_MOUNT_AS, "env",
ANNOTATION_AUTOMOUNT, "true"))
.withLabels(emptyMap())
.build())
.build();
when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret));
provisioner.provision(environment, namespace);
secretApplier.applySecret(environment, runtimeIdentity, secret);
// only second container has env set
assertEquals(container_match1.getEnv().size(), 0);
assertEquals(container_match2.getEnv().size(), 1);
EnvVar var2 = container_match2.getEnv().get(0);
assertEquals(var2.getName(), "MY_FOO");
assertEquals(var2.getValueFrom().getSecretKeyRef().getName(), "test_secret");
assertEquals(var2.getValueFrom().getSecretKeyRef().getKey(), "foo");
}
@Test(
expectedExceptions = InfrastructureException.class,
expectedExceptionsMessageRegExp =
"Unable to mount secret 'test_secret': it has missing or unknown type of the mount. Please make sure that 'che.eclipse.org/mount-as' annotation has value either 'env' or 'file'.")
public void shouldThrowExceptionWhenNoMountTypeSpecified() throws Exception {
@Test
public void shouldNotProvisionAllContainersifAutomountDisabled() throws Exception {
Container container_match1 = spy(new ContainerBuilder().withName("maven").build());
Container container_match2 = spy(new ContainerBuilder().withName("other").build());
when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match1, container_match2));
Secret secret =
new SecretBuilder()
.withData(ImmutableMap.of("settings.xml", "random", "another.xml", "freedom"))
.withData(singletonMap("foo", "random"))
.withMetadata(
new ObjectMetaBuilder()
.withName("test_secret")
.withAnnotations(emptyMap())
.withAnnotations(
ImmutableMap.of(
ANNOTATION_ENV_NAME, "MY_FOO",
ANNOTATION_MOUNT_AS, "env",
ANNOTATION_AUTOMOUNT, "false"))
.withLabels(emptyMap())
.build())
.build();
when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret));
provisioner.provision(environment, namespace);
secretApplier.applySecret(environment, runtimeIdentity, secret);
verify(container_match1).getName();
verify(container_match2).getName();
// both containers no actions
verifyNoMoreInteractions(container_match1, container_match2);
}
@Test(
expectedExceptions = InfrastructureException.class,
expectedExceptionsMessageRegExp =
"Unable to mount secret 'test_secret': It is configured to be mount as a environment variable, but its was not specified. Please define the 'che.eclipse.org/env-name' annotation on the secret to specify it.")
"Unable to mount secret 'test_secret': It is configured to be mount as a environment variable, but its name was not specified. Please define the 'che.eclipse.org/env-name' annotation on the secret to specify it.")
public void shouldThrowExceptionWhenNoEnvNameSpecifiedSingleValue() throws Exception {
Container container_match = new ContainerBuilder().withName("maven").build();
@ -360,19 +323,20 @@ public class SecretAsContainerResourceProvisionerTest {
.withMetadata(
new ObjectMetaBuilder()
.withName("test_secret")
.withAnnotations(ImmutableMap.of(ANNOTATION_MOUNT_AS, "env"))
.withAnnotations(
ImmutableMap.of(ANNOTATION_MOUNT_AS, "env", ANNOTATION_AUTOMOUNT, "true"))
.withLabels(emptyMap())
.build())
.build();
when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret));
provisioner.provision(environment, namespace);
secretApplier.applySecret(environment, runtimeIdentity, secret);
}
@Test(
expectedExceptions = InfrastructureException.class,
expectedExceptionsMessageRegExp =
"Unable to mount key 'foo' of secret 'test_secret': It is configured to be mount as a environment variable, but its was not specified. Please define the 'che.eclipse.org/foo_env-name' annotation on the secret to specify it.")
"Unable to mount key 'foo' of secret 'test_secret': It is configured to be mount as a environment variable, but its name was not specified. Please define the 'che.eclipse.org/foo_env-name' annotation on the secret to specify it.")
public void shouldThrowExceptionWhenNoEnvNameSpecifiedMultiValue() throws Exception {
Container container_match = new ContainerBuilder().withName("maven").build();
@ -384,12 +348,13 @@ public class SecretAsContainerResourceProvisionerTest {
.withMetadata(
new ObjectMetaBuilder()
.withName("test_secret")
.withAnnotations(ImmutableMap.of(ANNOTATION_MOUNT_AS, "env"))
.withAnnotations(
ImmutableMap.of(ANNOTATION_MOUNT_AS, "env", ANNOTATION_AUTOMOUNT, "true"))
.withLabels(emptyMap())
.build())
.build();
when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret));
provisioner.provision(environment, namespace);
secretApplier.applySecret(environment, runtimeIdentity, secret);
}
}

View File

@ -0,0 +1,473 @@
/*
* 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.provision.secret;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.DEVFILE_COMPONENT_ALIAS_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.FileSecretApplier.ANNOTATION_MOUNT_PATH;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.KubernetesSecretApplier.ANNOTATION_AUTOMOUNT;
import static org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.SecretAsContainerResourceProvisioner.ANNOTATION_MOUNT_AS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
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 com.google.common.collect.ImmutableMap;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.ContainerBuilder;
import io.fabric8.kubernetes.api.model.LabelSelector;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.PodSpecBuilder;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.api.model.Volume;
import io.fabric8.kubernetes.api.model.VolumeMount;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl;
import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl;
import org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodRole;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class FileSecretApplierTest {
@Mock private KubernetesEnvironment environment;
@Mock private KubernetesSecrets secrets;
@Mock private PodData podData;
@Mock private PodSpec podSpec;
@Mock private RuntimeIdentity runtimeIdentity;
FileSecretApplier secretApplier = new FileSecretApplier();
@BeforeMethod
public void setUp() throws Exception {
when(environment.getPodsData()).thenReturn(singletonMap("pod1", podData));
when(podData.getRole()).thenReturn(PodRole.DEPLOYMENT);
when(podData.getSpec()).thenReturn(podSpec);
}
@Test
public void shouldProvisionAsFiles() throws Exception {
Container container_match1 = new ContainerBuilder().withName("maven").build();
Container container_match2 = new ContainerBuilder().withName("other").build();
PodSpec localSpec =
new PodSpecBuilder()
.withContainers(ImmutableList.of(container_match1, container_match2))
.build();
when(podData.getSpec()).thenReturn(localSpec);
Secret secret =
new SecretBuilder()
.withData(ImmutableMap.of("settings.xml", "random", "another.xml", "freedom"))
.withMetadata(
new ObjectMetaBuilder()
.withName("test_secret")
.withAnnotations(
ImmutableMap.of(
ANNOTATION_MOUNT_AS,
"file",
ANNOTATION_MOUNT_PATH,
"/home/user/.m2",
ANNOTATION_AUTOMOUNT,
"true"))
.withLabels(emptyMap())
.build())
.build();
secretApplier.applySecret(environment, runtimeIdentity, secret);
// pod has volume created
assertEquals(environment.getPodsData().get("pod1").getSpec().getVolumes().size(), 1);
Volume volume = environment.getPodsData().get("pod1").getSpec().getVolumes().get(0);
assertEquals(volume.getName(), "test_secret");
assertEquals(volume.getSecret().getSecretName(), "test_secret");
// both containers has mounts set
assertEquals(
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(0)
.getVolumeMounts()
.size(),
2);
VolumeMount mount1 =
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(0)
.getVolumeMounts()
.get(0);
assertEquals(mount1.getName(), "test_secret");
assertEquals(mount1.getMountPath(), "/home/user/.m2/" + mount1.getSubPath());
assertFalse(mount1.getSubPath().isEmpty());
assertTrue(mount1.getReadOnly());
VolumeMount mount2 =
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(0)
.getVolumeMounts()
.get(1);
assertEquals(mount2.getName(), "test_secret");
assertEquals(mount2.getMountPath(), "/home/user/.m2/" + mount2.getSubPath());
assertFalse(mount2.getSubPath().isEmpty());
assertTrue(mount2.getReadOnly());
assertEquals(
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(1)
.getVolumeMounts()
.size(),
2);
if ("settings.xml".equals(mount1.getSubPath())) {
assertEquals(mount1.getSubPath(), "settings.xml");
assertEquals(mount2.getSubPath(), "another.xml");
} else {
assertEquals(mount1.getSubPath(), "another.xml");
assertEquals(mount2.getSubPath(), "settings.xml");
}
}
@Test
public void shouldProvisionAsFilesWithPathOverride() throws Exception {
Container container = new ContainerBuilder().withName("maven").build();
DevfileImpl mock_defvile = mock(DevfileImpl.class);
ComponentImpl component = new ComponentImpl();
component.setAlias("maven");
component.getVolumes().add(new VolumeImpl("test_secret", "/path/to/override"));
InternalMachineConfig internalMachineConfig = new InternalMachineConfig();
internalMachineConfig.getAttributes().put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, "maven");
when(environment.getMachines()).thenReturn(ImmutableMap.of("maven", internalMachineConfig));
when(environment.getDevfile()).thenReturn(mock_defvile);
when(mock_defvile.getComponents()).thenReturn(singletonList(component));
PodSpec localSpec = new PodSpecBuilder().withContainers(ImmutableList.of(container)).build();
when(podData.getSpec()).thenReturn(localSpec);
Secret secret =
new SecretBuilder()
.withData(ImmutableMap.of("settings.xml", "random", "another.xml", "freedom"))
.withMetadata(
new ObjectMetaBuilder()
.withName("test_secret")
.withAnnotations(
ImmutableMap.of(
ANNOTATION_MOUNT_AS,
"file",
ANNOTATION_MOUNT_PATH,
"/home/user/.m2",
ANNOTATION_AUTOMOUNT,
"true"))
.withLabels(emptyMap())
.build())
.build();
secretApplier.applySecret(environment, runtimeIdentity, secret);
// pod has volume created
assertEquals(environment.getPodsData().get("pod1").getSpec().getVolumes().size(), 1);
Volume volume = environment.getPodsData().get("pod1").getSpec().getVolumes().get(0);
assertEquals(volume.getName(), "test_secret");
assertEquals(volume.getSecret().getSecretName(), "test_secret");
// both containers has mounts set
assertEquals(
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(0)
.getVolumeMounts()
.size(),
2);
VolumeMount mount1 =
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(0)
.getVolumeMounts()
.get(0);
assertEquals(mount1.getName(), "test_secret");
assertEquals(mount1.getMountPath(), "/path/to/override/settings.xml");
assertTrue(mount1.getReadOnly());
}
@Test
public void shouldProvisionContainersWithAutomountOverrideTrue() throws Exception {
Container container_match1 = new ContainerBuilder().withName("maven").build();
Container container_match2 = new ContainerBuilder().withName("other").build();
DevfileImpl mock_defvile = mock(DevfileImpl.class);
ComponentImpl component = new ComponentImpl();
component.setAlias("maven");
component.setAutomountWorkspaceSecrets(true);
InternalMachineConfig internalMachineConfig = new InternalMachineConfig();
internalMachineConfig.getAttributes().put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, "maven");
when(environment.getMachines()).thenReturn(ImmutableMap.of("maven", internalMachineConfig));
when(environment.getDevfile()).thenReturn(mock_defvile);
when(mock_defvile.getComponents()).thenReturn(singletonList(component));
PodSpec localSpec =
new PodSpecBuilder()
.withContainers(ImmutableList.of(container_match1, container_match2))
.build();
when(podData.getSpec()).thenReturn(localSpec);
Secret secret =
new SecretBuilder()
.withData(singletonMap("foo", "random"))
.withMetadata(
new ObjectMetaBuilder()
.withName("test_secret")
.withAnnotations(
ImmutableMap.of(
ANNOTATION_MOUNT_AS,
"file",
ANNOTATION_MOUNT_PATH,
"/home/user/.m2",
ANNOTATION_AUTOMOUNT,
"false"))
.withLabels(emptyMap())
.build())
.build();
secretApplier.applySecret(environment, runtimeIdentity, secret);
// pod has volume created
assertEquals(environment.getPodsData().get("pod1").getSpec().getVolumes().size(), 1);
Volume volume = environment.getPodsData().get("pod1").getSpec().getVolumes().get(0);
assertEquals(volume.getName(), "test_secret");
assertEquals(volume.getSecret().getSecretName(), "test_secret");
// first container has mount set
assertEquals(
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(0)
.getVolumeMounts()
.size(),
1);
VolumeMount mount1 =
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(0)
.getVolumeMounts()
.get(0);
assertEquals(mount1.getName(), "test_secret");
assertEquals(mount1.getMountPath(), "/home/user/.m2/foo");
assertTrue(mount1.getReadOnly());
// second container has no mounts
assertEquals(
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(1)
.getVolumeMounts()
.size(),
0);
}
@Test
public void shouldNotProvisionContainersWithAutomountOverrideFalse() throws Exception {
Container container_match1 = new ContainerBuilder().withName("maven").build();
Container container_match2 = new ContainerBuilder().withName("other").build();
DevfileImpl mock_defvile = mock(DevfileImpl.class);
ComponentImpl component = new ComponentImpl();
component.setAlias("maven");
component.setAutomountWorkspaceSecrets(false);
InternalMachineConfig internalMachineConfig = new InternalMachineConfig();
internalMachineConfig.getAttributes().put(DEVFILE_COMPONENT_ALIAS_ATTRIBUTE, "maven");
when(environment.getMachines()).thenReturn(ImmutableMap.of("maven", internalMachineConfig));
when(environment.getDevfile()).thenReturn(mock_defvile);
when(mock_defvile.getComponents()).thenReturn(singletonList(component));
PodSpec localSpec =
new PodSpecBuilder()
.withContainers(ImmutableList.of(container_match1, container_match2))
.build();
when(podData.getSpec()).thenReturn(localSpec);
Secret secret =
new SecretBuilder()
.withData(singletonMap("foo", "random"))
.withMetadata(
new ObjectMetaBuilder()
.withName("test_secret")
.withAnnotations(
ImmutableMap.of(
ANNOTATION_MOUNT_AS,
"file",
ANNOTATION_MOUNT_PATH,
"/home/user/.m2",
ANNOTATION_AUTOMOUNT,
"true"))
.withLabels(emptyMap())
.build())
.build();
secretApplier.applySecret(environment, runtimeIdentity, secret);
// only second container has mounts
assertEquals(environment.getPodsData().get("pod1").getSpec().getVolumes().size(), 1);
Volume volume = environment.getPodsData().get("pod1").getSpec().getVolumes().get(0);
assertEquals(volume.getName(), "test_secret");
assertEquals(volume.getSecret().getSecretName(), "test_secret");
assertEquals(
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(0)
.getVolumeMounts()
.size(),
0);
assertEquals(
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(1)
.getVolumeMounts()
.size(),
1);
VolumeMount mount2 =
environment
.getPodsData()
.get("pod1")
.getSpec()
.getContainers()
.get(1)
.getVolumeMounts()
.get(0);
assertEquals(mount2.getName(), "test_secret");
assertEquals(mount2.getMountPath(), "/home/user/.m2/foo");
assertTrue(mount2.getReadOnly());
}
@Test
public void shouldNotProvisionAllContainersifAutomountDisabled() throws Exception {
Container container_match1 = spy(new ContainerBuilder().withName("maven").build());
Container container_match2 = spy(new ContainerBuilder().withName("other").build());
when(podSpec.getContainers()).thenReturn(ImmutableList.of(container_match1, container_match2));
Secret secret =
new SecretBuilder()
.withData(singletonMap("foo", "random"))
.withMetadata(
new ObjectMetaBuilder()
.withName("test_secret")
.withAnnotations(
ImmutableMap.of(
ANNOTATION_MOUNT_AS,
"file",
ANNOTATION_MOUNT_PATH,
"/home/user/.m2",
ANNOTATION_AUTOMOUNT,
"false"))
.withLabels(emptyMap())
.build())
.build();
secretApplier.applySecret(environment, runtimeIdentity, secret);
verify(container_match1).getName();
verify(container_match2).getName();
// both containers no actions
verifyNoMoreInteractions(container_match1, container_match2);
}
@Test(
expectedExceptions = InfrastructureException.class,
expectedExceptionsMessageRegExp =
"Unable to mount secret 'test_secret': It is configured to be mounted as a file but the mount path was not specified. Please define the 'che.eclipse.org/mount-path' annotation on the secret to specify it.")
public void shouldThrowExceptionWhenNoMountPathSpecifiedForFiles() throws Exception {
Container container_match = new ContainerBuilder().withName("maven").build();
PodSpec localSpec =
new PodSpecBuilder().withContainers(ImmutableList.of(container_match)).build();
when(podData.getSpec()).thenReturn(localSpec);
Secret secret =
new SecretBuilder()
.withData(ImmutableMap.of("settings.xml", "random", "another.xml", "freedom"))
.withMetadata(
new ObjectMetaBuilder()
.withName("test_secret")
.withAnnotations(singletonMap(ANNOTATION_MOUNT_AS, "file"))
.withLabels(emptyMap())
.build())
.build();
when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret));
secretApplier.applySecret(environment, runtimeIdentity, secret);
}
}

View File

@ -0,0 +1,77 @@
/*
* 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.provision.secret;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableMap;
import io.fabric8.kubernetes.api.model.LabelSelector;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class SecretAsContainerResourceProvisionerTest {
@Mock EnvironmentVariableSecretApplier environmentVariableSecretApplier;
@Mock FileSecretApplier fileSecretApplier;
private SecretAsContainerResourceProvisioner<KubernetesEnvironment> provisioner;
@Mock private KubernetesEnvironment environment;
@Mock private KubernetesNamespace namespace;
@Mock private KubernetesSecrets secrets;
@Mock private RuntimeIdentity runtimeIdentity;
@BeforeMethod
public void setUp() throws Exception {
when(namespace.secrets()).thenReturn(secrets);
provisioner =
new SecretAsContainerResourceProvisioner<>(
fileSecretApplier, environmentVariableSecretApplier, new String[] {"app:che"});
}
@Test(
expectedExceptions = InfrastructureException.class,
expectedExceptionsMessageRegExp =
"Unable to mount secret 'test_secret': it has missing or unknown type of the mount. Please make sure that 'che.eclipse.org/mount-as' annotation has value either 'env' or 'file'.")
public void shouldThrowExceptionWhenNoMountTypeSpecified() throws Exception {
Secret secret =
new SecretBuilder()
.withData(ImmutableMap.of("settings.xml", "random", "another.xml", "freedom"))
.withMetadata(
new ObjectMetaBuilder()
.withName("test_secret")
.withAnnotations(emptyMap())
.withLabels(emptyMap())
.build())
.build();
when(secrets.get(any(LabelSelector.class))).thenReturn(singletonList(secret));
provisioner.provision(environment, runtimeIdentity, namespace);
}
}

View File

@ -37,7 +37,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.StartSynchronizerFact
import org.eclipse.che.workspace.infrastructure.kubernetes.cache.KubernetesMachineCache;
import org.eclipse.che.workspace.infrastructure.kubernetes.cache.KubernetesRuntimeStateCache;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.WorkspaceVolumesStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.SecretAsContainerResourceProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.SecretAsContainerResourceProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.UnrecoverablePodEventListenerFactory;

View File

@ -75,7 +75,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesD
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesServices;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.WorkspaceVolumesStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.SecretAsContainerResourceProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.secret.SecretAsContainerResourceProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.UnrecoverablePodEventListenerFactory;

View File

@ -126,6 +126,13 @@ public interface ComponentDto extends Component {
ComponentDto withMountSources(Boolean mountSources);
@Override
Boolean getAutomountWorkspaceSecrets();
void setAutomountWorkspaceSecrets(Boolean automountWorkspaceSecrets);
ComponentDto withAutomountWorkspaceSecrets(Boolean automountWorkspaceSecrets);
@Override
List<String> getCommand();

View File

@ -138,6 +138,7 @@ public final class DtoConverter {
return newDto(ComponentDto.class)
.withType(component.getType())
.withAlias(component.getAlias())
.withAutomountWorkspaceSecrets(component.getAutomountWorkspaceSecrets())
// chePlugin/cheEditor
.withId(component.getId())
.withRegistryUrl(component.getRegistryUrl())

View File

@ -152,6 +152,14 @@ public class DevfileIntegrityValidator {
getIdentifiableComponentName(component), component.getType()));
}
if (component.getAutomountWorkspaceSecrets() != null && component.getAlias() == null) {
throw new DevfileFormatException(
format(
"The 'automountWorkspaceSecrets' property cannot be used in component which doesn't have alias. "
+ "Please add alias to component '%s' that would allow to distinguish its containers.",
getIdentifiableComponentName(component)));
}
switch (component.getType()) {
case EDITOR_COMPONENT_TYPE:
if (editorComponent != null) {

View File

@ -105,6 +105,9 @@ public class ComponentImpl implements Component {
@Column(name = "mount_sources")
private Boolean mountSources;
@Column(name = "automount_secrets")
private Boolean automountWorkspaceSecrets;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(
name = "devfile_component_command",
@ -186,6 +189,7 @@ public class ComponentImpl implements Component {
String cpuLimit,
String cpuRequest,
Boolean mountSources,
Boolean automountWorkspaceSecrets,
List<String> command,
List<String> args,
List<? extends Volume> volumes,
@ -213,6 +217,7 @@ public class ComponentImpl implements Component {
this.cpuLimit = cpuLimit;
this.cpuRequest = cpuRequest;
this.mountSources = mountSources;
this.automountWorkspaceSecrets = automountWorkspaceSecrets;
this.command = command;
this.args = args;
if (volumes != null) {
@ -244,6 +249,7 @@ public class ComponentImpl implements Component {
component.getCpuLimit(),
component.getCpuRequest(),
component.getMountSources(),
component.getAutomountWorkspaceSecrets(),
component.getCommand(),
component.getArgs(),
component.getVolumes(),
@ -394,6 +400,15 @@ public class ComponentImpl implements Component {
this.mountSources = mountSources;
}
@Override
public Boolean getAutomountWorkspaceSecrets() {
return automountWorkspaceSecrets;
}
public void setAutomountWorkspaceSecrets(Boolean automountWorkspaceSecrets) {
this.automountWorkspaceSecrets = automountWorkspaceSecrets;
}
@Override
public List<String> getCommand() {
if (command == null) {
@ -464,6 +479,7 @@ public class ComponentImpl implements Component {
}
ComponentImpl component = (ComponentImpl) o;
return getMountSources() == component.getMountSources()
&& getAutomountWorkspaceSecrets() == component.getAutomountWorkspaceSecrets()
&& Objects.equals(generatedId, component.generatedId)
&& Objects.equals(alias, component.alias)
&& Objects.equals(type, component.type)
@ -500,6 +516,7 @@ public class ComponentImpl implements Component {
getSelector(),
getEntrypoints(),
getMountSources(),
getAutomountWorkspaceSecrets(),
getCommand(),
getArgs(),
getVolumes(),
@ -542,6 +559,8 @@ public class ComponentImpl implements Component {
+ '\''
+ ", mountSources="
+ mountSources
+ ", automountWorkspaceSecrets="
+ automountWorkspaceSecrets
+ ", command="
+ command
+ ", args="

View File

@ -310,6 +310,7 @@
"registryUrl": {},
"memoryLimit": {},
"memoryRequest": {},
"automountWorkspaceSecrets": {},
"cpuLimit": {},
"cpuRequest": {}
}
@ -336,6 +337,7 @@
"volumes": {},
"memoryLimit": {},
"memoryRequest": {},
"automountWorkspaceSecrets": {},
"cpuLimit": {},
"cpuRequest": {},
"reference": {},
@ -402,6 +404,7 @@
"type": {},
"alias": {},
"mountSources": {},
"automountWorkspaceSecrets": {},
"volumes": {},
"env": {},
"endpoints": {},
@ -494,6 +497,7 @@
"env": {},
"cpuLimit": {},
"cpuRequest": {},
"automountWorkspaceSecrets": {},
"volumes": {},
"endpoints": {},
"memoryLimit": {
@ -614,6 +618,10 @@
"1230m"
]
},
"automountWorkspaceSecrets": {
"type": "boolean",
"description": "Describes whether namespace secrets should be mount to the component."
},
"volumes": {
"type": "array",
"description": "Describes volumes which should be mount to component",

View File

@ -53,6 +53,7 @@ public class DevfileSchemaValidatorTest {
{"kubernetes_openshift_component/devfile_kubernetes_component.yaml"},
{"kubernetes_openshift_component/devfile_kubernetes_component_absolute_reference.yaml"},
{"component/devfile_without_any_component.yaml"},
{"component/devfile_component_with_automount_secrets.yaml"},
{
"kubernetes_openshift_component/devfile_kubernetes_component_reference_and_content_as_block.yaml"
},

View File

@ -634,6 +634,7 @@ public class WorkspaceDaoTest {
"2",
"1",
false,
false,
singletonList("command"),
singletonList("arg"),
singletonList(volume3),
@ -968,6 +969,7 @@ public class WorkspaceDaoTest {
"2",
"130m",
false,
false,
singletonList("command"),
singletonList("arg"),
asList(volume1, volume2),
@ -998,6 +1000,7 @@ public class WorkspaceDaoTest {
"3",
"180m",
false,
false,
singletonList("command"),
singletonList("arg"),
asList(volume1, volume2),
@ -1005,16 +1008,13 @@ public class WorkspaceDaoTest {
asList(endpoint1, endpoint2));
component2.setSelector(singletonMap("key2", "value2"));
DevfileImpl devfile =
new DevfileImpl(
"0.0.1",
asList(project1, project2),
asList(component1, component2),
asList(command1, command2),
singletonMap("attribute1", "value1"),
new MetadataImpl(name));
return devfile;
return new DevfileImpl(
"0.0.1",
asList(project1, project2),
asList(component1, component2),
asList(command1, command2),
singletonMap("attribute1", "value1"),
new MetadataImpl(name));
}
private <T extends CascadeEvent> CascadeEventSubscriber<T> mockCascadeEventSubscriber() {

View File

@ -0,0 +1,26 @@
#
# 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
#
---
apiVersion: 1.0.0
metadata:
name: petclinic-dev-environment
components:
- type: chePlugin
alias: maven
id: eclipse/chemaven-jdk8/1.0.0
automountWorkspaceSecrets: false
- type: dockerimage
alias: maven2
image: eclipse/chemaven-jdk8/1.0.0
automountWorkspaceSecrets: true
memoryLimit: 256Mi

View File

@ -0,0 +1,13 @@
--
-- Copyright (c) 2012-2020 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
--
ALTER TABLE devfile_component ADD COLUMN automount_secrets BOOLEAN;

View File

@ -126,6 +126,7 @@ public final class TestObjectsFactory {
"200m",
"100m",
false,
false,
singletonList("command"),
singletonList("arg"),
null,