diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/NamespaceProvisioner.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/NamespaceProvisioner.java index b3a9b81235..4fd9834155 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/NamespaceProvisioner.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/NamespaceProvisioner.java @@ -22,6 +22,7 @@ import io.fabric8.kubernetes.client.KubernetesClientException; import java.util.Base64; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import javax.inject.Inject; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; @@ -46,9 +47,9 @@ public class NamespaceProvisioner implements EventSubscriber userPreferencesSecret = preparePreferencesSecret(user); try { String namespace = - namespaceFactory.evaluateNamespaceName( - new NamespaceResolutionContext(null, user.getId(), user.getName())); + namespaceFactory.evaluateNamespaceName( + new NamespaceResolutionContext(null, user.getId(), user.getName())); clientFactory.create().secrets().inNamespace(namespace).createOrReplace(userProfileSecret); - if (userPreferencesSecret != null) { - clientFactory.create().secrets().inNamespace(namespace).createOrReplace(userPreferencesSecret); + if (userPreferencesSecret.isPresent()) { + clientFactory + .create() + .secrets() + .inNamespace(namespace) + .createOrReplace(userPreferencesSecret.get()); } } catch (InfrastructureException | KubernetesClientException e) { LOG.error("There was a failure while creating user information secrets.", e); } - } private Secret prepareProfileSecret(User user) { @@ -118,7 +127,7 @@ public class NamespaceProvisioner implements EventSubscriber preparePreferencesSecret(User user) { Base64.Encoder enc = Base64.getEncoder(); Map preferences; try { @@ -126,39 +135,42 @@ public class NamespaceProvisioner implements EventSubscriber preferencesEncoded = new HashMap<>(); preferences.forEach( (key, value) -> - preferencesEncoded.put(normalizeDataKey(key), enc.encodeToString(value.getBytes()))); + 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(); + return Optional.of( + 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. This - * method replaces illegal characters with "-" (dash). + * 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 normalizeDataKey(String name) { + String normalizePreferenceName(String name) { return name.replaceAll("[^-._a-zA-Z0-9]+", "-").replaceAll("-+", "-"); } } diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/NamespaceProvisionerTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/NamespaceProvisionerTest.java index 1437b9bb22..b323d4c521 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/NamespaceProvisionerTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/NamespaceProvisionerTest.java @@ -11,10 +11,126 @@ */ package org.eclipse.che.workspace.infrastructure.kubernetes.provision; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.client.server.mock.KubernetesServer; +import java.util.Collections; +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.api.server.impls.KubernetesNamespaceMetaImpl; +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.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; import org.testng.annotations.Test; +@Listeners(MockitoTestNGListener.class) public class NamespaceProvisionerTest { + @Mock private KubernetesNamespaceFactory namespaceFactory; + @Mock private KubernetesClientFactory clientFactory; + @Mock private UserManager userManager; + @Mock private PreferenceManager preferenceManager; + @InjectMocks private NamespaceProvisioner namespaceProvisioner; + + 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"; + + private KubernetesServer kubernetesServer; + + @BeforeMethod + public void setUp() throws InfrastructureException, NotFoundException, ServerException { + kubernetesServer = new KubernetesServer(true, true); + kubernetesServer.before(); + + Map preferences = new HashMap<>(); + preferences.put("preference-name", "preference"); + + lenient().when(clientFactory.create()).thenReturn(kubernetesServer.getClient()); + lenient() + .when(namespaceFactory.provision(any())) + .thenReturn(new KubernetesNamespaceMetaImpl(USER_NAMESPACE, Collections.emptyMap())); + 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(preferenceManager.find(USER_ID)).thenReturn(preferences); + } + + @AfterMethod + public void cleanUp() { + kubernetesServer.after(); + } + @Test - public void normalizePreferenceNameTest() {} + public void shouldCreateSecretsOnNamespaceProvision() throws InfrastructureException { + namespaceProvisioner.provision(new NamespaceResolutionContext(null, USER_ID, USER_NAME)); + List createdSecrets = + kubernetesServer.getClient().secrets().inNamespace(USER_NAMESPACE).list().getItems(); + assertEquals(createdSecrets.size(), 2); + assertTrue( + createdSecrets + .stream() + .anyMatch(secret -> secret.getMetadata().getName().equals("user-profile"))); + assertTrue( + createdSecrets + .stream() + .anyMatch(secret -> secret.getMetadata().getName().equals("user-preferences"))); + } + + @Test + public void shouldCreateOnlyProfileSecret() throws InfrastructureException, ServerException { + when(preferenceManager.find(USER_ID)).thenReturn(Collections.emptyMap()); + namespaceProvisioner.provision(new NamespaceResolutionContext(null, USER_ID, USER_NAME)); + List createdSecrets = + kubernetesServer.getClient().secrets().inNamespace(USER_NAMESPACE).list().getItems(); + assertEquals(createdSecrets.size(), 1); + assertEquals(createdSecrets.get(0).getMetadata().getName(), "user-profile"); + } + + @Test + public void shouldCreateNoSecretOnException() throws InfrastructureException, NotFoundException, ServerException { + when(userManager.getById(USER_ID)).thenThrow(new ServerException("Test server exception")); + namespaceProvisioner.provision(new NamespaceResolutionContext(null, USER_ID, USER_NAME)); + assertTrue(kubernetesServer.getClient().secrets().inNamespace(USER_NAMESPACE).list().getItems().isEmpty()); + verifyNoInteractions(clientFactory); + } + + @Test + public void shouldNormalizePreferenceName() { + assertEquals(namespaceProvisioner.normalizePreferenceName("codename:bond"), "codename-bond"); + assertEquals(namespaceProvisioner.normalizePreferenceName("some--:pref"), "some-pref"); + assertEquals(namespaceProvisioner.normalizePreferenceName("pref[name].sub"), "pref-name-.sub"); + } + + @Test + public void shouldKeepPreferenceName() { + assertEquals(namespaceProvisioner.normalizePreferenceName("codename.bond"), "codename.bond"); + assertEquals(namespaceProvisioner.normalizePreferenceName("pref_name"), "pref_name"); + assertEquals( + namespaceProvisioner.normalizePreferenceName("some-name.over_rainbow"), + "some-name.over_rainbow"); + } }