From 9963f9486ebff9a0677a077396c6d4c4e8d8b957 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Thu, 30 May 2019 16:11:26 -0400 Subject: [PATCH] Add ws attribute to work around arbitrary user ID Add the workspace attribute 'supportArbitraryUser' to enable an attempt at working around containers running with arbitrary user ID on OpenShift. When the attribute is set to 'true', Che will modify the recipe containers' command and arguments to attempt to add an entry to /etc/passwd for the current user ID. This resolves various problems around programs that depend on username being available when running (e.g. maven, bash). If the command fails (e.g. because /etc/passwd is not writable), the original container command will still execute. Signed-off-by: Angel Misevski --- .../OpenShiftEnvironmentProvisioner.java | 7 +- .../OpenShiftCommandProvisioner.java | 98 +++++++ .../OpenShiftEnvironmentProvisionerTest.java | 9 +- .../OpenShiftCommandProvisionerTest.java | 252 ++++++++++++++++++ .../che/api/workspace/shared/Constants.java | 20 ++ .../che-core-api-workspace/README-devfile.md | 25 ++ .../src/main/resources/schema/devfile.json | 5 + 7 files changed, 413 insertions(+), 3 deletions(-) create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftCommandProvisioner.java create mode 100644 infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftCommandProvisionerTest.java diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisioner.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisioner.java index 5a504b33fc8..18d9a73cb7a 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisioner.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisioner.java @@ -33,6 +33,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy.RestartPolicyRewriter; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.server.ServersConverter; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; +import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftCommandProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftUniqueNamesProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.provision.RouteTlsProvisioner; import org.slf4j.Logger; @@ -66,6 +67,7 @@ public class OpenShiftEnvironmentProvisioner private final ProxySettingsProvisioner proxySettingsProvisioner; private final ServiceAccountProvisioner serviceAccountProvisioner; private final CertificateProvisioner certificateProvisioner; + private final OpenShiftCommandProvisioner commandProvisioner; @Inject public OpenShiftEnvironmentProvisioner( @@ -83,7 +85,8 @@ public OpenShiftEnvironmentProvisioner( ImagePullSecretProvisioner imagePullSecretProvisioner, ProxySettingsProvisioner proxySettingsProvisioner, ServiceAccountProvisioner serviceAccountProvisioner, - CertificateProvisioner certificateProvisioner) { + CertificateProvisioner certificateProvisioner, + OpenShiftCommandProvisioner commandProvisioner) { this.pvcEnabled = pvcEnabled; this.volumesStrategy = volumesStrategy; this.uniqueNamesProvisioner = uniqueNamesProvisioner; @@ -99,6 +102,7 @@ public OpenShiftEnvironmentProvisioner( this.proxySettingsProvisioner = proxySettingsProvisioner; this.serviceAccountProvisioner = serviceAccountProvisioner; this.certificateProvisioner = certificateProvisioner; + this.commandProvisioner = commandProvisioner; } @Override @@ -133,6 +137,7 @@ public void provision(OpenShiftEnvironment osEnv, RuntimeIdentity identity) proxySettingsProvisioner.provision(osEnv, identity); serviceAccountProvisioner.provision(osEnv, identity); certificateProvisioner.provision(osEnv, identity); + commandProvisioner.provision(osEnv, identity); LOG.debug( "Provisioning OpenShift environment done for workspace '{}'", identity.getWorkspaceId()); } diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftCommandProvisioner.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftCommandProvisioner.java new file mode 100644 index 00000000000..c350d988e9c --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftCommandProvisioner.java @@ -0,0 +1,98 @@ +/* + * 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.openshift.provision; + +import static org.eclipse.che.api.workspace.shared.Constants.ARBITRARY_USER_ATTRIBUTE; +import static org.eclipse.che.api.workspace.shared.Constants.CONTAINER_SOURCE_ATTRIBUTE; +import static org.eclipse.che.api.workspace.shared.Constants.RECIPE_CONTAINER_SOURCE; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import io.fabric8.kubernetes.api.model.Container; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; +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.Names; +import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; +import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ConfigurationProvisioner; +import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; + +public class OpenShiftCommandProvisioner implements ConfigurationProvisioner { + + @VisibleForTesting protected static final List SHELL_BINARY = ImmutableList.of("/bin/sh"); + @VisibleForTesting protected static final String SHELL_ARGS = "-c"; + + @VisibleForTesting + protected static final String ADD_USER_COMMAND = + "if ! whoami &> /dev/null && [ -w /etc/passwd ]; then " + + "echo \"user:x:$(id -u):0:user user:projects/:/bin/bash\" >> /etc/passwd;" + + "fi;"; + + @VisibleForTesting protected static final String COMMAND_FORMAT = "%s %s %s"; + + @Override + public void provision(OpenShiftEnvironment osEnv, RuntimeIdentity identity) + throws InfrastructureException { + + if (!supportArbitraryUser(osEnv.getAttributes())) { + return; + } + + Set recipeMachineNames = + osEnv + .getMachines() + .entrySet() + .stream() + .filter( + e -> + RECIPE_CONTAINER_SOURCE.equals( + e.getValue().getAttributes().get(CONTAINER_SOURCE_ATTRIBUTE))) + .map(Entry::getKey) + .collect(Collectors.toSet()); + + for (PodData podData : osEnv.getPodsData().values()) { + for (Container container : podData.getSpec().getContainers()) { + String machineName = Names.machineName(podData, container); + if (recipeMachineNames.contains(machineName)) { + rewriteContainerCommand(container); + } + } + } + } + + private void rewriteContainerCommand(Container container) { + List defaultCommand = container.getCommand(); + List defaultArgs = container.getArgs(); + + if (defaultCommand == null || defaultCommand.size() == 0) { + return; + } + + String script = + String.format( + COMMAND_FORMAT, + ADD_USER_COMMAND, + String.join(" ", defaultCommand), + String.join(" ", defaultArgs)); + container.setCommand(SHELL_BINARY); + container.setArgs(ImmutableList.of(SHELL_ARGS, script)); + } + + private boolean supportArbitraryUser(Map workspaceAttributes) { + String supportArbitraryUser = workspaceAttributes.get(ARBITRARY_USER_ATTRIBUTE); + return "true".equals(supportArbitraryUser); + } +} diff --git a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisionerTest.java b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisionerTest.java index 1f53a955817..c7ebb70f94e 100644 --- a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisionerTest.java +++ b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisionerTest.java @@ -28,6 +28,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy.RestartPolicyRewriter; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.server.ServersConverter; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; +import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftCommandProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftUniqueNamesProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.provision.RouteTlsProvisioner; import org.mockito.InOrder; @@ -61,6 +62,7 @@ public class OpenShiftEnvironmentProvisionerTest { @Mock private ProxySettingsProvisioner proxySettingsProvisioner; @Mock private ServiceAccountProvisioner serviceAccountProvisioner; @Mock private CertificateProvisioner certificateProvisioner; + @Mock private OpenShiftCommandProvisioner commandProvisioner; private OpenShiftEnvironmentProvisioner osInfraProvisioner; @@ -84,7 +86,8 @@ public void setUp() { imagePullSecretProvisioner, proxySettingsProvisioner, serviceAccountProvisioner, - certificateProvisioner); + certificateProvisioner, + commandProvisioner); provisionOrder = inOrder( installerServersPortProvisioner, @@ -100,7 +103,8 @@ public void setUp() { imagePullSecretProvisioner, proxySettingsProvisioner, serviceAccountProvisioner, - certificateProvisioner); + certificateProvisioner, + commandProvisioner); } @Test @@ -125,6 +129,7 @@ public void performsOrderedProvisioning() throws Exception { provisionOrder.verify(proxySettingsProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verify(serviceAccountProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verify(certificateProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); + provisionOrder.verify(commandProvisioner).provision(eq(osEnv), eq(runtimeIdentity)); provisionOrder.verifyNoMoreInteractions(); } } diff --git a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftCommandProvisionerTest.java b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftCommandProvisionerTest.java new file mode 100644 index 00000000000..d41b7704686 --- /dev/null +++ b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftCommandProvisionerTest.java @@ -0,0 +1,252 @@ +/* + * 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.openshift.provision; + +import static org.eclipse.che.api.workspace.shared.Constants.ARBITRARY_USER_ATTRIBUTE; +import static org.eclipse.che.api.workspace.shared.Constants.CONTAINER_SOURCE_ATTRIBUTE; +import static org.eclipse.che.api.workspace.shared.Constants.RECIPE_CONTAINER_SOURCE; +import static org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftCommandProvisioner.ADD_USER_COMMAND; +import static org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftCommandProvisioner.COMMAND_FORMAT; +import static org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftCommandProvisioner.SHELL_ARGS; +import static org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftCommandProvisioner.SHELL_BINARY; +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.assertNull; +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.Pod; +import io.fabric8.kubernetes.api.model.PodBuilder; +import java.util.Collections; +import java.util.List; +import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; +import org.eclipse.che.workspace.infrastructure.kubernetes.Names; +import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData; +import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; +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 OpenShiftCommandProvisionerTest { + + private static String POD_NAME = "testPod"; + private static String RECIPE_CONTAINER_NAME = "recipeContainer"; + private static String SIDECAR_CONTAINER_NAME = "nonRecipeContainer"; + + private static List CONTAINER_COMMAND = ImmutableList.of("testCommand"); + private static List CONTAINER_ARGS = ImmutableList.of("test", "args"); + + private static List UPDATED_COMMAND = + ImmutableList.of( + SHELL_ARGS, + String.format( + COMMAND_FORMAT, + ADD_USER_COMMAND, + String.join(" ", CONTAINER_COMMAND), + String.join(" ", CONTAINER_ARGS))); + + @Mock private OpenShiftEnvironment osEnv; + @Mock private RuntimeIdentity runtimeIdentity; + + @Mock private InternalMachineConfig recipeMachine; + @Mock private InternalMachineConfig sidecarMachine; + + Container recipeContainer; + Container sidecarContainer; + PodData podData; + + private OpenShiftCommandProvisioner provisioner; + + @BeforeMethod + public void setup() { + recipeContainer = buildContainer(RECIPE_CONTAINER_NAME); + sidecarContainer = buildContainer(SIDECAR_CONTAINER_NAME); + + podData = buildPodData(POD_NAME, recipeContainer, sidecarContainer); + when(osEnv.getPodsData()).thenReturn(ImmutableMap.of(POD_NAME, podData)); + + when(recipeMachine.getAttributes()) + .thenReturn(ImmutableMap.of(CONTAINER_SOURCE_ATTRIBUTE, RECIPE_CONTAINER_SOURCE)); + when(sidecarMachine.getAttributes()) + .thenReturn(ImmutableMap.of(CONTAINER_SOURCE_ATTRIBUTE, "plugin")); + + when(osEnv.getMachines()) + .thenReturn( + ImmutableMap.of( + Names.machineName(podData, recipeContainer), + recipeMachine, + Names.machineName(podData, sidecarContainer), + sidecarMachine)); + + when(osEnv.getPodsData()).thenReturn(ImmutableMap.of(POD_NAME, podData)); + + this.provisioner = new OpenShiftCommandProvisioner(); + } + + @Test + public void shouldDoNothingWhenAttributeNotPresent() throws Exception { + when(osEnv.getAttributes()).thenReturn(Collections.emptyMap()); + + provisioner.provision(osEnv, runtimeIdentity); + + verify(osEnv).getAttributes(); + verifyNoMoreInteractions(osEnv); + } + + @Test + public void shouldDoNothingWhenAttributeIsFalse() throws Exception { + when(osEnv.getAttributes()).thenReturn(ImmutableMap.of(ARBITRARY_USER_ATTRIBUTE, "false")); + + provisioner.provision(osEnv, runtimeIdentity); + + verify(osEnv).getAttributes(); + verifyNoMoreInteractions(osEnv); + } + + @Test + public void shouldUpdateCommandAndArgsForRecipeMachine() throws Exception { + when(osEnv.getAttributes()).thenReturn(ImmutableMap.of(ARBITRARY_USER_ATTRIBUTE, "true")); + + provisioner.provision(osEnv, runtimeIdentity); + + PodData actualPod = osEnv.getPodsData().get(POD_NAME); + Container actual = + actualPod + .getSpec() + .getContainers() + .stream() + .filter(c -> c.getName().equals(RECIPE_CONTAINER_NAME)) + .findFirst() + .get(); + + assertEquals(actual.getName(), RECIPE_CONTAINER_NAME, "Should not modify container name"); + assertEquals(actual.getCommand(), SHELL_BINARY, "Should update container Command"); + assertEquals(actual.getArgs(), UPDATED_COMMAND, "Should update conatiner Args"); + } + + @Test + public void shouldDoNothingForSidecarMachine() throws Exception { + when(osEnv.getAttributes()).thenReturn(ImmutableMap.of(ARBITRARY_USER_ATTRIBUTE, "true")); + + provisioner.provision(osEnv, runtimeIdentity); + + PodData actualPod = osEnv.getPodsData().get(POD_NAME); + Container actual = + actualPod + .getSpec() + .getContainers() + .stream() + .filter(c -> c.getName().equals(SIDECAR_CONTAINER_NAME)) + .findFirst() + .get(); + + assertEquals(actual.getName(), SIDECAR_CONTAINER_NAME); + assertEquals( + actual.getCommand(), CONTAINER_COMMAND, "Should not change non-recipe container Command"); + assertEquals(actual.getArgs(), CONTAINER_ARGS, "Should not change non-recipe container Args"); + } + + @Test + public void shouldNotRewriteWhenOriginalCommandIsNull() throws Exception { + when(osEnv.getAttributes()).thenReturn(ImmutableMap.of(ARBITRARY_USER_ATTRIBUTE, "true")); + Container noCommand = buildContainer(RECIPE_CONTAINER_NAME); + PodData podData = buildPodData(POD_NAME, noCommand, sidecarContainer); + podData + .getSpec() + .getContainers() + .stream() + .filter(c -> RECIPE_CONTAINER_NAME.equals(c.getName())) + .forEach( + c -> { + c.setCommand(null); + c.setArgs(null); + }); + when(osEnv.getPodsData()).thenReturn(ImmutableMap.of(POD_NAME, podData)); + + provisioner.provision(osEnv, runtimeIdentity); + + PodData actualPod = osEnv.getPodsData().get(POD_NAME); + Container actual = + actualPod + .getSpec() + .getContainers() + .stream() + .filter(c -> c.getName().equals(RECIPE_CONTAINER_NAME)) + .findFirst() + .get(); + + assertEquals(actual.getName(), RECIPE_CONTAINER_NAME); + assertNull( + actual.getCommand(), "Should not do anything to command when container command is null"); + assertNull(actual.getArgs(), "Should not do anything to args when container command is null"); + } + + @Test + public void shouldNotRewriteWhenOriginalCommandIsEmpty() throws Exception { + when(osEnv.getAttributes()).thenReturn(ImmutableMap.of(ARBITRARY_USER_ATTRIBUTE, "true")); + Container noCommand = buildContainer(RECIPE_CONTAINER_NAME); + noCommand.setCommand(Collections.emptyList()); + noCommand.setArgs(Collections.emptyList()); + PodData podData = buildPodData(POD_NAME, noCommand, sidecarContainer); + when(osEnv.getPodsData()).thenReturn(ImmutableMap.of(POD_NAME, podData)); + + provisioner.provision(osEnv, runtimeIdentity); + + PodData actualPod = osEnv.getPodsData().get(POD_NAME); + Container actual = + actualPod + .getSpec() + .getContainers() + .stream() + .filter(c -> c.getName().equals(RECIPE_CONTAINER_NAME)) + .findFirst() + .get(); + + assertEquals(actual.getName(), RECIPE_CONTAINER_NAME); + assertTrue( + actual.getCommand().isEmpty(), + "Should not do anything to command when container command is empty"); + assertTrue( + actual.getArgs().isEmpty(), + "Should not do anything to args when container command is empty"); + } + + private Container buildContainer(String name) { + return new ContainerBuilder() + .withName(name) + .withCommand(CONTAINER_COMMAND) + .withArgs(CONTAINER_ARGS) + .build(); + } + + private PodData buildPodData(String name, Container... containers) { + Pod pod = + new PodBuilder() + .withNewMetadata() + .withName(name) + .endMetadata() + .withNewSpec() + .withContainers(containers) + .endSpec() + .build(); + return new PodData(pod); + } +} diff --git a/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/Constants.java b/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/Constants.java index db0d669f68e..c5e8f3a9347 100644 --- a/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/Constants.java +++ b/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/Constants.java @@ -106,6 +106,26 @@ public final class Constants { */ public static final String PERSIST_VOLUMES_ATTRIBUTE = "persistVolumes"; + /** + * This attribute configures a workspace running on OpenShift to attempt to work around the + * OpenShift default of starting containers with an arbitrary UID. Should be set/read from {@link + * WorkspaceConfig#getAttributes()}. + * + *

Value is expected to be boolean; if it is set to 'true', then the recipe containers in the + * the workspace will have their entrypoint overridden to attempt to add an entry for the current + * user ID to the /etc/passwd file. If this fails for whatever reason, the original entrypoint + * should still be executed. + * + *

In general this attribute requires some modifications to the container being used to run the + * workspace to have any effect; containers running on OpenShift should set the /etc/passwd + * directory to be writable by the root group. + * + * @see + * OpenShift docs + */ + public static final String ARBITRARY_USER_ATTRIBUTE = "supportArbitraryUser"; + /** * Contains a list of workspace tooling plugins that should be used in a workspace. Should be * set/read from {@link WorkspaceConfig#getAttributes}. diff --git a/wsmaster/che-core-api-workspace/README-devfile.md b/wsmaster/che-core-api-workspace/README-devfile.md index 1d1f83f6391..4299d4374d6 100644 --- a/wsmaster/che-core-api-workspace/README-devfile.md +++ b/wsmaster/che-core-api-workspace/README-devfile.md @@ -337,6 +337,31 @@ attributes: persistVolumes: false ``` +#### Arbitrary user ID support +Containers running on OpenShift start using an arbitrarily assigned user ID. This means that the current user in the workspace will not have an entry in the `/etc/passwd` file, and thus not have an associated username or home directory. + +This can cause issues when certain tools depend on having a username or home directory; for example, maven will have trouble placing the `.m2` directory when there is no username (unless otherwise configured), and the terminal may have issues with autocompletion, keybindings, etc. + +When attribute `supportArbitraryUser` is set to `true`, workspaces running on OpenShift will have their main containers' command and arguments (i.e. their entrypoint) overwritten to add the current user ID to the `/etc/passwd` file. If this step fails (e.g. due to `/etc/passwd` not being writable by the root group), this attribute has no effect on the workspace; the original entrypoint will still execute. + +This attribute also depends on the main workspace container having a `command` and `args` defined in the devfile (i.e. it cannot depend on the underlying container's entrypoint). + +The default value for this attribute is `false`, and absence of this attribute is interpreted accordingly. + +Example devfile: +```yaml +apiVersion: 1.0.0 +metadata: + name: petclinic-dev-environment +projects: + - name: petclinic + source: + type: git + location: 'https://github.com/che-samples/web-java-spring-petclinic.git' +attributes: + supportArbitraryUser: true +``` + ### Live working examples - [NodeJS simple "Hello World" example](https://che.openshift.io/f?url=https://raw.githubusercontent.com/redhat-developer/devfile/master/samples/web-nodejs-sample/devfile.yaml) diff --git a/wsmaster/che-core-api-workspace/src/main/resources/schema/devfile.json b/wsmaster/che-core-api-workspace/src/main/resources/schema/devfile.json index 5bfbcb950ad..96cdbb625e5 100644 --- a/wsmaster/che-core-api-workspace/src/main/resources/schema/devfile.json +++ b/wsmaster/che-core-api-workspace/src/main/resources/schema/devfile.json @@ -712,6 +712,11 @@ "description": "Defines whether volumes should be stored or not. Defaults to `true`. In case of `false` workspace volumes will be created as `emptyDir`. The data in the `emptyDir` volume is deleted forever when a workspace Pod is removed for any reason(pod is crashed, workspace is restarted).", "default": "true" }, + "supportArbitraryUser": { + "type": "boolean", + "description": "Defines whether Che should modify containers to support running as an arbitrary user ID on OpenShift. Defaults to `false`. If `true`, workspace recipe containers will have their command and args overwritten to add a user entry to /etc/passwd.", + "default": "false" + }, "additionalProperties": { "type": "string" },