Merge pull request #117 from xbaran4/user-secrets

Propagate Che user informations and preferences to DevWorkspaces
pull/102/head
Pavol Baran 2021-09-29 13:30:03 +02:00 committed by GitHub
commit 738a663e72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 606 additions and 27 deletions

View File

@ -46,5 +46,13 @@ public final class Constants {
public static final String POD_STATUS_PHASE_FAILED = "Failed";
public static final String POD_STATUS_PHASE_SUCCEEDED = "Succeeded";
/** DevWorkspace labels and annotations for mounting secrets and configmaps. */
public static final String DEV_WORKSPACE_MOUNT_LABEL =
"controller.devfile.io/mount-to-devworkspace";
public static final String DEV_WORKSPACE_MOUNT_PATH_ANNOTATION =
"controller.devfile.io/mount-path";
public static final String DEV_WORKSPACE_MOUNT_AS_ANNOTATION = "controller.devfile.io/mount-as";
private Constants() {}
}

View File

@ -48,6 +48,9 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesDev
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.RemoveNamespaceOnWorkspaceRemove;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserPreferencesConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserProfileConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.CommonPVCStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.PerWorkspacePVCStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.UniqueWorkspacePVCStrategy;
@ -98,6 +101,11 @@ public class KubernetesInfraModule extends AbstractModule {
workspaceAttributeValidators.addBinding().to(K8sInfraNamespaceWsAttributeValidator.class);
workspaceAttributeValidators.addBinding().to(AsyncStorageModeValidator.class);
Multibinder<NamespaceConfigurator> namespaceConfigurators =
Multibinder.newSetBinder(binder(), NamespaceConfigurator.class);
namespaceConfigurators.addBinding().to(UserProfileConfigurator.class);
namespaceConfigurators.addBinding().to(UserPreferencesConfigurator.class);
bind(KubernetesNamespaceService.class);
MapBinder<String, InternalEnvironmentFactory> factories =

View File

@ -35,6 +35,7 @@ import org.eclipse.che.dto.server.DtoFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.KubernetesNamespaceMetaDto;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.NamespaceProvisioner;
/** @author Sergii Leshchenko */
@Tag(name = "kubernetes-namespace", description = "Kubernetes REST API for working with Namespaces")
@ -43,10 +44,13 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesN
public class KubernetesNamespaceService extends Service {
private final KubernetesNamespaceFactory namespaceFactory;
private final NamespaceProvisioner namespaceProvisioner;
@Inject
public KubernetesNamespaceService(KubernetesNamespaceFactory namespaceFactory) {
public KubernetesNamespaceService(
KubernetesNamespaceFactory namespaceFactory, NamespaceProvisioner namespaceProvisioner) {
this.namespaceFactory = namespaceFactory;
this.namespaceProvisioner = namespaceProvisioner;
}
@GET
@ -88,7 +92,7 @@ public class KubernetesNamespaceService extends Service {
})
public KubernetesNamespaceMetaDto provision() throws InfrastructureException {
return asDto(
namespaceFactory.provision(
namespaceProvisioner.provision(
new NamespaceResolutionContext(EnvironmentContext.getCurrent().getSubject())));
}

View File

@ -53,7 +53,6 @@ import org.eclipse.che.api.core.model.workspace.Workspace;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.user.server.PreferenceManager;
import org.eclipse.che.api.user.server.UserManager;
import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.commons.annotation.Nullable;
@ -255,7 +254,7 @@ public class KubernetesNamespaceFactory {
* @return optional with kubernetes namespace meta
* @throws InfrastructureException when any error occurs during namespace fetching
*/
protected Optional<KubernetesNamespaceMeta> fetchNamespace(String name)
public Optional<KubernetesNamespaceMeta> fetchNamespace(String name)
throws InfrastructureException {
try {
Namespace namespace = clientFactory.create().namespaces().withName(name).get();
@ -372,21 +371,6 @@ public class KubernetesNamespaceFactory {
return namespace;
}
public KubernetesNamespaceMeta provision(NamespaceResolutionContext namespaceResolutionContext)
throws InfrastructureException {
KubernetesNamespace namespace =
getOrCreate(
new RuntimeIdentityImpl(
null,
null,
namespaceResolutionContext.getUserId(),
evaluateNamespaceName(namespaceResolutionContext)));
return fetchNamespace(namespace.getName())
.orElseThrow(
() -> new InfrastructureException("Not able to find namespace " + namespace.getName()));
}
public KubernetesNamespace get(RuntimeIdentity identity) throws InfrastructureException {
String workspaceId = identity.getWorkspaceId();
String namespaceName = identity.getInfrastructureNamespace();

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2012-2021 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.namespace.configurator;
import io.fabric8.kubernetes.api.model.Secret;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.NamespaceProvisioner;
/**
* Configures user's namespace after provisioning in {@link NamespaceProvisioner} with whatever is
* needed. Such as creating user profile and preferences {@link Secret} in user namespace.
*
* @author Pavol Baran
*/
public interface NamespaceConfigurator {
/**
* Configures user's namespace after provisioning.
*
* @param namespaceResolutionContext users namespace context
* @throws InfrastructureException when any error occurs
*/
public void configure(NamespaceResolutionContext namespaceResolutionContext)
throws InfrastructureException;
}

View File

@ -0,0 +1,135 @@
/*
* Copyright (c) 2012-2021 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.namespace.configurator;
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_MOUNT_AS_ANNOTATION;
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_MOUNT_LABEL;
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_MOUNT_PATH_ANNOTATION;
import com.google.common.annotations.VisibleForTesting;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.client.KubernetesClientException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.model.user.User;
import org.eclipse.che.api.user.server.PreferenceManager;
import org.eclipse.che.api.user.server.UserManager;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
/**
* Creates {@link Secret} with user preferences. This serves as a way for DevWorkspaces to acquire
* information about the user.
*
* @author Pavol Baran
*/
public class UserPreferencesConfigurator implements NamespaceConfigurator {
private static final String USER_PREFERENCES_SECRET_NAME = "user-preferences";
private static final String USER_PREFERENCES_SECRET_MOUNT_PATH = "/config/user/preferences";
private static final int PREFERENCE_NAME_MAX_LENGTH = 253;
private final KubernetesNamespaceFactory namespaceFactory;
private final KubernetesClientFactory clientFactory;
private final UserManager userManager;
private final PreferenceManager preferenceManager;
@Inject
public UserPreferencesConfigurator(
KubernetesNamespaceFactory namespaceFactory,
KubernetesClientFactory clientFactory,
UserManager userManager,
PreferenceManager preferenceManager) {
this.namespaceFactory = namespaceFactory;
this.clientFactory = clientFactory;
this.userManager = userManager;
this.preferenceManager = preferenceManager;
}
@Override
public void configure(NamespaceResolutionContext namespaceResolutionContext)
throws InfrastructureException {
Secret userPreferencesSecret = preparePreferencesSecret(namespaceResolutionContext);
String namespace = namespaceFactory.evaluateNamespaceName(namespaceResolutionContext);
try {
clientFactory
.create()
.secrets()
.inNamespace(namespace)
.createOrReplace(userPreferencesSecret);
} catch (KubernetesClientException e) {
throw new InfrastructureException(
"Error occurred while trying to create user preferences secret.", e);
}
}
private Secret preparePreferencesSecret(NamespaceResolutionContext namespaceResolutionContext)
throws InfrastructureException {
Base64.Encoder enc = Base64.getEncoder();
User user;
Map<String, String> preferences;
try {
user = userManager.getById(namespaceResolutionContext.getUserId());
preferences = preferenceManager.find(user.getId());
} catch (NotFoundException | ServerException e) {
throw new InfrastructureException(
String.format(
"Preferences of user with id:%s cannot be retrieved.",
namespaceResolutionContext.getUserId()),
e);
}
if (preferences == null || preferences.isEmpty()) {
throw new InfrastructureException(
String.format(
"Preferences of user with id:%s are empty. Cannot create user preferences secrets.",
namespaceResolutionContext.getUserId()));
}
Map<String, String> preferencesEncoded = new HashMap<>();
preferences.forEach(
(key, value) ->
preferencesEncoded.put(
normalizePreferenceName(key), enc.encodeToString(value.getBytes())));
return new SecretBuilder()
.addToData(preferencesEncoded)
.withNewMetadata()
.withName(USER_PREFERENCES_SECRET_NAME)
.addToLabels(DEV_WORKSPACE_MOUNT_LABEL, "true")
.addToAnnotations(DEV_WORKSPACE_MOUNT_AS_ANNOTATION, "file")
.addToAnnotations(DEV_WORKSPACE_MOUNT_PATH_ANNOTATION, USER_PREFERENCES_SECRET_MOUNT_PATH)
.endMetadata()
.build();
}
/**
* Some preferences names are not compatible with k8s restrictions on key field in secret. The
* keys of data must consist of alphanumeric characters, -, _ or . This method replaces illegal
* characters with -
*
* @param name original preference name
* @return k8s compatible preference name used as a key field in Secret
*/
@VisibleForTesting
String normalizePreferenceName(String name) {
name = name.replaceAll("[^-._a-zA-Z0-9]+", "-").replaceAll("-+", "-");
return name.substring(0, Math.min(name.length(), PREFERENCE_NAME_MAX_LENGTH));
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright (c) 2012-2021 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.namespace.configurator;
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_MOUNT_AS_ANNOTATION;
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_MOUNT_LABEL;
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.DEV_WORKSPACE_MOUNT_PATH_ANNOTATION;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.client.KubernetesClientException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.model.user.User;
import org.eclipse.che.api.user.server.UserManager;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
/**
* Creates {@link Secret} with user profile information such as his id, name and email. This serves
* as a way for DevWorkspaces to acquire information about the user.
*
* @author Pavol Baran
*/
public class UserProfileConfigurator implements NamespaceConfigurator {
private static final String USER_PROFILE_SECRET_NAME = "user-profile";
private static final String USER_PROFILE_SECRET_MOUNT_PATH = "/config/user/profile";
private final KubernetesNamespaceFactory namespaceFactory;
private final KubernetesClientFactory clientFactory;
private final UserManager userManager;
@Inject
public UserProfileConfigurator(
KubernetesNamespaceFactory namespaceFactory,
KubernetesClientFactory clientFactory,
UserManager userManager) {
this.namespaceFactory = namespaceFactory;
this.clientFactory = clientFactory;
this.userManager = userManager;
}
@Override
public void configure(NamespaceResolutionContext namespaceResolutionContext)
throws InfrastructureException {
Secret userProfileSecret = prepareProfileSecret(namespaceResolutionContext);
String namespace = namespaceFactory.evaluateNamespaceName(namespaceResolutionContext);
try {
clientFactory.create().secrets().inNamespace(namespace).createOrReplace(userProfileSecret);
} catch (KubernetesClientException e) {
throw new InfrastructureException(
"Error occurred while trying to create user profile secret.", e);
}
}
private Secret prepareProfileSecret(NamespaceResolutionContext namespaceResolutionContext)
throws InfrastructureException {
User user;
try {
user = userManager.getById(namespaceResolutionContext.getUserId());
} catch (NotFoundException | ServerException e) {
throw new InfrastructureException(
String.format(
"Could not find current user with id:%s.", namespaceResolutionContext.getUserId()),
e);
}
Base64.Encoder enc = Base64.getEncoder();
final Map<String, String> userProfileData = new HashMap<>();
userProfileData.put("id", enc.encodeToString(user.getId().getBytes()));
userProfileData.put("name", enc.encodeToString(user.getName().getBytes()));
userProfileData.put("email", enc.encodeToString(user.getEmail().getBytes()));
return new SecretBuilder()
.addToData(userProfileData)
.withNewMetadata()
.withName(USER_PROFILE_SECRET_NAME)
.addToLabels(DEV_WORKSPACE_MOUNT_LABEL, "true")
.addToAnnotations(DEV_WORKSPACE_MOUNT_AS_ANNOTATION, "file")
.addToAnnotations(DEV_WORKSPACE_MOUNT_PATH_ANNOTATION, USER_PROFILE_SECRET_MOUNT_PATH)
.endMetadata()
.build();
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2012-2021 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 io.fabric8.kubernetes.api.model.Namespace;
import java.util.Set;
import javax.inject.Inject;
import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
/**
* Provisions the k8s {@link Namespace}. After provisioning, configures the namespace through {@link
* NamespaceConfigurator}.
*
* @author Pavol Baran
*/
public class NamespaceProvisioner {
private final KubernetesNamespaceFactory namespaceFactory;
private final Set<NamespaceConfigurator> namespaceConfigurators;
@Inject
public NamespaceProvisioner(
KubernetesNamespaceFactory namespaceFactory,
Set<NamespaceConfigurator> namespaceConfigurators) {
this.namespaceFactory = namespaceFactory;
this.namespaceConfigurators = namespaceConfigurators;
}
/** Tests for this method are in KubernetesFactoryTest. */
public KubernetesNamespaceMeta provision(NamespaceResolutionContext namespaceResolutionContext)
throws InfrastructureException {
KubernetesNamespace namespace =
namespaceFactory.getOrCreate(
new RuntimeIdentityImpl(
null,
null,
namespaceResolutionContext.getUserId(),
namespaceFactory.evaluateNamespaceName(namespaceResolutionContext)));
KubernetesNamespaceMeta namespaceMeta =
namespaceFactory
.fetchNamespace(namespace.getName())
.orElseThrow(
() ->
new InfrastructureException(
"Not able to find namespace " + namespace.getName()));
configureNamespace(namespaceResolutionContext);
return namespaceMeta;
}
private void configureNamespace(NamespaceResolutionContext namespaceResolutionContext)
throws InfrastructureException {
for (NamespaceConfigurator configurator : namespaceConfigurators) {
configurator.configure(namespaceResolutionContext);
}
}
}

View File

@ -35,6 +35,7 @@ import org.eclipse.che.dto.server.DtoFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.dto.KubernetesNamespaceMetaDto;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.NamespaceProvisioner;
import org.everrest.assured.EverrestJetty;
import org.everrest.core.Filter;
import org.everrest.core.GenericContainerRequest;
@ -67,6 +68,7 @@ public class KubernetesNamespaceServiceTest {
private CheJsonProvider jsonProvider = new CheJsonProvider(Collections.emptySet());
@Mock private KubernetesNamespaceFactory namespaceFactory;
@Mock private NamespaceProvisioner namespaceProvisioner;
@InjectMocks private KubernetesNamespaceService service;
@ -98,7 +100,7 @@ public class KubernetesNamespaceServiceTest {
KubernetesNamespaceMetaImpl namespaceMeta =
new KubernetesNamespaceMetaImpl(
"ws-namespace", ImmutableMap.of("phase", "active", "default", "true"));
when(namespaceFactory.provision(any(NamespaceResolutionContext.class)))
when(namespaceProvisioner.provision(any(NamespaceResolutionContext.class)))
.thenReturn(namespaceMeta);
// when
final Response response =
@ -113,7 +115,7 @@ public class KubernetesNamespaceServiceTest {
KubernetesNamespaceMetaDto actual = unwrapDto(response, KubernetesNamespaceMetaDto.class);
assertEquals(actual.getName(), namespaceMeta.getName());
assertEquals(actual.getAttributes(), namespaceMeta.getAttributes());
verify(namespaceFactory).provision(any(NamespaceResolutionContext.class));
verify(namespaceProvisioner).provision(any(NamespaceResolutionContext.class));
}
@Test
@ -122,7 +124,7 @@ public class KubernetesNamespaceServiceTest {
KubernetesNamespaceMetaImpl namespaceMeta =
new KubernetesNamespaceMetaImpl(
"ws-namespace", ImmutableMap.of("phase", "active", "default", "true"));
when(namespaceFactory.provision(any(NamespaceResolutionContext.class)))
when(namespaceProvisioner.provision(any(NamespaceResolutionContext.class)))
.thenReturn(namespaceMeta);
// when
final Response response =
@ -136,7 +138,7 @@ public class KubernetesNamespaceServiceTest {
assertEquals(response.getStatusCode(), 200);
ArgumentCaptor<NamespaceResolutionContext> captor =
ArgumentCaptor.forClass(NamespaceResolutionContext.class);
verify(namespaceFactory).provision(captor.capture());
verify(namespaceProvisioner).provision(captor.capture());
NamespaceResolutionContext actualContext = captor.getValue();
assertEquals(actualContext.getUserId(), SUBJECT.getUserId());
assertEquals(actualContext.getUserName(), SUBJECT.getUserName());

View File

@ -12,6 +12,7 @@
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.DEFAULT_ATTRIBUTE;
@ -86,6 +87,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesCl
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.NamespaceProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@ -1248,7 +1250,7 @@ public class KubernetesNamespaceFactoryTest {
// when
NamespaceResolutionContext context =
new NamespaceResolutionContext("workspace123", "user123", "jondoe");
KubernetesNamespaceMeta actual = namespaceFactory.provision(context);
KubernetesNamespaceMeta actual = testProvisioning(context);
// then
assertEquals(actual.getName(), "jondoe-che");
@ -1288,7 +1290,7 @@ public class KubernetesNamespaceFactoryTest {
// when
NamespaceResolutionContext context =
new NamespaceResolutionContext("workspace123", "user123", "jondoe");
namespaceFactory.provision(context);
testProvisioning(context);
// then
fail("should not reach this point since exception has to be thrown");
@ -1329,7 +1331,8 @@ public class KubernetesNamespaceFactoryTest {
// when
NamespaceResolutionContext context =
new NamespaceResolutionContext("workspace123", "user123", "jondoe");
namespaceFactory.provision(context);
testProvisioning(context);
// then
fail("should not reach this point since exception has to be thrown");
@ -1535,4 +1538,9 @@ public class KubernetesNamespaceFactoryTest {
.endStatus()
.build();
}
private KubernetesNamespaceMeta testProvisioning(NamespaceResolutionContext context)
throws InfrastructureException {
return new NamespaceProvisioner(namespaceFactory, emptySet()).provision(context);
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright (c) 2012-2021 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.namespace.configurator;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.user.server.PreferenceManager;
import org.eclipse.che.api.user.server.UserManager;
import org.eclipse.che.api.user.server.model.impl.UserImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
/**
* Tests for {@link UserPreferencesConfigurator}.
*
* @author Pavol Baran
*/
@Listeners(MockitoTestNGListener.class)
public class UserPreferencesConfiguratorTest {
private static final String USER_ID = "user-id";
private static final String USER_NAME = "user-name";
private static final String USER_EMAIL = "user-email";
private static final String USER_NAMESPACE = "user-namespace";
@Mock private KubernetesNamespaceFactory namespaceFactory;
@Mock private KubernetesClientFactory clientFactory;
@Mock private UserManager userManager;
@Mock private PreferenceManager preferenceManager;
@InjectMocks private UserPreferencesConfigurator userPreferencesConfigurator;
private KubernetesServer kubernetesServer;
private NamespaceResolutionContext context;
@BeforeMethod
public void setUp() throws InfrastructureException, NotFoundException, ServerException {
context = new NamespaceResolutionContext(null, USER_ID, USER_NAME);
kubernetesServer = new KubernetesServer(true, true);
kubernetesServer.before();
Map<String, String> preferences = new HashMap<>();
preferences.put("preference-name", "preference");
lenient()
.when(userManager.getById(USER_ID))
.thenReturn(new UserImpl(USER_ID, USER_EMAIL, USER_NAME));
lenient().when(namespaceFactory.evaluateNamespaceName(any())).thenReturn(USER_NAMESPACE);
lenient().when(clientFactory.create()).thenReturn(kubernetesServer.getClient());
lenient().when(preferenceManager.find(USER_ID)).thenReturn(preferences);
}
@AfterMethod
public void cleanUp() {
kubernetesServer.after();
}
@Test
public void shouldCreatePreferencesSecret() throws InfrastructureException {
userPreferencesConfigurator.configure(context);
List<Secret> secrets =
kubernetesServer.getClient().secrets().inNamespace(USER_NAMESPACE).list().getItems();
assertEquals(secrets.size(), 1);
assertEquals(secrets.get(0).getMetadata().getName(), "user-preferences");
}
@Test(
expectedExceptions = InfrastructureException.class,
expectedExceptionsMessageRegExp =
"Preferences of user with id:" + USER_ID + " cannot be retrieved.")
public void shouldNotCreateSecretOnException() throws ServerException, InfrastructureException {
when(preferenceManager.find(USER_ID)).thenThrow(new ServerException("test exception"));
userPreferencesConfigurator.configure(context);
fail("InfrastructureException should have been thrown.");
}
@Test
public void shouldNormalizePreferenceName() {
assertEquals(
userPreferencesConfigurator.normalizePreferenceName("codename:bond"), "codename-bond");
assertEquals(userPreferencesConfigurator.normalizePreferenceName("some--:pref"), "some-pref");
assertEquals(
userPreferencesConfigurator.normalizePreferenceName("pref[name].sub"), "pref-name-.sub");
}
@Test
public void shouldKeepPreferenceName() {
assertEquals(
userPreferencesConfigurator.normalizePreferenceName("codename.bond"), "codename.bond");
assertEquals(userPreferencesConfigurator.normalizePreferenceName("pref_name"), "pref_name");
assertEquals(
userPreferencesConfigurator.normalizePreferenceName("some-name.over_rainbow"),
"some-name.over_rainbow");
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) 2012-2021 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.namespace.configurator;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
import java.util.List;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.user.server.UserManager;
import org.eclipse.che.api.user.server.model.impl.UserImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
/**
* Tests for {@link UserProfileConfigurator}.
*
* @author Pavol Baran
*/
@Listeners(MockitoTestNGListener.class)
public class UserProfileConfiguratorTest {
private static final String USER_ID = "user-id";
private static final String USER_NAME = "user-name";
private static final String USER_EMAIL = "user-email";
private static final String USER_NAMESPACE = "user-namespace";
@Mock private KubernetesNamespaceFactory namespaceFactory;
@Mock private KubernetesClientFactory clientFactory;
@Mock private UserManager userManager;
@InjectMocks private UserProfileConfigurator userProfileConfigurator;
private KubernetesServer kubernetesServer;
private NamespaceResolutionContext context;
@BeforeMethod
public void setUp() throws InfrastructureException, NotFoundException, ServerException {
context = new NamespaceResolutionContext(null, USER_ID, USER_NAME);
kubernetesServer = new KubernetesServer(true, true);
kubernetesServer.before();
when(userManager.getById(USER_ID)).thenReturn(new UserImpl(USER_ID, USER_EMAIL, USER_NAME));
when(namespaceFactory.evaluateNamespaceName(any())).thenReturn(USER_NAMESPACE);
when(clientFactory.create()).thenReturn(kubernetesServer.getClient());
}
@AfterMethod
public void cleanUp() {
kubernetesServer.after();
}
@Test
public void shouldCreateProfileSecret() throws InfrastructureException {
userProfileConfigurator.configure(context);
List<Secret> secrets =
kubernetesServer.getClient().secrets().inNamespace(USER_NAMESPACE).list().getItems();
assertEquals(secrets.size(), 1);
assertEquals(secrets.get(0).getMetadata().getName(), "user-profile");
}
@Test(
expectedExceptions = InfrastructureException.class,
expectedExceptionsMessageRegExp = "Could not find current user with id:" + USER_ID + ".")
public void shouldNotCreateSecretOnException()
throws NotFoundException, ServerException, InfrastructureException {
when(userManager.getById(USER_ID)).thenThrow(new ServerException("test exception"));
userProfileConfigurator.configure(context);
fail("InfrastructureException should have been thrown.");
}
}

View File

@ -53,6 +53,9 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesDev
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserPreferencesConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserProfileConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.CommonPVCStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.PerWorkspacePVCStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.UniqueWorkspacePVCStrategy;
@ -110,6 +113,11 @@ public class OpenShiftInfraModule extends AbstractModule {
workspaceAttributeValidators.addBinding().to(K8sInfraNamespaceWsAttributeValidator.class);
workspaceAttributeValidators.addBinding().to(AsyncStorageModeValidator.class);
Multibinder<NamespaceConfigurator> namespaceConfigurators =
Multibinder.newSetBinder(binder(), NamespaceConfigurator.class);
namespaceConfigurators.addBinding().to(UserProfileConfigurator.class);
namespaceConfigurators.addBinding().to(UserPreferencesConfigurator.class);
bind(KubernetesNamespaceService.class);
MapBinder<String, InternalEnvironmentFactory> factories =

View File

@ -207,7 +207,7 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
}
@Override
protected Optional<KubernetesNamespaceMeta> fetchNamespace(String name)
public Optional<KubernetesNamespaceMeta> fetchNamespace(String name)
throws InfrastructureException {
return fetchNamespaceObject(name).map(this::asNamespaceMeta);
}