Per-component override of secrets automount and paths override for file mounts;
parent
9bbb5eab8e
commit
3b2dbca536
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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="
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
@ -126,6 +126,7 @@ public final class TestObjectsFactory {
|
|||
"200m",
|
||||
"100m",
|
||||
false,
|
||||
false,
|
||||
singletonList("command"),
|
||||
singletonList("arg"),
|
||||
null,
|
||||
|
|
|
|||
Loading…
Reference in New Issue